Source code for plasmapy_sphinx.autodoc.automodapi

This module contains the functionality used to define the :rst:dir:`automodapi`
directive and its :ref:`supporting configuration values <automodapi-confvals>`.

.. contents:: Content

`automodapi` Directive

.. rst:directive:: automodapi

    The :rst:dir:`automodapi` directive is a wrapper on Sphinx's
    :rst:dir:`automodule` directive and registered as one of the
    `sphinx.ext.autodoc` directives.  As such, many of the :rst:dir:`automodule`
    options still work, except any of the *member* options and *ignore-module-all*.
    The registration of :rst:dir:`automodapi` with `~sphinx.ext.autodoc` means
    :rst:dir:`automodapi` is used by Sphinx when indexing the documented module.

    :rst:dir:`automodapi` is used to document modules (i.e. packages and ``.py``
    files.  The contents of the `plasmapy_sphinx.utils` page shows an example
    of how the following declaration is rendered.

    .. code-block:: rst


        .. automodapi:: plasmapy_sphinx.utils

    The module level docstring is first rendered and then the categorized groups
    are rendered in :rst:dir:`automodsumm` tables filtered for the associated group
    and placed under the appropriate object type heading.  The order in which the
    :rst:dir:`automodsumm` tables are displayed is controlled by the configuration
    value :confval:`automodapi_group_order`.

    .. rst:directive:option:: groups

        When a module is inspected all the identified objects are categorized into
        groups.  The built-in groups are:

        | **modules**    | Direct sub-packages and modules.                            |
        | **classes**    | Python classes. (excluding **exceptions** and **warnings**) |
        | **exceptions** | Classes that inherit from `BaseException`. (excluding       |
        |                | **warnings**)                                               |
        | **warnings**   | Classes that inherit from `Warning`.                        |
        | **functions**  | Objects that satisfy :func:`inspect.isroutine`.             |
        | **variables**  | All other objects.                                          |

        In addition to the built-in groups, groups defined in
        :confval:`automodapi_custom_groups` will be categorized.  When objects are
        collected and grouped the **modules** will be done first, followed by any
        custom group, and, finally, the built-in groups.  By default, tables will
        be generated for **all** groups.

        The contents of the :ref:`API section <automodapi-api>` below shows
        an example of how the follow declaration is rendered.

        .. code-block:: rst

            .. automodapi:: plasmapy_sphinx.autodoc.automodapi


            .. automodapi:: plasmapy_sphinx.autodoc.automodapi
               :groups: all

        The behavior of ``:no-main-docstring:`` is described below
        in the :rst:dir:`automodapi:no-main-docstring` section.  If you
        only want to display only **classes**, then the following can be done.

        .. code-block:: rst

            .. automodapi:: plasmapy_sphinx.autodoc.automodapi
               :groups: classes

        If you want to include multiple groups, then specify all groups as a
        comma separated list.

        .. code-block:: rst

            .. automodapi:: plasmapy_sphinx.autodoc.automodapi
               :groups: classes, functions

    .. rst:directive:option:: exclude-groups

        This option behaves just like :rst:dir:`automodapi:groups` except
        you are selectively excluding groups for the generated tables.  Using the
        same example as before, a table of just **classes** can be generated by

        .. code-block:: rst

            .. automodapi:: plasmapy_sphinx.autodoc.automodapi
               :exclude-groups: functions

    .. rst:directive:option:: no-groups

        Specify if you want to exclude all :rst:dir:`automodsumm` tables from
        being generated.

        .. code-block:: rst

            .. automodapi:: plasmapy_sphinx.autodoc.automodapi

    .. rst:directive:option:: skip

        This option allows you to skip (exclude) selected objects from the
        generated tables.  The argument is given as a comma separated list of
        the object's short name.  Continuing with the example from above, let's
        skip `~plasmapy_sphinx.autodoc.automodapi.ModAPIDocumenter` and
        `~plasmapy_sphinx.autodoc.automodapi.setup` from the tables.

        .. code-block:: rst

            .. automodapi:: plasmapy_sphinx.autodoc.automodapi
               :skip: ModAPIDocumenter, setup

        .. automodapi:: plasmapy_sphinx.autodoc.automodapi
           :skip: ModAPIDocumenter, setup

    .. rst:directive:option:: noindex

        Like the rest of Sphinx's `~sphinx.ext.autodoc` directives,
        :rst:dir:`automodapi` is used by Sphinx to index the documented module
        and make available cross-referencing.  To prevent multiple
        cross-references to the same documented module, ``:noindex:`` can be used
        to prevent the indexing.

        .. code-block:: rst

            .. automodapi:: plasmapy_sphinx.autodoc.automodapi

    .. rst:directive:option:: include-heading

        This option will create a top level heading for the documented module.

        .. code-block:: rst

            .. automodapi::

        If ``bar`` is a package, then the heading will look like

        .. code-block:: rst


        and if ``bar`` is a ``.py`` file, then the heading will look like

        .. code-block:: rst


    .. rst:directive:option:: heading-chars

        (Default ``-^``) A two character string specifying the underline characters
        used for the heading created by :rst:dir:`automodapi`.  The following code

        .. code-block:: rst

            .. automodapi::
               :heading-chars: ^~

        would generate reStructuredText equivalent to

        .. code-block:: rst


            I am the main docstring

            Sub-Packages & Modules

            .. automodsumm::
               :toctree: api
               :groups: modules

        If :rst:dir:`automodapi:include-heading` is not given, then the first
        character will be used for the :rst:dir:`automodsumm` table headings.

    .. rst:directive:option:: toctree

        If you want the tables generated by :rst:dir:`automodapi` to serve as
        :rst:dir:`toctree`'s, then specify this option with a directory path
        ``DIRNAME`` with respect to the location of your ````.

        .. code-block:: rst

            .. automodapi:: plasmapy_sphinx.autodoc.automodapi
               :toctree: DIRNAME

        If ``:toctree:`` is not set, then ``DIRNAME`` will default to what is set
        by :confval:`automodapi_default_toctree_dir`.  If no :rst:dir:`toctree`
        is desired, then use option :rst:dir:`automodapi:no-toctree`.

    .. rst:directive:option:: no-toctree

        Use this option if you do not want the tables generated by
        :rst:dir:`automodapi` to serve as :rst:dir:`toctree`'s.

        .. code-block:: rst

            .. automodapi:: plasmapy_sphinx.autodoc.automodapi

    .. rst:directive:option:: no-main-docstring

        By default the module level docstring will be rendered and displayed, but
        setting this option will omit the module level docstring and leave just
        the :rst:dir:`autosummary` tables for each collected group of objects.

        .. code-block:: rst

            .. automodapi:: plasmapy_sphinx.autodoc.automodapi

    .. rst:directive:option:: inheritance-diagram

        Set this option to generate inheritance diagrams (using the directive
        :rst:dir:`inheritance-diagram`) for the groups listed in the configuration
        value :confval:`automodapi_groups_with_inheritance_diagrams`.  Default
        behavior is controlled by the configuration value
        :confval:`automodapi_include_inheritance_diagram`.  The option
        :rst:dir:`automodapi:no-inheritance-diagram` will supersede this option.

        .. code-block:: rst

            .. automodapi:: plasmapy_sphinx.autodoc.automodapi

    .. rst:directive:option:: no-inheritance-diagram

        Set this option to not generate inheritance diagrams.  This option will
        take precedence over the :rst:dir:`automodapi:inheritance-diagram` option.

        .. code-block:: rst

            .. automodapi:: plasmapy_sphinx.autodoc.automodapi

.. _automodapi-confvals:

`automodapi` Configuration Values

A configuration value is a variable that can be defined in ```` to configure
the default behavior of related `sphinx` directives.  The configuration values
below relate to the behavior of the :rst:dir:`automodapi` directive.

.. confval:: automodapi_default_toctree_dir

    (Default ``"api"``)  The default directory name, with respect to ````,
    for :rst:dir:`automodapi` to write stub files to.  This is the default
    value for :rst:dir:`automodapi:toctree`.

.. confval:: automodapi_group_order

    (Default ``("modules", "classes", "exceptions", "warnings", "functions",
    "variables")``)  A tuple defining the order :rst:dir:`automodapi` should
    display the collected groups.  If :rst:dir:`automodapi` identifies groups
    that are not identified here (e.g. unlisted custom groups), then those
    groups will be displayed alphabetically after the groups defined in
    :confval:`automodapi_group_order`.  *This configuration value should be
    defined if custom groups are defined by* :confval:`automodapi_custom_groups` *.*

.. confval:: automodapi_groups_with_inheritance_diagrams

    (Default ``["classes", "exceptions", "warnings"]``) A list defining which
    groups should have inheritance diagrams associated with them when displayed
    by :rst:dir:`automodapi`.

.. confval:: automodapi_include_inheritance_diagram

    (Default `True`) Controls if :rst:dir:`automodapi` will by default generated
    inheritance diagrams (see directive :rst:dir:`inheritance-diagram`) for a
    given group.  The groups that should get inheritance diagrams are defined by
    the configuration value :confval:`automodapi_groups_with_inheritance_diagrams`.

__all__ = ["AutomodapiOptions", "ModAPIDocumenter", "setup"]

import inspect
import re
import sys
import warnings

from collections import OrderedDict
from import Callable
from docutils.parsers.rst import directives
from docutils.statemachine import StringList
from sphinx.application import Sphinx

    from sphinx.deprecation import RemovedInSphinx50Warning
except ImportError:

    class RemovedInSphinx50Warning(PendingDeprecationWarning):

from sphinx.ext.autodoc import bool_option, ModuleDocumenter
from sphinx.locale import __
from sphinx.util import logging
from typing import Any, Dict, List, Optional, Union

from ..automodsumm.core import AutomodsummOptions, option_str_list
from ..utils import default_grouping_info

if sys.version_info >= (3, 0):
    text_type = str
    text_type = unicode  # noqa

logger = logging.getLogger(__name__)

_option_spec = {
    "groups": option_str_list,
    "exclude-groups": option_str_list,
    "no-groups": bool_option,
    # "group-order": option_str_list,
    # "merge-groups": bool_option,
    "skip": option_str_list,
    "include-heading": bool_option,
    # "members": lambda x: None,
    "heading-chars": directives.unchanged,
    "toctree": directives.unchanged,
    "no-toctree": bool_option,
    "no-main-docstring": bool_option,
    "inheritance-diagram": bool_option,
    "no-inheritance-diagram": bool_option,
}  # type: Dict[str, Callable]
for option_name in list(_option_spec):
    if "member" in option_name:
        del _option_spec[option_name]
del _option_spec["ignore-module-all"]
del option_name

[docs] class AutomodapiOptions(AutomodsummOptions): """ Class for advanced conditioning and manipulation of option arguments of `plasmapy_sphinx.autodoc.automodapi.ModAPIDocumenter`. """ option_spec = _option_spec logger = logger
[docs] def condition_options(self): super().condition_options() self.condition_heading_chars_option() self.condition_include_heading_option() self.condition_inheritance_diagram_option()
[docs] def condition_toctree_option(self): """ Additional conditioning of the ``:toctree:`` option. (See options :rst:dir:`automodapi:toctree` and :rst:dir:`automodapi:no-toctree` for additional details.) """ if "no-toctree" in self.options and self.options["no-toctree"]: if "toctree" in self.options: del self.options["toctree"] elif "toctree" not in self.options: self.options["toctree"] = super().condition_toctree_option()
[docs] def condition_heading_chars_option(self): """ Additional conditioning of the ``:heading-chars:`` option. (See option :rst:dir:`automodapi:heading-chars` for additional details.) """ non_alphanumerics = re.compile("[^0-9a-zA-Z]]+") heading_chars = self.options.get("heading-chars", None) if ( heading_chars is None or heading_chars == "" or len(heading_chars) < 2 or non_alphanumerics.fullmatch(heading_chars) is None ): self.options["heading-chars"] = "-^"
[docs] def condition_include_heading_option(self): """ Additional conditioning of the ``:include-heading:`` option. (See option :rst:dir:`automodapi:include-heading` for additional details.) """ if "include-heading" not in self.options: self.options["include-heading"] = False
[docs] def condition_inheritance_diagram_option(self): """ Additional conditioning of the ``:inheritance-diagram:`` option. (See options :rst:dir:`automodapi:inheritance-diagram` and :rst:dir:`automodapi:no-inheritance-diagram` for additional details.) """ if "no-inheritance-diagram" in self.options: self.options["inheritance-diagram"] = False del self.options["no-inheritance-diagram"] elif "inheritance-diagram" in self.options: self.options["inheritance-diagram"] = False else: self.options[ "inheritance-diagram" ] =
[docs] def condition_group_options(self): """ Additional conditioning of the grouping options. (See options :rst:dir:`automodapi:groups`, :rst:dir:`automodapi:exclude-groups`, and :rst:dir:`automodapi:no-groups` for additional details.) """ if "no-groups" in self.options and self.options["no-groups"]: self.options["groups"] = [] if "exclude-groups" in self.options: del self.options["exclude-groups"] return super().condition_group_options()
@property def options_for_automodsumm(self) -> Dict[str, Any]: """ A dictionary of options suitable for :rst:dir:`automodsumm` based on the options given to :rst:dir:`automodapi`, and excluding the group options. """ options = {} asumm_opts = list(AutomodsummOptions.option_spec) for name in asumm_opts: if name in self.options and name not in ("groups", "exclude-groups"): val = self.options[name] if isinstance(val, list): val = ", ".join(val) if name == "toctree" and self.toctree["original"] is not None: # automodsumm requires path relative to the confdir but # Automodsumm.review_toctree_option generates the path relative # to the file location options[name] = self.toctree["original"] else: options[name] = val return options
[docs] class ModAPIDocumenter(ModuleDocumenter): """ The class that defines the `~sphinx.ext.autodoc` directive :rst:dir:`automodapi`. """ objtype = "modapi" """Defines the *auto* directive name. In this case ``automodapi``.""" directivetype = "module" """Defines the generated directive name. In this case ``.. :py:module::``.""" titles_allowed = True content_indent = "" logger = logger """ Instance of the `~sphinx.util.logging.SphinxLoggerAdapter` for report during builds. """ option_spec = _option_spec """Mapping of option names to validator functions.""" # Templates used for generated the additional content associated with the # directive (e.g. the automodsumm tables) _templates = { "mod-heading": "\n".join(["{modname} {pkg_or_mod}", "{underline}", ""]), "heading": "\n".join(["{title}", "{underline}"]), "automodsumm": "\n".join([".. automodsumm:: {modname}", " :groups: {group}"]), "options": " :{option}: {opt_args}", "inheritance-diagram": "\n".join( [ ".. inheritance-diagram:: {cls_list}", " :private-bases:", " :parts: 1", ], ), } @property def grouping_info(self) -> Dict[str, Dict[str, Union[str, None]]]: """ Dictionary of :rst:dir:`automodapi` and :rst:dir:`automodsumm` group information. The primary key is the group name, e.g. **modules**, **classes**, etc. The value associated with the primary key is a dictionary with the following keys: +-------------+------------------------------------------------------------+ | title | Title used to head the :rst:dir:`automodsumm` table. | +-------------+------------------------------------------------------------+ | description | Brief description to follow the title. | +-------------+------------------------------------------------------------+ | dunder | Name of the dunder variable used to define a custom group. | +-------------+------------------------------------------------------------+ """ group_order = tuple( custom_group_info = group_names = set(default_grouping_info) | set(custom_group_info) remainder = list(group_names - set(group_order)) if len(remainder) > 0: group_order += tuple(sorted(remainder)) _grouping_info = OrderedDict() for name in group_order: if name in default_grouping_info: _info = default_grouping_info[name] else: _info = custom_group_info[name] _grouping_info.update( { name: { "title": _info.get("title", None), "description": _info.get("description", None), "dunder": _info.get("dunder", None), }, }, ) return _grouping_info
[docs] def generate_more_content(self, modname: str) -> List[str]: """ Generate the "more content" associate with the :rst:dir:`automodsumm` tables and inheritance diagrams. Parameters ---------- modname : str Name of the module being documented (i.e. that given to :rst:dir:`automodapi`). Returns ------- List[str] A list of strings to be added the to the directive's content. """ app = inheritance_groups = app.config.automodapi_groups_with_inheritance_diagrams lines = [] option_processor = AutomodapiOptions( app, modname, self.options, _warn=self.logger.warning, docname=self.env.docname, ) asumm_options = option_processor.options_for_automodsumm mod_objs = option_processor.mod_objs_option_filtered heading_char = ( option_processor.options["heading-chars"][1] if option_processor.options["include-heading"] else option_processor.options["heading-chars"][0] ) include_inheritance_diagram = option_processor.options["inheritance-diagram"] # scan thru default groups first for group, info in self.grouping_info.items(): if group not in mod_objs: continue title = info["title"] description = info["description"] # type: str underline = heading_char * len(title) # sub-heading lines.extend( self._templates["heading"] .format(title=title, underline=underline) .splitlines() ) lines.append("") # add group description if description is not None: description = inspect.cleandoc(description) descr_list = description.split("\n") lines.extend(descr_list) lines.append("") # add automodsumm directive lines.extend( self._templates["automodsumm"] .format(modname=modname, group=group) .splitlines() ) # add options for automodsumm directive for name, val in asumm_options.items(): if name == "toctree" and group == "modules": continue lines.extend( self._templates["options"] .format(option=name, opt_args=val) .splitlines() ) lines.append("") # add inheritance-diagram if group in inheritance_groups and include_inheritance_diagram: cls_list = " ".join(mod_objs[group]["qualnames"]) lines.extend( self._templates["inheritance-diagram"] .format(cls_list=cls_list) .splitlines() ) lines.append("") return lines
[docs] def generate_heading(self, modname: str) -> None: """ Generate and place a heading at the top of the directive's content. If ``modname`` is a package then the title will be ``<modname> Package``; and if a module (``.py`` file) then the title will be ``<modname> Module``. Parameters ---------- modname : str Name of the module being documented (i.e. that given to :rst:dir:`automodapi`). """ app = sourcename = self.get_sourcename() option_processor = AutomodapiOptions( app, modname, self.options, _warn=self.logger.warning, docname=self.env.docname, ) if not option_processor.options["include-heading"]: return modname = re.escape(modname) if option_processor.pkg_or_module == "pkg": pkg_or_mod = "Package" else: pkg_or_mod = "Module" heading_char = option_processor.options["heading-chars"][0] underline = heading_char * (len(modname) + 1 + len(pkg_or_mod)) heading_lines = ( self._templates["mod-heading"] .format(modname=modname, pkg_or_mod=pkg_or_mod, underline=underline) .splitlines() ) for line in heading_lines: self.add_line(line, source=sourcename)
[docs] def add_content( self, more_content: Optional[StringList], no_docstring: bool = False ) -> None: """Add content from docstrings, attribute documentation and user.""" if no_docstring: warnings.warn( "The 'no_docstring' argument to %s.add_content() is deprecated." % self.__class__.__name__, RemovedInSphinx50Warning, stacklevel=2, ) no_docstring = self.options.get("no-main-docstring", False) # set sourcename and add content from attribute documentation sourcename = self.get_sourcename() if self.analyzer: attr_docs = self.analyzer.find_attr_docs() if self.objpath: key = (".".join(self.objpath[:-1]), self.objpath[-1]) if key in attr_docs: no_docstring = True # make a copy of docstring for attributes to avoid cache # the change of autodoc-process-docstring event. docstrings = [list(attr_docs[key])] for i, line in enumerate(self.process_doc(docstrings)): self.add_line(line, sourcename, i) # add content from docstrings if not no_docstring: docstrings = self.get_doc() if docstrings is None: # Do not call autodoc-process-docstring on get_doc() returns None. pass else: if not docstrings: # append at least a dummy docstring, so that the event # autodoc-process-docstring is fired and can add some # content if desired docstrings.append([]) for i, line in enumerate(self.process_doc(docstrings)): self.add_line(line, sourcename, i) # add additional content (e.g. from document), if present if more_content: for line, src in zip(, more_content.items): self.add_line(line, src[0], src[1])
[docs] def generate( self, more_content: Optional[StringList] = None, real_modname: str = None, check_module: bool = False, all_members: bool = False, ) -> None: """ Generate reST for the object given by **, and possibly for its members. If *more_content* is given, include that content. If *real_modname* is given, use that module name to find attribute docs. If *check_module* is True, only generate if the object is defined in the module name it is imported from. If *all_members* is True, document all members. """ docname = self.env.docname if not docname.endswith(".rst"): docname += ".rst" if not self.parse_name(): # need a module to import logger.warning( __( f"don't know which module to import for autodocumenting " f"{} (try placing a 'module' or 'currentmodule' " f"directive in the document, or giving an explicit module name)" ), type="autodoc", ) return # now, import the module and get object to document if not self.import_object(): return # If there is no real module defined, figure out which to use. # The real module is used in the module analyzer to look up the module # where the attribute documentation would actually be found in. # This is used for situations where you have a module that collects the # functions and classes of internal submodules. real_modname = real_modname or self.get_real_modname() # type: str # Generate heading self.generate_heading(modname=real_modname) # Generate the 'more_content' (automodsumm and inheritance diagrams) more_content = StringList() more_lines = self.generate_more_content(modname=real_modname) for line in more_lines: more_content.append(line, source=docname) # generate super().generate( more_content=more_content, real_modname=real_modname, check_module=check_module, all_members=all_members, )
[docs] def setup(app: Sphinx): """Sphinx ``setup()`` function for the :rst:dir:`automodapi` functionality.""" from ..automodsumm.core import setup as setup_automodsumm rtn = setup_automodsumm(app) app.setup_extension("sphinx.ext.inheritance_diagram") app.add_autodocumenter(ModAPIDocumenter) app.add_config_value("automodapi_include_inheritance_diagram", True, True) app.add_config_value("automodapi_default_toctree_dir", "api", True) app.add_config_value( "automodapi_group_order", ("modules", "classes", "exceptions", "warnings", "functions", "variables"), True, ) app.add_config_value( "automodapi_groups_with_inheritance_diagrams", ["classes", "exceptions", "warnings"], True, ) return rtn