diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6d96a4d..8ac1c82 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -54,8 +54,5 @@ repos: rev: "v1.2.0" hooks: - id: mypy - additional_dependencies: [ - "git+https://github.com/python-cosmology/cosmology-api.git#egg=cosmology-api" - ] args: - --strict diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..7bd1c9d --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +api/reference diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_ext/sphinx_ext_autosummary_context.py b/docs/_ext/sphinx_ext_autosummary_context.py new file mode 100644 index 0000000..611d111 --- /dev/null +++ b/docs/_ext/sphinx_ext_autosummary_context.py @@ -0,0 +1,51 @@ +"""Monkey-patching autosummary to emit the autosummary-gather-context event.""" +from inspect import signature +from typing import TYPE_CHECKING + +from sphinx.errors import ExtensionError +from sphinx.ext.autosummary.generate import ( + generate_autosummary_content, + generate_autosummary_docs, +) + +if TYPE_CHECKING: + from sphinx.application import Sphinx + +GENERATE_SIGNATURE = signature(generate_autosummary_content) + + +def generate_autosummary_content_with_context(*args: object, **kwargs: object) -> str: + """Wrap generate_autosummary_content to emit autosummary-gather-context.""" + ba = GENERATE_SIGNATURE.bind_partial(*args, **kwargs) + app = ba.arguments["app"] + if app: + name = ba.arguments["name"] + obj = ba.arguments["obj"] + parent = ba.arguments["parent"] + context = ba.arguments["context"] + results = app.emit("autosummary-gather-context", name, obj, parent, context) + for extra_context in results: + if extra_context: + context.update(extra_context) + return generate_autosummary_content(*args, **kwargs) # type: ignore[arg-type] + + +def setup(app: "Sphinx") -> None: + """Monkey-patch the autosummary-gather-context event if not present.""" + # try connecting a mock callback to autosummary-gather-context + try: + listener_id = app.connect("autosummary-gather-context", lambda: None) + except ExtensionError: + listener_id = None + + if listener_id is not None: + # event exists, all good + app.disconnect(listener_id) + else: + # patch generate_autosummary_docs to call the wrapper above + generate_autosummary_docs.__globals__[ + "generate_autosummary_content" + ] = generate_autosummary_content_with_context + + # register the new event + app.add_event("autosummary-gather-context") diff --git a/docs/_ext/sphinx_ext_cosmology_api.py b/docs/_ext/sphinx_ext_cosmology_api.py new file mode 100644 index 0000000..fbcef40 --- /dev/null +++ b/docs/_ext/sphinx_ext_cosmology_api.py @@ -0,0 +1,84 @@ +# ruff: noqa +import sys +from types import ModuleType +from inspect import getmembers, getmro +from sphinx.util.inspect import signature, stringify_signature + +# get_overloads is a Python 3.11 feature +try: + from typing import get_overloads +except ImportError: + get_overloads = None + +import cosmology +import cosmology.api + +DOCS_MODULE_NAME = "cosmology.docs" + + +class cosmo(cosmology.api.StandardCosmology): + """This is a mock cosmology class used for documenting all members + of the cosmology.api protocols. + """ + + +COSMOLOGY_BASES = [cls for cls in getmro(cosmo) if cls is not cosmo] + + +def context_callback(app, name, obj, parent, context): + """Callback function to provide extra context for reference.""" + # add protocols to context if obj is a method or property of Cosmology + extra_context = None + if parent is cosmo: + _, _, membername = name.rpartition(".") + protocols = [ + f"{cls.__qualname__}.{membername}" + for cls in COSMOLOGY_BASES + if hasattr(cls, membername) + ] + extra_context = {"protocols": protocols} + return extra_context + + +def signature_callback(app, what, name, obj, options, sig, return_annotation): + """Callback function to provide overloaded signatures.""" + if what in ("function", "method") and callable(obj): + overloads = get_overloads(obj) + if overloads: + kwargs = {} + if app.config.autodoc_typehints in ("none", "description"): + kwargs["show_annotation"] = False + if app.config.autodoc_typehints_format == "short": + kwargs["unqualified_typehints"] = True + type_aliases = app.config.autodoc_type_aliases + bound_method = what == "method" + sigs = [] + for overload in overloads: + overload_sig = signature( + overload, bound_method=bound_method, type_aliases=type_aliases + ) + sigs.append(stringify_signature(overload_sig, **kwargs)) + return "\n".join(sigs), None + + +def setup(app): + """Initialise the cosmology documentation extension.""" + # create a mock `cosmology.docs` module containing `Cosmology` + docs_module = ModuleType(DOCS_MODULE_NAME) + docs_module.cosmo = cosmo + cosmo.__module__ = DOCS_MODULE_NAME + sys.modules[DOCS_MODULE_NAME] = docs_module + + # change the autosummary_filename_map for all members of Cosmology + filename_map = {} + filename_prefix = f"{cosmo.__module__}.{cosmo.__name__}" + for name, _obj in getmembers(cosmo): + filename_map[f"{filename_prefix}.{name}"] = name + app.config.autosummary_filename_map.update(filename_map) + + # register a callback for extra autosummary context + app.connect("autosummary-gather-context", context_callback) + + # register a callback to show overloaded signatures + if get_overloads: + app.connect("autodoc-process-signature", signature_callback) diff --git a/docs/_static/.gitignore b/docs/_static/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/docs/_templates/autosummary/reference.rst b/docs/_templates/autosummary/reference.rst new file mode 100644 index 0000000..881c952 --- /dev/null +++ b/docs/_templates/autosummary/reference.rst @@ -0,0 +1,20 @@ +{{ name | escape | underline }} + +{% if protocols -%} +.. rubric:: Protocols + +.. currentmodule:: cosmology +{% for protocol in protocols %} +{% if loop.first %}.. {{ objtype }}:: {{ protocol }} +{%- else %}{% for c in objtype %} {% endfor %} {{ protocol }} +{%- endif %} +{%- endfor %} + :nocontentsentry: + +{% endif -%} + +.. rubric:: Reference + +.. currentmodule:: {{ module }} + +.. auto{{ objtype }}:: {{ objname }} diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..b75ebae --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,6 @@ + +Changelog +========= + +.. include:: ../CHANGES.rst + :start-line: 2 diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..e795213 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,177 @@ +# ruff: noqa + +"""Configuration file for the Sphinx documentation builder. + +This file only contains a selection of the most common options. For a full +list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html +""" + + +import os +import sys +import tomli + +sys.path.append(os.path.abspath("../src")) + + +# -- Project information ----------------------------------------------------- + + +def read_pyproject(): + """Get author information from package metadata.""" + with open(os.path.abspath("../pyproject.toml"), "rb") as f: + toml = tomli.load(f) + + project = dict(toml["project"]) + version = project["version"] + authors = ", ".join(d["name"] for d in project["authors"]) + + return version, authors + + +package_version, package_authors = read_pyproject() + +project = "cosmology" +author = package_authors +copyright = f"2023, {author}" + + +# The full version, including alpha/beta/rc tags. +release = package_version +# The short X.Y version. +version = release.partition("-")[0] + + +# -- General configuration --------------------------------------------------- + +sys.path.append(os.path.abspath("./_ext")) + +# By default, highlight as Python 3. +highlight_language = "python3" + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autosummary", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "numpydoc", + "sphinx_copybutton", + "sphinx_ext_autosummary_context", + "sphinx_ext_cosmology_api", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# This is added to the end of RST files - a good place to put substitutions to +# be used globally. +rst_epilog = """ +.. |author| replace:: {author} + +.. _Python: http://www.python.org +""" + +intersphinx_mapping = { + "python": ( + "https://docs.python.org/3/", + (None, "http://data.astropy.org/intersphinx/python3.inv"), + ), + "numpy": ( + "https://numpy.org/doc/stable/", + (None, "http://data.astropy.org/intersphinx/numpy.inv"), + ), + "scipy": ( + "https://docs.scipy.org/doc/scipy/reference/", + (None, "https://docs.scipy.org/doc/scipy/objects.inv"), + ), +} + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "furo" + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = f"{project} v{release}" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + + +# -- autodoc extension ------------------------------------------------------- + +# The default options for autodoc directives. They are applied to all autodoc +# directives automatically. It must be a dictionary which maps option names to +# the values. +autodoc_default_options = { + "members": False, + "inherited-members": False, + "show-inheritance": True, +} + + +add_module_names = False + +# autodoc_class_signature = "separated" + +autosummary_generate = True + + +# -- numpydoc extension ------------------------------------------------------ + +# Whether to show all members of a class in the Methods and Attributes sections +# automatically. True by default. +numpydoc_show_class_members = True + +# Whether to show all inherited members of a class in the Methods and +# Attributes sections automatically. If it's false, inherited members won't +# shown. True by default. +numpydoc_show_inherited_class_members = True + +# Whether to create a Sphinx table of contents for the lists of class methods +# and attributes. If a table of contents is made, Sphinx expects each entry to +# have a separate page. True by default. +numpydoc_class_members_toctree = False + +# A regular expression matching citations which should be mangled to avoid +# conflicts due to duplication across the documentation. Defaults to '[\w-]+'. +# > numpydoc_citation_re = '[\w-]+' + +# Whether to format the Attributes section of a class page in the same way as +# the Parameter section. If it's False, the Attributes section will be +# formatted as the Methods section using an autosummary table. True by default. +numpydoc_attributes_as_param_list = False + +# Whether to create cross-references for the parameter types in the +# Parameters, Other Parameters, Returns and Yields sections of the docstring. +numpydoc_xref_param_type = True + +# Words not to cross-reference. Most likely, these are common words used in +# parameter type descriptions that may be confused for classes of the same +# name. This can be overwritten or modified in packages and is provided here +# for convenience. +numpydoc_xref_ignore = { + "or", + "default", + "optional", + "positional-only", + "keyword-only", +} + +# -- copybutton extension ------------------------------------------------------ + +copybutton_exclude = ".linenos, .gp, .go" diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..b2779fe --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,25 @@ +#################### +Cosmology for Python +#################### + +Check out the `API `_! + + +.. toctree:: + :caption: Usage + :hidden: + + install + + +.. toctree:: + :caption: Other + :hidden: + + changelog + + +Contributors +============ + +.. include:: ../AUTHORS.rst diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 0000000..4b4070f --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,35 @@ +.. _cosmology-install: + +************ +Installation +************ + + +From Source: Cloning, Building, Installing +========================================== + +The latest development version of :mod:`cosmology` can be cloned from `GitHub +`_ using ``git`` + +.. code-block:: bash + + git clone https://github.com/cosmology-api/cosmology.git + +To build and install the project (from the root of the source tree, e.g., inside +the cloned :mod:`cosmology` directory) + +.. code-block:: bash + + python -m pip install [-e] . + + +Python Dependencies +=================== + +This package has the following dependencies: + +* `Python`_ >= 3.9 + +Explicit version requirements are specified in the project `pyproject.toml +`_. +``pip`` and ``conda`` should install and enforce these versions automatically. diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..32bb245 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/pyproject.toml b/pyproject.toml index a8194cc..67b3000 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] - name = "cosmology.core" + name = "cosmology" version = "0.1.0" - description = "Cosmology API standard compatability library" + description = "Cosmology Library" readme = "README.rst" requires-python = ">=3.9" license = {file = "LICENSE"} @@ -22,34 +22,32 @@ "Programming Language :: Python :: 3", ] dependencies = [ - "cosmology.api @ git+https://github.com/python-cosmology/cosmology-api.git", + "cosmology.api >= 0.1.0", ] [project.optional-dependencies] - all = [ - ] test = [ "coverage[toml]", - "numpy>=1.18", + "numpy>=1.20", "pytest", - "pytest-astropy", + "pytest-cov", + "sybil", + "typing_extensions", ] docs = [ - "graphviz", - "IPython", - "jupyter_client", - "nbsphinx", - "pydata-sphinx-theme", + "furo", + "numpydoc", "sphinx", - "sphinx-astropy", - "sphinxcontrib.bibtex < 3.0.0", - "tomlkit", + "sphinx-copybutton", + "sybil", + "tomli", + "typing_extensions", ] [project.urls] - homepage = "https://cosmology.readthedocs.org/core" - repository = "https://github.com/cosmology-api/cosmology" - documentation = "https://cosmology.readthedocs.org/core" + homepage = "https://cosmology.readthedocs.org" + repository = "https://github.com/cosmology-api/cosmolog" + documentation = "https://cosmology.readthedocs.org" [build-system] @@ -93,9 +91,11 @@ ] [tool.mypy] + python_version = 3.9 + namespace_packages = true explicit_package_bases = true - python_version = 3.9 + mypy_path = "$MYPY_CONFIG_FILE_DIR/src" strict = true disallow_subclassing_any = false @@ -105,14 +105,20 @@ warn_redundant_casts = true warn_unused_configs = true warn_unreachable = true - exclude = '''(^|/)tests/''' + exclude = '''(^|/)tests/|(^/)docs/|(^/)conftest\.py''' [[tool.mypy.overrides]] - module = "tests/*" + module = [ + "tests/*", + "docs/*", + ] ignore_errors = true [[tool.mypy.overrides]] - module="cosmology.api.*" + module = [ + "tomli.*", + "sybil.*", + ] ignore_missing_imports = true @@ -122,13 +128,26 @@ line-length = 88 select = ["ALL"] ignore = [ - "ANN101", "ANN102", "ANN401", - "ARG001", "ARG002", - "D105", "D107", "D203", "D213", - "FBT003", - "PD", + "ANN101", # Missing type annotation for {name} in method # TODO: report to Ruff + "ANN102", + "ANN401", + "ARG001", # Unused function argument + "ARG002", # Unused method argument + "COM812", # trailing-comma-missing + "D105", # Missing docstring in magic method + "D107", # Missing docstring in __init__ + "D203", # 1 blank line required before class docstring + "D213", # Multi-line docstring summary should start at the second line + "FBT003", # Boolean positional value in function call + "PD", # pandas-vet "RET505", "RET506", "RET507", ] [tool.ruff.per-file-ignores] +"docs/*.py" = ["INP001"] +"tests/*.py" = [ + "ANN", + "PLR0913", # Too many arguments to function call + "TID252", +] "test_*.py" = ["ANN", "D", "S101"]