Can edit the Jinja environment

Closes #200
This commit is contained in:
Ashley Whetter 2020-11-13 22:47:10 -08:00
parent 06a1969d11
commit a8d07b610f
10 changed files with 250 additions and 165 deletions

View File

@ -21,6 +21,10 @@ Features
Basic incremental build support is enabled ``autoapi_keep_files`` is enabled. Basic incremental build support is enabled ``autoapi_keep_files`` is enabled.
Providing none of the source files have changed, Providing none of the source files have changed,
AutoAPI will skip parsing the source code and regenerating the API documentation. AutoAPI will skip parsing the source code and regenerating the API documentation.
* `#200 <https://github.com/readthedocs/sphinx-autoapi/issues/200>`:
Can pass a callback that edits the Jinja Environment object before
template rendering begins.
This allows custom filters, tests, and globals to be added to the environment.
Bug Fixes Bug Fixes
^^^^^^^^^ ^^^^^^^^^

View File

@ -302,6 +302,7 @@ def setup(app):
app.add_config_value("autoapi_python_use_implicit_namespaces", False, "html") app.add_config_value("autoapi_python_use_implicit_namespaces", False, "html")
app.add_config_value("autoapi_python_class_content", "class", "html") app.add_config_value("autoapi_python_class_content", "class", "html")
app.add_config_value("autoapi_generate_api_docs", True, "html") app.add_config_value("autoapi_generate_api_docs", True, "html")
app.add_config_value("autoapi_prepare_jinja_env", None, "html")
app.add_autodocumenter(documenters.AutoapiFunctionDocumenter) app.add_autodocumenter(documenters.AutoapiFunctionDocumenter)
app.add_autodocumenter(documenters.AutoapiPropertyDocumenter) app.add_autodocumenter(documenters.AutoapiPropertyDocumenter)
app.add_autodocumenter(documenters.AutoapiDecoratorDocumenter) app.add_autodocumenter(documenters.AutoapiDecoratorDocumenter)

View File

@ -193,6 +193,8 @@ class SphinxMapperBase(object):
return "\n".join(prepare_docstring(value)) return "\n".join(prepare_docstring(value))
self.jinja_env.filters["prepare_docstring"] = _wrapped_prepare self.jinja_env.filters["prepare_docstring"] = _wrapped_prepare
if self.app.config.autoapi_prepare_jinja_env:
self.app.config.autoapi_prepare_jinja_env(self.jinja_env)
self.url_root = url_root self.url_root = url_root

View File

@ -107,7 +107,7 @@ class PythonPythonMapper(PythonMapperBase):
"""Whether this object should be displayed in documentation. """Whether this object should be displayed in documentation.
This attribute depends on the configuration options given in This attribute depends on the configuration options given in
:confval:`autoapi_options`. :confval:`autoapi_options` and the result of :event:`autoapi-skip-member`.
:type: bool :type: bool
""" """
@ -161,6 +161,8 @@ class PythonPythonMapper(PythonMapperBase):
class PythonFunction(PythonPythonMapper): class PythonFunction(PythonPythonMapper):
"""The representation of a function."""
type = "function" type = "function"
is_callable = True is_callable = True
member_order = 40 member_order = 40
@ -191,6 +193,8 @@ class PythonFunction(PythonPythonMapper):
class PythonMethod(PythonFunction): class PythonMethod(PythonFunction):
"""The representation of a method."""
type = "method" type = "method"
is_callable = True is_callable = True
member_order = 50 member_order = 50
@ -255,6 +259,8 @@ class PythonAttribute(PythonData):
class TopLevelPythonPythonMapper(PythonPythonMapper): class TopLevelPythonPythonMapper(PythonPythonMapper):
"""A common base class for modules and packages."""
_RENDER_LOG_LEVEL = "VERBOSE" _RENDER_LOG_LEVEL = "VERBOSE"
def __init__(self, obj, **kwargs): def __init__(self, obj, **kwargs):
@ -297,14 +303,20 @@ class TopLevelPythonPythonMapper(PythonPythonMapper):
class PythonModule(TopLevelPythonPythonMapper): class PythonModule(TopLevelPythonPythonMapper):
"""The representation of a module."""
type = "module" type = "module"
class PythonPackage(TopLevelPythonPythonMapper): class PythonPackage(TopLevelPythonPythonMapper):
"""The representation of a package."""
type = "package" type = "package"
class PythonClass(PythonPythonMapper): class PythonClass(PythonPythonMapper):
"""The representation of a class."""
type = "class" type = "class"
member_order = 30 member_order = 30
@ -390,5 +402,7 @@ class PythonClass(PythonPythonMapper):
class PythonException(PythonClass): class PythonException(PythonClass):
"""The representation of an exception class."""
type = "exception" type = "exception"
member_order = 20 member_order = 20

View File

@ -26,7 +26,7 @@
{% set visible_classes = obj.classes|rejectattr("inherited")|selectattr("display")|list %} {% set visible_classes = obj.classes|rejectattr("inherited")|selectattr("display")|list %}
{% endif %} {% endif %}
{% for klass in visible_classes %} {% for klass in visible_classes %}
{{ klass.rendered|indent(3) }} {{ klass.render()|indent(3) }}
{% endfor %} {% endfor %}
{% if "inherited-members" in autoapi_options %} {% if "inherited-members" in autoapi_options %}
{% set visible_attributes = obj.attributes|selectattr("display")|list %} {% set visible_attributes = obj.attributes|selectattr("display")|list %}
@ -34,7 +34,7 @@
{% set visible_attributes = obj.attributes|rejectattr("inherited")|selectattr("display")|list %} {% set visible_attributes = obj.attributes|rejectattr("inherited")|selectattr("display")|list %}
{% endif %} {% endif %}
{% for attribute in visible_attributes %} {% for attribute in visible_attributes %}
{{ attribute.rendered|indent(3) }} {{ attribute.render()|indent(3) }}
{% endfor %} {% endfor %}
{% if "inherited-members" in autoapi_options %} {% if "inherited-members" in autoapi_options %}
{% set visible_methods = obj.methods|selectattr("display")|list %} {% set visible_methods = obj.methods|selectattr("display")|list %}
@ -42,6 +42,6 @@
{% set visible_methods = obj.methods|rejectattr("inherited")|selectattr("display")|list %} {% set visible_methods = obj.methods|rejectattr("inherited")|selectattr("display")|list %}
{% endif %} {% endif %}
{% for method in visible_methods %} {% for method in visible_methods %}
{{ method.rendered|indent(3) }} {{ method.render()|indent(3) }}
{% endfor %} {% endfor %}
{% endif %} {% endif %}

View File

@ -92,7 +92,7 @@ Functions
{% endblock %} {% endblock %}
{% endif %} {% endif %}
{% for obj_item in visible_children %} {% for obj_item in visible_children %}
{{ obj_item.rendered|indent(0) }} {{ obj_item.render()|indent(0) }}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -27,6 +27,7 @@ autoapi_dirs = ['../autoapi']
autoapi_generate_api_docs = False autoapi_generate_api_docs = False
intersphinx_mapping = { intersphinx_mapping = {
'jinja': ('https://jinja.palletsprojects.com/en/master/', None),
'sphinx': ('https://www.sphinx-doc.org/en/master/', None), 'sphinx': ('https://www.sphinx-doc.org/en/master/', None),
'python': ('https://docs.python.org/3/', None), 'python': ('https://docs.python.org/3/', None),
} }

View File

@ -170,18 +170,28 @@ Customisation Options
Default: ``bysource`` Default: ``bysource``
The order to document members. The order to document members. This option can have the following values:
* ``alphabetical``: Order members by their name, case sensitively. * ``alphabetical``: Order members by their name, case sensitively.
* ``bysource``: Order members by the order that they were defined in the source code. * ``bysource``: Order members by the order that they were defined in the source code.
* ``groupwise``: Order members by their type then alphabetically, in the order:
* ``groupwise``: Order members by their type then alphabetically,
ordering the types as follows:
* Submodules and subpackages * Submodules and subpackages
* Attributes * Attributes
* Exceptions * Exceptions
* Classes * Classes
* Functions * Functions
* Methods * Methods
.. confval:: autoapi_python_use_implicit_namespaces .. confval:: autoapi_python_use_implicit_namespaces
Default: ``False`` Default: ``False``
@ -198,6 +208,19 @@ Customisation Options
this should be done manually in the ``conf.py``. this should be done manually in the ``conf.py``.
.. confval:: autoapi_prepare_jinja_env
Default: ``None``
A callback that is called shortly after the Jinja environment is created.
It passed the Jinja environment for editing before template rendering begins.
The callback should have the following signature:
.. py:function:: prepare_jinja_env(jinja_env: jinja2.Environment) -> None
:noindex:
Events Events
~~~~~~ ~~~~~~

View File

@ -22,6 +22,22 @@ We provide :samp:`base/base.rst` as an incredibly basic output of every object::
.. {language}:{type}:: {name} .. {language}:{type}:: {name}
Custom Filters, Tests, and Globals
----------------------------------
The :confval:`autoapi_prepare_jinja_env` configuration option allows you
to pass a callback that can edit the :class:`jinja2.Environment` object
before rendering begins.
This callback, among other things, can be used to add custom filters,
tests, and/or globals to the Jinja environment. For example:
.. code-block:: python
def autoapi_prepare_jinja_env(jinja_env):
jinja_env.filters["my_custom_filter"] = lambda value: value.upper()
Context Context
------- -------
@ -30,10 +46,13 @@ This contains:
* ``autoapi_options``: The value of the :confval:`autoapi_options` * ``autoapi_options``: The value of the :confval:`autoapi_options`
configuration option. configuration option.
* ``include_summaries``: The value of the :confval:`autoapi_include_summaries`
configuration option.
* ``obj``: A Python object derived from :class:`PythonMapperBase`. * ``obj``: A Python object derived from :class:`PythonMapperBase`.
* ``sphinx_version``: The contents of :attr:`sphinx.version_info`. * ``sphinx_version``: The contents of :attr:`sphinx.version_info`.
This object has a number of standard attributes you can reliably access per language. The object in ``obj`` has a number of standard attributes
that you can reliably access per language.
.. warning:: .. warning::

View File

@ -753,3 +753,24 @@ class TestImplicitNamespacePackage(object):
example_file = example_handle.read() example_file = example_handle.read()
assert "namespace.example.second_sub_method" in example_file assert "namespace.example.second_sub_method" in example_file
def test_custom_jinja_filters(builder):
confoverrides = {
"autoapi_prepare_jinja_env": (
lambda jinja_env: jinja_env.filters.update(
{
"prepare_docstring": (
lambda docstring: "This is using custom filters."
)
}
)
),
}
builder("pyexample", confoverrides=confoverrides)
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
assert "This is using custom filters." in example_file