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