From 5022759a96e36bad6c78ad1af2acd920273e900d Mon Sep 17 00:00:00 2001 From: Ilia Kats Date: Mon, 2 Mar 2026 17:26:31 +0100 Subject: [PATCH 1/7] docs: don't print Methods table heading if there are no methods Currently, the methods heading is printed if there are any methods, including __init__, but __init__ is filtered out from the methods list and table. This can lead to the situation that if a class has only __init__ and no other public methods, the "Methods table" and "Methods" headings will be printed, but no actual methods will be documented. Move the methods filtering to the very beginning and overwrite the methods variable with the filtered version to prevent above problem. --- .../docs/_templates/autosummary/class.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst b/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst index 7b4a0cf8..9a6bf3d2 100644 --- a/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst +++ b/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst @@ -6,6 +6,8 @@ .. autoclass:: {{ objname }} +{% set methods = methods | select("ne", "__init__") | list %} + {% block attributes %} {% if attributes %} Attributes table @@ -25,9 +27,7 @@ Methods table .. autosummary:: {% for item in methods %} - {%- if item != '__init__' %} ~{{ name }}.{{ item }} - {%- endif -%} {%- endfor %} {% endif %} {% endblock %} @@ -51,10 +51,8 @@ Methods ~~~~~~~ {% for item in methods %} -{%- if item != '__init__' %} .. automethod:: {{ [objname, item] | join(".") }} -{%- endif -%} {%- endfor %} {% endif %} From 0088489811a210d775fe12ca6428a5f7887c0d29 Mon Sep 17 00:00:00 2001 From: Ilia Kats Date: Mon, 2 Mar 2026 17:37:59 +0100 Subject: [PATCH 2/7] docs: use fullname instead of objname in the class template With this change, one can apply autosummary to submodules of the package and have everything work. --- .../docs/_templates/autosummary/class.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst b/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst index 9a6bf3d2..1cd43506 100644 --- a/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst +++ b/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst @@ -4,7 +4,7 @@ .. add toctree option to make autodoc generate the pages -.. autoclass:: {{ objname }} +.. autoclass:: {{ fullname }} {% set methods = methods | select("ne", "__init__") | list %} @@ -39,7 +39,7 @@ Attributes {% for item in attributes %} -.. autoattribute:: {{ [objname, item] | join(".") }} +.. autoattribute:: {{ [fullname, item] | join(".") }} {%- endfor %} {% endif %} @@ -52,7 +52,7 @@ Methods {% for item in methods %} -.. automethod:: {{ [objname, item] | join(".") }} +.. automethod:: {{ [fullname, item] | join(".") }} {%- endfor %} {% endif %} From bb19ebb2e822c2c9f63af29e56e0183ac05b5cf4 Mon Sep 17 00:00:00 2001 From: Ilia Kats Date: Mon, 2 Mar 2026 17:42:18 +0100 Subject: [PATCH 3/7] docs: add a module.rst autosummary template The only difference to the default template is the addition of toctree directives for functions, classes, and exceptions. This enables one to pass a module to autosummary and have it generate documenation pages and crossreferences for all top-level functions, classes, and exceptions, not just for submodules. --- .../docs/_templates/autosummary/module.rst | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 {{cookiecutter.project_name}}/docs/_templates/autosummary/module.rst diff --git a/{{cookiecutter.project_name}}/docs/_templates/autosummary/module.rst b/{{cookiecutter.project_name}}/docs/_templates/autosummary/module.rst new file mode 100644 index 00000000..9c0b618d --- /dev/null +++ b/{{cookiecutter.project_name}}/docs/_templates/autosummary/module.rst @@ -0,0 +1,62 @@ +{{ fullname | escape | underline}} + +.. automodule:: {{ fullname }} + + {% block attributes %} + {% if attributes %} + .. rubric:: {{ _('Module Attributes') }} + + .. autosummary:: + {% for item in attributes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block functions %} + {% if functions %} + .. rubric:: {{ _('Functions') }} + + .. autosummary:: + :toctree: + {% for item in functions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block classes %} + {% if classes %} + .. rubric:: {{ _('Classes') }} + + .. autosummary:: + :toctree: + {% for item in classes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block exceptions %} + {% if exceptions %} + .. rubric:: {{ _('Exceptions') }} + + .. autosummary:: + :toctree: + {% for item in exceptions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + +{% block modules %} +{% if modules %} +.. rubric:: Modules + +.. autosummary:: + :toctree: +{% for item in modules %} + {{ item }} +{%- endfor %} +{% endif %} +{% endblock %} From d23210331df38731328539a5baab1be1d659aef3 Mon Sep 17 00:00:00 2001 From: Ilia Kats Date: Tue, 3 Mar 2026 09:59:55 +0100 Subject: [PATCH 4/7] filter out __init__ in the loop, but move headings into the loop and print them in the first iteration --- .../docs/_templates/autosummary/class.rst | 35 ++++++++----------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst b/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst index 1cd43506..8aa70d4b 100644 --- a/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst +++ b/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst @@ -1,4 +1,4 @@ -{{ fullname | escape | underline}} +{{ fullname | escape | underline }} .. currentmodule:: {{ module }} @@ -6,54 +6,47 @@ .. autoclass:: {{ fullname }} -{% set methods = methods | select("ne", "__init__") | list %} - {% block attributes %} -{% if attributes %} +{% for item in attributes %} +{% if loop.first %} Attributes table ~~~~~~~~~~~~~~~~ .. autosummary:: -{% for item in attributes %} +{% endif %} ~{{ name }}.{{ item }} {%- endfor %} -{% endif %} {% endblock %} {% block methods %} -{% if methods %} +{% for item in methods if item != "__init__" %} +{% if loop.first %} Methods table ~~~~~~~~~~~~~ .. autosummary:: -{% for item in methods %} - ~{{ name }}.{{ item }} -{%- endfor %} {% endif %} + ~{{ name }}.{{ item }} +{% endfor %} {% endblock %} {% block attributes_documentation %} -{% if attributes %} +{% for item in attributes %} +{% if loop.first %} Attributes ~~~~~~~~~~ -{% for item in attributes %} - +{% endif %} .. autoattribute:: {{ [fullname, item] | join(".") }} {%- endfor %} - -{% endif %} {% endblock %} {% block methods_documentation %} -{% if methods %} +{% for item in methods if item != "__init__" %} +{% if loop.first %} Methods ~~~~~~~ - -{% for item in methods %} - +{% endif %} .. automethod:: {{ [fullname, item] | join(".") }} {%- endfor %} - -{% endif %} {% endblock %} From a765df67c6ce1fb1239a4879407ac494d79afcc5 Mon Sep 17 00:00:00 2001 From: Ilia Kats Date: Tue, 3 Mar 2026 15:07:27 +0100 Subject: [PATCH 5/7] re-indroduce pre-filtering of methods --- .../docs/_templates/autosummary/class.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst b/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst index 8aa70d4b..11ec7ff2 100644 --- a/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst +++ b/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst @@ -6,6 +6,8 @@ .. autoclass:: {{ fullname }} +{% set methods = methods | select("ne", "__init__") | list %} + {% block attributes %} {% for item in attributes %} {% if loop.first %} @@ -19,7 +21,7 @@ Attributes table {% endblock %} {% block methods %} -{% for item in methods if item != "__init__" %} +{% for item in methods %} {% if loop.first %} Methods table ~~~~~~~~~~~~~ @@ -42,7 +44,7 @@ Attributes {% endblock %} {% block methods_documentation %} -{% for item in methods if item != "__init__" %} +{% for item in methods %} {% if loop.first %} Methods ~~~~~~~ From 976cf82e9441a8ae752620e737bc4b45c2d6cd2d Mon Sep 17 00:00:00 2001 From: Ilia Kats Date: Tue, 24 Mar 2026 13:51:16 +0100 Subject: [PATCH 6/7] class template: indent everything below autoclass this is necessary to make it pick up cross-references within the current class, e.g. :attr:`n_obs`. --- .../docs/_templates/autosummary/class.rst | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst b/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst index 11ec7ff2..a65b285c 100644 --- a/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst +++ b/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst @@ -11,44 +11,44 @@ {% block attributes %} {% for item in attributes %} {% if loop.first %} -Attributes table -~~~~~~~~~~~~~~~~ + Attributes table + ~~~~~~~~~~~~~~~~ -.. autosummary:: + .. autosummary:: {% endif %} - ~{{ name }}.{{ item }} + ~{{ name }}.{{ item }} {%- endfor %} {% endblock %} {% block methods %} {% for item in methods %} {% if loop.first %} -Methods table -~~~~~~~~~~~~~ + Methods table + ~~~~~~~~~~~~~ -.. autosummary:: + .. autosummary:: {% endif %} - ~{{ name }}.{{ item }} + ~{{ name }}.{{ item }} {% endfor %} {% endblock %} {% block attributes_documentation %} {% for item in attributes %} {% if loop.first %} -Attributes -~~~~~~~~~~ + Attributes + ~~~~~~~~~~ {% endif %} -.. autoattribute:: {{ [fullname, item] | join(".") }} + .. autoattribute:: {{ [fullname, item] | join(".") }} {%- endfor %} {% endblock %} {% block methods_documentation %} {% for item in methods %} {% if loop.first %} -Methods -~~~~~~~ + Methods + ~~~~~~~ {% endif %} -.. automethod:: {{ [fullname, item] | join(".") }} + .. automethod:: {{ [fullname, item] | join(".") }} {%- endfor %} {% endblock %} From 2cad863e7e222b1c1c3de4c9a524cb4104cff4f6 Mon Sep 17 00:00:00 2001 From: Ilia Kats Date: Thu, 26 Mar 2026 16:45:12 +0100 Subject: [PATCH 7/7] add Sphinx extension to determine a class member's type This is necessary to have return types for properties. See discussion in https://github.com/scverse/mudata/pull/130 --- .../docs/_templates/autosummary/class.rst | 2 +- .../docs/extensions/member_type.py | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 {{cookiecutter.project_name}}/docs/extensions/member_type.py diff --git a/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst b/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst index a65b285c..7252c8c8 100644 --- a/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst +++ b/{{cookiecutter.project_name}}/docs/_templates/autosummary/class.rst @@ -39,7 +39,7 @@ ~~~~~~~~~~ {% endif %} - .. autoattribute:: {{ [fullname, item] | join(".") }} + .. auto{{ [fullname, item] | join(".") | member_type }}:: {{ [fullname, item] | join(".") }} {%- endfor %} {% endblock %} diff --git a/{{cookiecutter.project_name}}/docs/extensions/member_type.py b/{{cookiecutter.project_name}}/docs/extensions/member_type.py new file mode 100644 index 00000000..d3e061cb --- /dev/null +++ b/{{cookiecutter.project_name}}/docs/extensions/member_type.py @@ -0,0 +1,34 @@ +"""Extension adding a jinja2 filter that determines a class member’s type.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Literal + +from jinja2.defaults import DEFAULT_FILTERS +from jinja2.utils import import_string + +if TYPE_CHECKING: + from sphinx.application import Sphinx + + +def member_type(obj_path: str) -> Literal["method", "property", "attribute"]: + """Determine object member type. + + E.g.: `.. auto{{ '{{' }} fullname | member_type {{ '}}' }}::` + """ + # https://jinja.palletsprojects.com/en/stable/api/#custom-filters + cls_path, member_name = obj_path.rsplit(".", 1) + cls = import_string(cls_path) + member = getattr(cls, member_name, None) + match member: + case property(): + return "property" + case _ if callable(member): + return "method" + case _: + return "attribute" + + +def setup(app: Sphinx): + """App setup hook.""" + DEFAULT_FILTERS["member_type"] = member_type