diff --git a/.github/assets/cookiecutter-scverse-instance.json b/.github/assets/cookiecutter-scverse-instance.json index 1d6e8428..874300dc 100644 --- a/.github/assets/cookiecutter-scverse-instance.json +++ b/.github/assets/cookiecutter-scverse-instance.json @@ -8,6 +8,9 @@ "github_user": "scverse", "project_repo": "https://github.com/scverse/cookiecutter-scverse-instance", "license": "BSD 3-Clause License", - "_copy_without_render": [".github/workflows/**.yaml"] + "_copy_without_render": [ + ".github/workflows/**.yaml", + "docs/_templates/autosummary/**.rst" + ] } } diff --git a/cookiecutter.json b/cookiecutter.json index cc9cad13..5b341c30 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -14,5 +14,8 @@ "GNU General Public License Version 3", "Unlicense" ], - "_copy_without_render": [".github/workflows/**.yaml"] + "_copy_without_render": [ + ".github/workflows/**.yaml", + "docs/_templates/autosummary/**.rst" + ] } diff --git a/{{cookiecutter.project_name}}/.flake8 b/{{cookiecutter.project_name}}/.flake8 index da171efb..3ba9f6fe 100644 --- a/{{cookiecutter.project_name}}/.flake8 +++ b/{{cookiecutter.project_name}}/.flake8 @@ -48,6 +48,8 @@ rst-roles = class, func, ref, + cite:p, + cite:t, rst-directives = envvar, exception, diff --git a/{{cookiecutter.project_name}}/.readthedocs.yaml b/{{cookiecutter.project_name}}/.readthedocs.yaml index 170325e9..9e5d5fa2 100644 --- a/{{cookiecutter.project_name}}/.readthedocs.yaml +++ b/{{cookiecutter.project_name}}/.readthedocs.yaml @@ -6,6 +6,7 @@ build: python: "3.10" sphinx: configuration: docs/conf.py + # disable this for more lenient docs builds fail_on_warning: true python: install: diff --git a/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst b/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst new file mode 100644 index 00000000..49f45edd --- /dev/null +++ b/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst @@ -0,0 +1,65 @@ +{{ fullname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. add toctree option to make autodoc generate the pages + +.. autoclass:: {{ objname }} + +{% block attributes %} +{% if attributes %} +Attributes table +~~~~~~~~~~~~~~~~~~ + +.. autosummary:: +{% for item in attributes %} + ~{{ fullname }}.{{ item }} +{%- endfor %} +{% endif %} +{% endblock %} + +{% block methods %} +{% if methods %} +Methods table +~~~~~~~~~~~~~ + +.. autosummary:: +{% for item in methods %} + {%- if item != '__init__' %} + ~{{ fullname }}.{{ item }} + {%- endif -%} +{%- endfor %} +{% endif %} +{% endblock %} + +{% block attributes_documentation %} +{% if attributes %} +Attributes +~~~~~~~~~~~ + +{% for item in attributes %} +{{ item }} +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoattribute:: {{ [objname, item] | join(".") }} +{%- endfor %} + +{% endif %} +{% endblock %} + +{% block methods_documentation %} +{% if methods %} +Methods +~~~~~~~ + +{% for item in methods %} +{%- if item != '__init__' %} +{{ item }} +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automethod:: {{ [objname, item] | join(".") }} +{%- endif -%} +{%- endfor %} + +{% endif %} +{% endblock %} diff --git a/{{cookiecutter.project_name}}/docs/api.md b/{{cookiecutter.project_name}}/docs/api.md index c508b486..54a81166 100644 --- a/{{cookiecutter.project_name}}/docs/api.md +++ b/{{cookiecutter.project_name}}/docs/api.md @@ -34,4 +34,5 @@ :toctree: generated pl.basic_plot + pl.BasicClass ``` diff --git a/{{cookiecutter.project_name}}/docs/conf.py b/{{cookiecutter.project_name}}/docs/conf.py index 27b4ba26..52e38548 100644 --- a/{{cookiecutter.project_name}}/docs/conf.py +++ b/{{cookiecutter.project_name}}/docs/conf.py @@ -17,10 +17,11 @@ # -- Project information ----------------------------------------------------- info = metadata("{{cookiecutter.project_name}}") -project = info["Name"] +project_name = info["Name"] author = info["Author"] copyright = f"{datetime.now():%Y}, {author}." version = info["Version"] +repository_url = "https://github.com/" + "{{cookiecutter.github_user}}" + "/" + project_name # The full version, including alpha/beta/rc tags release = info["Version"] @@ -33,7 +34,7 @@ html_context = { "display_github": True, # Integrate GitHub "github_user": "{{cookiecutter.github_user}}", # Username - "github_repo": project, # Repo name + "github_repo": project_name, # Repo name "github_version": "main", # Version "conf_py_path": "/docs/", # Path in the checkout to the docs root } @@ -43,15 +44,14 @@ # Add any Sphinx extension module names here, as strings. # They can be extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ - "myst_parser", + "myst_nb", + "sphinx_copybutton", "sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx.ext.autosummary", "sphinx.ext.napoleon", "sphinxcontrib.bibtex", "sphinx_autodoc_typehints", - "scanpydoc.definition_list_typed_field", - "nbsphinx", "sphinx.ext.mathjax", *[p.stem for p in (HERE / "extensions").glob("*.py")], ] @@ -65,13 +65,31 @@ napoleon_use_rtype = True # having a separate entry generally helps readability napoleon_use_param = True myst_heading_anchors = 3 # create anchors for h1-h3 +myst_enable_extensions = [ + "amsmath", + "colon_fence", + "deflist", + "dollarmath", + "html_image", + "html_admonition", +] +myst_url_schemes = ("http", "https", "mailto") +nb_output_stderr = "remove" +nb_execution_mode = "off" +nb_merge_streams = True +typehints_defaults = "braces" + +source_suffix = { + ".rst": "restructuredtext", + ".ipynb": "myst-nb", + ".myst": "myst-nb", +} intersphinx_mapping = { "anndata": ("https://anndata.readthedocs.io/en/stable/", None), "numpy": ("https://numpy.org/doc/stable/", None), } -nbsphinx_execute = "never" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -84,10 +102,16 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = "furo" +html_theme = "sphinx_book_theme" html_static_path = ["_static"] +html_title = project_name + +html_theme_options = { + "repository_url": repository_url, + "use_repository_button": True, +} -pygments_style = "sphinx" +pygments_style = "default" nitpick_ignore = [ # If building the documentation fails because of a missing link that is outside your control, diff --git a/{{cookiecutter.project_name}}/docs/developer_docs.md b/{{cookiecutter.project_name}}/docs/developer_docs.md index a5fe49d4..53a57d53 100644 --- a/{{cookiecutter.project_name}}/docs/developer_docs.md +++ b/{{cookiecutter.project_name}}/docs/developer_docs.md @@ -20,6 +20,7 @@ On the RTD dashboard choose "Import a Project" and follow the instructions to ad that break the documentation. To do so, got to `Admin -> Advanced Settings`, check the `Build pull requests for this projects` option, and click `Save`. For more information, please refer to the [official RTD documentation](https://docs.readthedocs.io/en/stable/pull-requests.html). +- If you find the RTD builds are failing, you can disable the `fail_on_warning` option in `.readthedocs.yaml`. ### Coverage tests with _Codecov_ @@ -289,15 +290,15 @@ Please write documentation for your package. This project uses [sphinx][] with t - the [myst][] extension allows to write documentation in markdown/Markedly Structured Text - [Numpy-style docstrings][numpydoc] (through the [napoloen][numpydoc-napoleon] extension). -- Jupyter notebooks as tutorials through [nbsphinx][] (See [Tutorials with nbsphinx](#tutorials-with-nbsphinx-and-jupyter-notebooks)) +- Jupyter notebooks as tutorials through [myst-nb][] (See [Tutorials with myst-nb](#tutorials-with-myst-nb-and-jupyter-notebooks)) - [Sphinx autodoc typehints][], to automatically reference annotated input and output types See the [scanpy developer docs](https://scanpy.readthedocs.io/en/latest/dev/documentation.html) for more information on how to write documentation. -### Tutorials with nbsphinx and jupyter notebooks +### Tutorials with myst-nb and jupyter notebooks -The documentation is set-up to render jupyter notebooks stored in the `docs/notebooks` directory using [nbsphinx][]. +The documentation is set-up to render jupyter notebooks stored in the `docs/notebooks` directory using [myst-nb][]. Currently, only notebooks in `.ipynb` format are supported that will be included with both their input and output cells. It is your reponsibility to update and re-run the notebook whenever necessary. @@ -305,8 +306,6 @@ If you are interested in automatically running notebooks as part of the continuo out [this feature request](https://github.com/scverse/cookiecutter-scverse/issues/40) in the `cookiecutter-scverse` repository. -[nbsphinx]: https://github.com/spatialaudio/nbsphinx - #### Hints - If you refer to objects from other packages, please add an entry to `intersphinx_mapping` in `docs/conf.py`. Only @@ -329,7 +328,7 @@ open _build/html/index.html [codecov docs]: https://docs.codecov.com/docs [pre-commit.ci]: https://pre-commit.ci/ [readthedocs.org]: https://readthedocs.org/ -[nbshpinx]: https://github.com/spatialaudio/nbsphinx +[myst-nb]: https://myst-nb.readthedocs.io/en/latest/ [jupytext]: https://jupytext.readthedocs.io/en/latest/ [pre-commit]: https://pre-commit.com/ [anndata]: https://github.com/scverse/anndata diff --git a/{{cookiecutter.project_name}}/docs/extensions/.gitkeep b/{{cookiecutter.project_name}}/docs/extensions/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/{{cookiecutter.project_name}}/docs/extensions/typed_returns.py b/{{cookiecutter.project_name}}/docs/extensions/typed_returns.py new file mode 100644 index 00000000..94478130 --- /dev/null +++ b/{{cookiecutter.project_name}}/docs/extensions/typed_returns.py @@ -0,0 +1,29 @@ +# code from https://github.com/theislab/scanpy/blob/master/docs/extensions/typed_returns.py +# with some minor adjustment +import re + +from sphinx.application import Sphinx +from sphinx.ext.napoleon import NumpyDocstring + + +def _process_return(lines): + for line in lines: + m = re.fullmatch(r"(?P\w+)\s+:\s+(?P[\w.]+)", line) + if m: + # Once this is in scanpydoc, we can use the fancy hover stuff + yield f'-{m["param"]} (:class:`~{m["type"]}`)' + else: + yield line + + +def _parse_returns_section(self, section): + lines_raw = list(_process_return(self._dedent(self._consume_to_next_section()))) + lines = self._format_block(":returns: ", lines_raw) + if lines and lines[-1]: + lines.append("") + return lines + + +def setup(app: Sphinx): + """Set app.""" + NumpyDocstring._parse_returns_section = _parse_returns_section diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index ac4bdb28..d3a4976e 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -29,13 +29,13 @@ dev = [ ] doc = [ "sphinx>=4", - "furo", - "myst-parser", + "sphinx-book-theme>=0.3.3", + "myst-nb", "sphinxcontrib-bibtex>=1.0.0", - "scanpydoc[typehints]>=0.7.4", + "sphinx-autodoc-typehints", # For notebooks - "nbsphinx", - "ipykernel" + "ipykernel", + "sphinx-copybutton", ] test = [ "pytest", diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/pl/__init__.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/pl/__init__.py index a9cd20a3..c2315dd0 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/pl/__init__.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/pl/__init__.py @@ -1 +1 @@ -from .basic import basic_plot +from .basic import BasicClass, basic_plot diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/pl/basic.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/pl/basic.py index 71b3627d..431582b5 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/pl/basic.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/pl/basic.py @@ -2,6 +2,44 @@ def basic_plot(adata: AnnData) -> int: - """Generate a basic plot for an AnnData object.""" + """Generate a basic plot for an AnnData object. + + Parameters + ---------- + adata + The AnnData object to preprocess. + + Returns + ------- + Some integer value. + """ print("Import matplotlib and implement a plotting function here.") return 0 + + +class BasicClass: + """A basic class. + + Parameters + ---------- + adata + The AnnData object to preprocess. + """ + + def __init__(self, adata: AnnData): + print("Implement a class here.") + + def my_method(self, param: int) -> int: + """A basic method. + + Parameters + ---------- + param + A parameter. + + Returns + ------- + Some integer value. + """ + print("Implement a method here.") + return 0 diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/pp/basic.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/pp/basic.py index 936f1194..5ff1d411 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/pp/basic.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/pp/basic.py @@ -2,6 +2,16 @@ def basic_preproc(adata: AnnData) -> int: - """Run a basic preprocessing on the AnnData object.""" + """Run a basic preprocessing on the AnnData :cite:p:`Wolf2018` object. + + Parameters + ---------- + adata + The AnnData object to preprocess. + + Returns + ------- + Some integer value. + """ print("Implement a preprocessing function here.") return 0 diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/tl/basic.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/tl/basic.py index 4e3387c5..d215ade4 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/tl/basic.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/tl/basic.py @@ -2,6 +2,16 @@ def basic_tool(adata: AnnData) -> int: - """Run a tool on the AnnData object.""" + """Run a tool on the AnnData object. + + Parameters + ---------- + adata + The AnnData object to preprocess. + + Returns + ------- + Some integer value. + """ print("Implement a tool to run on the AnnData object.") return 0