79788781

Date: 2025-10-12 20:55:54
Score: 2
Natty:
Report link

Allow me to add a GLORIOUS (but not perfect) solution to your list.

The Problem

If you put an imported attribute without the full prefix within .. autosummary:: or after .. autodata::, Sphinx will insert the docstring of the attribute's type.

For example, if you write something like:

.. automodule:: rogue_scroll

   .. autosummary::

      SYLLABLES

.. autodata:: SYLLABLES

You would get something like:

Bad docstring

It is not easy to automatically (not hardcode) get the full path of an imported attribute. Even inside a template, you do not have easy access to that information. But I am here to the rescue!

The Solution

It is a three-step solution:

  1. Generate an attribute list (including full module prefix) for each module using sphinx-autogen and template magic.
  2. Use some GNU Make functions to generate a combined list of all attributes.
  3. Use the combined attribute list within the template to generate the final module documentation.

sphinx-autogen is a command-line tool shipped with Sphinx. It is also used automatically under the hood by the directive .. autosummary:: with the option :toctree:.

I am using Python 3.13 and Sphinx 8.2.3.

Project structure

├─ src/
│  └─ rogue_scroll/
│     ├─ __init__.py
│     └─ _scroll.py
└─ docs/
   ├─ Makefile
   ├─ *build/
   └─ source/
      ├─ conf.py
      ├─ index.rst
      ├─ modules.rst
      ├─ autogen_attributes.rst
      ├─ *_attributes/
      ├─ *_autosummary/
      ├─ _static/
      └─ _templates/
         └─ autosummary/
            ├─ attributes.rst
            └─ module.rst

*Generated during build

The attribute lists will be placed in the directory docs/source/_attributes/. The final documentation will be generated in docs/build/. Open the file docs/build/html/index.html to visualize it.

docs/Makefile

SOURCE_DIR        := source
BUILD_DIR         := build
TEMPLATES_DIR     := $(SOURCE_DIR)/_templates
AUTOSUMMARY_DIR   := $(SOURCE_DIR)/_autosummary
ATTRIBUTES_DIR    := $(SOURCE_DIR)/_attributes
ATTRIBUTES_LIST   := $(ATTRIBUTES_DIR)/list.rst
AUTOGEN_ATTR_FILE := $(SOURCE_DIR)/autogen_attributes.rst

.PHONY: html attributes
html: $(ATTRIBUTES_LIST)
    sphinx-build -M html $(SOURCE_DIR) $(BUILD_DIR) -v -a -E

$(ATTRIBUTES_LIST): attributes
    $(file > $(ATTRIBUTES_LIST),$(subst $(eval ) ,,\
        $(foreach FILE,$(wildcard $(ATTRIBUTES_DIR)/*),\
        $(firstword $(file < $(FILE))))))

attributes: $(AUTOGEN_ATTR_FILE)
    rm -rf $(ATTRIBUTES_LIST)
    export PYTHONPATH=../src && sphinx-autogen -i -t $(TEMPLATES_DIR) $<

When you execute make html to build the documentation, the Makefile will execute the following commands:

rm -rf source/_attributes/list.rst
export PYTHONPATH=../src && sphinx-autogen -i -t source/_templates source/autogen_attributes.rst
sphinx-build -M html source build -v -a -E

docs/source/conf.py

import os
import sys

sys.path.insert(0, os.path.abspath('../../src'))

project = "Project name"
author = "Author name"
version = "1.0.0"
release = version
copyright = f"2025, {author}"

extensions = ['sphinx.ext.autodoc','sphinx.ext.autosummary']
templates_path = ['_templates']
exclude_patterns = ['build', '_attributes', 'autogen_attributes.rst']
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
autosummary_imported_members = True

Note that autogen_attributes.rst and _attributes/ are listed in exclude_patterns, because they are only used to generate the attributes list and must not be included in the final documentation.

Also, I am using the "Read The Docs" theme because it is pretty :). You can install it executing pip install sphinx-rtd-theme.

docs/source/index.rst

Main page title
===============

.. toctree::
   :maxdepth: 3

   modules

Main file of the documentation.

docs/source/modules.rst

Modules
=======

.. autosummary::
   :toctree: _autosummary
   :template: module
   :recursive:

   rogue_scroll

This file generates documentation for each module using the template docs/source/_templates/module.rst. The reST files generated during build will be placed in docs/source/_autosummary.

docs/source/autogen_attributes.rst

.. autosummary::
   :toctree: _attributes
   :template: attributes
   :recursive:

   rogue_scroll
   rogue_scroll._scroll

This is the main file parsed by sphinx-autogen to generate an attribute list for each module using the template docs/source/_templates/attributes.rst. Usually, you would include only the top-level package (rogue_scroll), but Sphinx does not include private modules (any file starting with _) by default, so I had to include rogue_scroll._scroll manually.

docs/source/_templates/attributes.rst

{# PART 1 --------------------------------------------------------------------#}

{% for a in attributes -%}
   {{ '%s.%s,' % (fullname, a) -}}
{% endfor -%}

{# PART 2 --------------------------------------------------------------------#}

{{ ',\n\n.. automodule:: %s\n\n' % fullname -}}

{% if modules -%}
   {{ '   .. autosummary::\n'
      '      :toctree:\n'
      '      :template: attributes\n'
      '      :recursive:\n\n' -}}
   {% for m in modules -%}
      {{ '      %s\n' % m -}}
   {% endfor -%}
{% endif -%}

This template is used to generate an attribute list for each module.

docs/source/_templates/module.rst

{{ fullname | underline }}

{# PART 1 --------------------------------------------------------------------#}

{% set attributes_file -%}
   {% include '../_attributes/list.rst' -%}
{% endset -%}

{% set global_attributes = attributes_file.split(',') -%}

{% for a in global_attributes -%}
   {% if a == '' -%}
      {% set _ = global_attributes.pop(loop.index0) -%}
   {% endif -%}
{% endfor -%}

{# PART 2 --------------------------------------------------------------------#}

{% set imported_attributes = [] -%}

{% for m in members -%}
   {% if m not in modules ~ functions ~ classes ~ exceptions ~ attributes -%}
      {% set outer_loop = loop -%}
      {% for a in global_attributes -%}
         {% if m == a.split('.')[-1] -%}
            {% set _ = imported_attributes.append((a, m)) -%}
         {% endif -%}
      {% endfor -%}
   {% endif -%}
{% endfor -%}

{# PART 3 --------------------------------------------------------------------#}

{{ '.. automodule:: %s' % fullname -}}

{% if attributes or imported_attributes -%}
   {{ '\n\n   .. autosummary::\n\n' -}}
   {% for a in attributes -%}
      {{ '      ~%s\n' % a -}}
   {% endfor -%}
   {% if imported_attributes -%}
      {% for a in imported_attributes -%}
         {{ '      ~%s\n' % (a[0].replace('%s.' % fullname, '', 1)) -}}
      {% endfor -%}
   {% endif -%}
{% endif -%}

{# PART 4 --------------------------------------------------------------------#}

{% if attributes or imported_attributes -%}
   {% for a in attributes if a in members -%}
      {{ '\n.. autodata:: %s\n' % a -}}
   {% endfor -%}
   {% if imported_attributes -%}
      {% for a in imported_attributes -%}
         {{ '\n.. py:data:: %s\n\n' % a[1] -}}
         {{ '   .. autodata:: %s\n' % a[0] -}}
         {{ '      :noindex:\n' -}}
      {% endfor -%}
   {% endif -%}
{% endif -%}

{# PART 5 --------------------------------------------------------------------#}

{{ '\n\nname: %s\n\n' % name -}}
{{ 'objname: %s\n\n' % objname -}}
{{ 'fullname: %s\n\n' % fullname -}}
{{ 'objtype: %s\n\n' % objtype -}}
{{ 'module: %s\n\n' % module -}}
{{ 'class: %s\n\n' % class -}}
{{ 'members: %s\n\n' % members -}}
{{ 'inherited_members: %s\n\n' % inherited_members -}}
{{ 'functions: %s\n\n' % functions -}}
{{ 'classes: %s\n\n' % classes -}}
{{ 'exceptions: %s\n\n' % exceptions -}}
{{ 'methods: %s\n\n' % methods -}}
{{ 'attributes: %s\n\n' % attributes -}}
{{ 'modules: %s\n\n' % modules -}}

{{ 'global_attributes:\n' -}}
{% for a in global_attributes -%}
   {{ '   %s,\n' % a -}}
{% endfor -%}
{{ '\n' -}}

{{ 'imported_attributes:\n' -}}
{% for a in imported_attributes -%}
   {{ '   (%s, %s),\n' % a -}}
{% endfor -%}
{{ '\n' -}}

This template is used to generate the documentation for each module.

src/init.py

"""rogue_scroll docstring"""

from ._scroll import SYLLABLES
from ._scroll import SCROLL_PROBS

src/_scroll.py

"""_scroll docstring"""

SYLLABLES = 123
"""SYLLABLES docstring"""

SCROLL_PROBS = 123
"""SCROLL_PROBS docstring"""

The Result

Result

I created this answer specifically for your case (Merry Christmas!), but I posted a more complete and generic example on my GitHub. It is licensed under the "do whatever the f*** you want" license (MIT).

This solution is not absolute. It is just a result of many days of research and template tinkering. 100% AI-free. If you can improve this solution somehow, please let me know!

For more details about templates, see [1] and [2].

Reasons:
  • Blacklisted phrase (0.5): I cannot
  • Whitelisted phrase (-2): solution:
  • RegEx Blacklisted phrase (2.5): please let me know
  • RegEx Blacklisted phrase (1): I want
  • Contains signature (1):
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (0.5):
Posted by: LucasJ