Shailesh Appukuttan
Shailesh Appukuttan

Reputation: 832

Using Jinja2 with Sphinx autosummary

I am trying to use sphinx.ext.autosummary to document a Python package. Since 'autosummary' requires us to list all the items to be included, I wanted to specify these using Jinja2.

My conf.py is as follows (relevant parts shown):

extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.autosummary',
    'sphinx.ext.doctest',
    'sphinx.ext.todo',
    'sphinx.ext.coverage',
    'sphinx.ext.viewcode',
    'sphinx.ext.napoleon',
    'sphinx_automodapi.automodapi'
]

autodoc_default_options = {
    'imported-members':True
}
add_module_names = False
autosummary_generate = True
numpydoc_show_class_members = False

def rstjinja(app, docname, source):
    """
    Render our pages as a jinja template for fancy templating goodness.
    """
    # Make sure we're outputting HTML
    if app.builder.format != 'html':
        return
    src = source[0]
    rendered = app.builder.templates.render_string(
        src, app.config.html_context
    )
    source[0] = rendered

def setup(app):
    app.connect("source-read", rstjinja)

# in actual usage, `entities` is determined at docs generation time via some code
html_context = {
    'entities' : ["classA", "classB", "classD"]
}

The methods rstjinja() and setup() were borrowed from here. It clearly states that:

The Jinja templates will be rendered before the RST is processed.

My .rst file is as follows:

#####
Title
#####

.. currentmodule:: Package.SubModule

.. autosummary::
    :nosignatures:
    :toctree:

    {% for item in entities %}
        {{ item }}
    {% endfor %}

The output correctly shows me a summary table consisting of 3 entries (one for each of the three classes I had specified: "classA", "classB", "classD"). The first column displays the name of the class, and the second column shows a one-line description (from its docstring). The data in the second column clearly indicates that Sphinx is able to identify the relevant classes and extract its docstrings.

My problem is that 'autosummary' does not generate stubs for these classes, and therefore these entries in the table are not clickable. On the terminal I see the following warning for each of the classes with missing stubs:

WARNING: autosummary: stub file not found 'Package.SubModule.classA'. Check your autosummary_generate setting.

As seen in my conf.py file, this setting is already True.

If I alter (for the sake of exploring) the .rst file to the following:

#####
Title
#####

.. currentmodule:: Package.SubModule

.. autosummary::
    :nosignatures:
    :toctree:

    {% for item in entities %}
        {{ item }}
    {% endfor %}
    classA

Then I get a table similar to the previous case, but with an extra row at the end corresponding to "classA". And interestingly, both the entries for "classA" (first one generated through Jinja, second via explicitly specifying) now hyperlink to the stub created for "classA".

Why is this so? Why are stubs not created when the same info is being specified only via Jinja (even though sphinx does display the docstrings for these in the table)?

How can I resolve this issue? It is important for me to be able to supply the list of entities to be documented via Jinja (as I determine these via some Python code in conf.py).

Additional info: In the above example, the classes can be imported via

from Package.SubModule import classA, classB, classD

Upvotes: 5

Views: 2300

Answers (1)

Shailesh Appukuttan
Shailesh Appukuttan

Reputation: 832

I found a workaround using sphinx_automodapi.automodapi extension.

Relevant bits of my conf.py:

import sphinx_automodapi

extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.doctest',
    'sphinx.ext.todo',
    'sphinx.ext.coverage',
    'sphinx.ext.viewcode',
    'sphinx.ext.napoleon',
    'sphinx_automodapi.automodapi'
]

add_module_names = False
autosummary_generate = True
numpydoc_show_class_members = False

def rstjinja(app, docname, source):
    """
    Render our pages as a jinja template for fancy templating goodness.
    """
    # Make sure we're outputting HTML
    if app.builder.format != 'html':
        return
    src = source[0]
    rendered = app.builder.templates.render_string(
        src, app.config.html_context
    )
    source[0] = rendered

def setup(app):
    app.connect("source-read", rstjinja)


html_context = {
    'entities'       : ["classC", "classE"] # NOTE: specify classes NOT to be included/documented; items specified here will be skipped in doc generation
}

NOTE: the list of classes passed via html_context are the classes that are to be excluded from the documentation. It would have been nice if the extension allowed to directly specify the required classes. I have opened a ticket (here: https://github.com/astropy/sphinx-automodapi/issues/92) for the same (currently unresolved).

In actual usage, the list of classes can be dynamically determined. For example:

import inspect, importlib, sciunit
package_import_name = "package_name"

submodule = "{}.submodule_name".format(package_import_name)
module = importlib.import_module(submodule)
exlcude_classes = [x[0] for x in inspect.getmembers(module,
                    lambda member: inspect.isclass(member)
                                    and not(<<specify condition>>))]

html_context = {
    'entities'       : exlcude_classes
}

Sample of my .rst file:

##########
Submodules
##########

.. automodapi:: package_name.submodule_name
    :nosignatures:
    :no-main-docstr:
    :skip: {{ entities|join(', ') }}

Upvotes: 2

Related Questions