Source code for mmf.sphinx.ext.apigen

r"""
======
apigen
======

This extension automatically generates API documentation.  To use it:

1) Include `'mmf.sphinx.ext.apigen'` in the `extensions` list in your
   :file:`conf.py` file.

   .. note:: This extension must be installed *before* the
      :ext:`sphinx.ext.autosummary` extension.

2) Set the configuration option :confval:`apigen_packages` to a list
   of packages for which you would like to generate.  These must be
   importable.
3) Specify :confval:`apigen_outdir` as the output directory.  The
   default is in the `doctreedir/_generated/api` but it is probably
   better if you set this explicitly.
4) Include the :file:`index_*` files in a :rst:dir:`toctree`
   directive::
      .. toctree::
         :maxdepth: 2
         :glob:
   
         _build/_generated/api/index_*

   This will generate the full API.
5) Document your modules.  The following module attributes affect what is added
   to the generated documentation:

   `__all__`
      This list is a standard python feature.  It defines which symbols
      are imported with `from mod import *` for example.  If defined, then only
      attributes included here will be documented.  If this is not defined, then
      the list of objects to document will be extracted from the module
      `__dict__`.

   `__apigen__`
      Dictionary with the following keys:
      `'include'`
         List of objects to include in documentation.  These can be
         strings or a compiled :class:`re.RegexObject` object.
      `'exclude'`
         List of objects to exclude from documentation.  These can be
         strings or a compiled :class:`re.RegexObject` object.
      `'nowarn'
         This boolean flag can be set to `True` to suppress warnings
         about the module not defining `__all__`.  Typically, if `__all__` is
         not defined, then the user will be warned that all imported objects
         will also be documented.  This is usually not desired because, for
         example, imported modules might be very large and should be documented
         on their own.  However, it can be tedious to maintain `__all__`, so if
         a module as changing content, then it might be desirable to deduced the
         contents from `__dict__`.

         .. note:: In this case, you should probably be careful to delete or
            exclude any imports you do not want documented.

The following config values control the generation of the API
documentation.

.. confval:: apigen_packages
   
   This should be a list of importable package names.  The API
   documentation will be generated for these.

.. confval:: apigen_outdir
   
   The documentation will be placed in this directory (default is the
   `$(doctreedir)/_generated/api` directory.)

.. confval:: apigen_package_skip_patterns
   
   None or sequence of {strings, regexps}. Sequence of strings giving
   URIs of packages to be excluded.  Operates on the package path,
   starting at (including) the first dot in the package path, after
   *package_name* - so, if *package_name* is ``sphinx``, then
   ``sphinx.util`` will result in ``.util`` being passed for searching by these
   regexps.  If is None, gives default. Default is: ``['\.tests$']``

.. confval:: apigen_module_skip_patterns

   None or sequence. Sequence of strings giving URIs of modules to be
   excluded Operates on the module name including preceding URI path,
   back to the first dot after *package_name*.  For example
   ``sphinx.util.console`` results in the string to search of
   ``.util.console`` If is None, gives default. Default is:
   ``['\.setup$', '\._']``

.. confval:: apigen_label

   If `True`, then section labels "Modules", "Classes", "Functions"
   etc. will be generated, otherwise, the type can be inferred from
   the name: Modules will have a "."  prepended, interfaces start with
   "I", classes have nothing, and functions end in "()".

.. confval:: apigen_autosummary

   If `True`, then an :ext:`sphinx.ext.autosummary` table will be
   generated at the start of each module summarizing the contents.

.. confval:: apigen_headings

   If `True`, then each function, class, interface, and module will
   appear as a heading so that the structure appears in the table of
   contents.  This is somewhat redundant though.

.. confval:: apigen_inherited_members

   If `True`, then inherited members of each class will be included, otherwise,
   the user must follow the links to the base class.  Default is `False`.

.. todo:: We presently exclude extension modules.

   To include extension modules, first identify them as valid in the
   ``_uri2path`` method, then handle them in the ``_parse_module``
   script.

We get functions and classes by parsing the text of `.py` files.
Alternatively we could import the modules for discovery, and we'd have
to do that for extension modules.  This would involve changing the
``_parse_module`` method to work via import and introspection, and
might involve changing ``discover_modules`` (which determines which
files are modules, and therefore which module URIs will be passed to
``_parse_module``).

.. note:: This is a modified version of a script originally shipped
   with NIPY which in tern was a modified version of a script
   originally shipped with the PyMVPA project.  PyMVPA is an
   MIT-licensed project.

.. warning:: We monkey-patch autodoc.py a bit to get things working.  See
   :class:`MonkeypatchAutodoc`.

"""
import sys
import os
import re
import inspect
import logging
import traceback

from sphinx.errors import ExtensionError

try:
    import zope.interface
    def isinterface(obj):
        return isinstance(obj,
                          zope.interface.interface.InterfaceClass)
except ImportError:
[docs] def isinterface(obj): return False
try: _d = OrderedDict() except: from mmf.contrib.odict import odict as OrderedDict
[docs]def isclass(obj): return inspect.isclass(obj) and not isinterface(obj) # Functions and classes
class _ApiDocWriter(object): ''' Class for automatic detection and parsing of API docs to Sphinx-parsable reST format. This is the original version, and it does not import the modules. Instead, it parses the files, looking for the relevant material.''' # only separating first two levels rst_section_levels = ['*', '=', '-', '~', '^'] def __init__(self, package_name, rst_extension='.rst', package_skip_patterns=None, module_skip_patterns=None, labels=False, autosummary=True, inherited_members=False, headings=False, ): ''' Initialize package for parsing Parameters ---------- package_name : string Name of the top-level package. *package_name* must be the name of an importable package rst_extension : string, optional Extension for reST files, default '.rst' package_skip_patterns : None or sequence of {strings, regexps} Sequence of strings giving URIs of packages to be excluded Operates on the package path, starting at (including) the first dot in the package path, after *package_name* - so, if *package_name* is ``sphinx``, then ``sphinx.util`` will result in ``.util`` being passed for searching by these regexps. If is None, gives default. Default is: ['\.tests$'] module_skip_patterns : None or sequence Sequence of strings giving URIs of modules to be excluded Operates on the module name including preceding URI path, back to the first dot after *package_name*. For example ``sphinx.util.console`` results in the string to search of ``.util.console`` If is None, gives default. Default is: ['\.setup$', '\._'] labels : bool If `True`, then section labels "Modules", "Classes", "Functions" etc. will be generated, otherwise, the type can be inferred from the name: Modules will have a "." prepended, interfaces start with "I", classes have nothing, and functions end in "()". autosummary : bool If `True`, then an :ext:`sphinx.ext.autosummary` table will be generated at the start of each module summarizing the contents. inherited_members : bool If `True`, then inherited members of each class will be included, otherwise, the user must follow the links to the base class. Default is `False`. headings : bool If `True`, then each function, class, interface, and module will appear as a heading so that the structure appears in the table of contents. This is somewhat redundant though. ''' if package_skip_patterns is None: package_skip_patterns = ['\\.tests$'] if module_skip_patterns is None: module_skip_patterns = ['\\.setup$', '\\._'] self.package_name = package_name self.rst_extension = rst_extension self.package_skip_patterns = package_skip_patterns self.module_skip_patterns = module_skip_patterns self.labels = labels self.autosummary = autosummary self.inherited_members = inherited_members self.headings = headings def get_package_name(self): return self._package_name def set_package_name(self, package_name): r""" Set package_name >>> docwriter = ApiDocWriter('sphinx') >>> import sphinx >>> docwriter.root_path == sphinx.__path__[0] True >>> docwriter.package_name = 'docutils' >>> import docutils >>> docwriter.root_path == docutils.__path__[0] True """ # It's also possible to imagine caching the module parsing here self._package_name = package_name self.root_module = __import__(package_name) if hasattr(self.root_module, '__path__'): self.root_path = self.root_module.__path__[0] else: self.root_path = self.root_module.__file__ self.written_modules = None package_name = property(get_package_name, set_package_name, None, 'get/set package_name') def _get_object_name(self, line): ''' Get second token in line >>> docwriter = ApiDocWriter('sphinx') >>> docwriter._get_object_name(" def func(): ") 'func' >>> docwriter._get_object_name(" class Klass(object): ") 'Klass' >>> docwriter._get_object_name(" class Klass: ") 'Klass' ''' name = line.split()[1].split('(')[0].strip() # in case we have classes which are not derived from object # ie. old style classes return name.rstrip(':') def _uri2path(self, uri): ''' Convert uri to absolute filepath Parameters ---------- uri : string URI of python module to return path for Returns ------- path : None or string Returns None if there is no valid path for this URI Otherwise returns absolute file system path for URI Examples -------- >>> docwriter = ApiDocWriter('sphinx') >>> import sphinx >>> modpath = sphinx.__path__[0] >>> res = docwriter._uri2path('sphinx.builder') >>> res == os.path.join(modpath, 'builder.py') True >>> res = docwriter._uri2path('sphinx') >>> res == os.path.join(modpath, '__init__.py') True >>> docwriter._uri2path('sphinx.does_not_exist') ''' if uri == self.package_name: return os.path.join(self.root_path, '__init__.py') path = uri.replace('.', os.path.sep) path = path.replace(self.package_name + os.path.sep, '') path = os.path.join(self.root_path, path) # XXX maybe check for extensions as well? if os.path.exists(path + '.py'): # file path += '.py' elif os.path.exists(os.path.join(path, '__init__.py')): path = os.path.join(path, '__init__.py') else: return None return path def _path2uri(self, dirpath): ''' Convert directory path to uri ''' relpath = dirpath.replace(self.root_path, self.package_name) if relpath.startswith(os.path.sep): relpath = relpath[1:] return relpath.replace(os.path.sep, '.') def _parse_module(self, uri): ''' Parse module defined in *uri* ''' filename = self._uri2path(uri) if filename is None: # nothing that we could handle here. return ([],[],OrderedDict(),OrderedDict(),[]) f = open(filename, 'rt') modules, functions, classes, interfaces, others = self._parse_lines(f) f.close() return modules, functions, classes, interfaces, others, {} def _parse_lines(self, linesource): r""" Parse lines of text for modules, functions, classes, and interfaces.""" modules = [] functions = [] private_functions = [] classes = [] private_classes = [] interfaces = [] for line in linesource: if line.startswith('def ') and line.count('('): # exclude private stuff name = self._get_object_name(line) if name.startswith('_'): private_functions.append(name) else: functions.append(name) elif line.startswith('class '): # exclude private stuff name = self._get_object_name(line) if name.startswith('_'): private_classes.append(name) elif name.startswith('I') and name[1].isupper(): interfaces.append(name) else: classes.append(name) else: pass private_functions.sort() functions.sort() functions.extend(private_functions) private_classes.sort() classes.sort() classes.extend(private_classes) interfaces.sort() classes = OrderedDict([(_k, None) for _k in classes]) interfaces = OrderedDict([(_k, None) for _k in interfaces]) return modules, functions, classes, interfaces def generate_api_doc(self, uri): '''Make autodoc documentation template string for a module Parameters ---------- uri : string python location of module - e.g 'sphinx.builder' Returns ------- S : string Contents of API doc ''' logger = logging.getLogger("mmf.sphinx.ext.apigen") # get the names of all classes and functions (modules, functions, classes, interfaces, others, nowarn) = self._parse_module(uri) if not nowarn and not (len(modules) + len(functions) + len(classes) + len(interfaces)): logger.warning("Empty api - '%s'" % uri) # Make a shorter version of the uri that omits the package name for # titles uri_short = re.sub(r'^%s\.' % self.package_name,'',uri) ad = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n' if False: # Old code chap_title = uri_short ad += (chap_title+'\n'+ self.rst_section_levels[1] * len(chap_title) + '\n\n') # Set the chapter title to read 'module' for all modules # except for the main packages if '.' in uri: title = 'Module: :mod:`' + uri + '`' else: title = ':mod:`' + uri + '`' ad += title + '\n' + self.rst_section_levels[2] * len(title) else: title = ':mod:`' + uri + '`' ad += title + '\n' + self.rst_section_levels[1] * len(title) ad += '\n\n.. currentmodule:: ' + uri + '\n\n' if self.autosummary and len(modules + functions + classes.keys() + interfaces.keys()): ad += "\n.. autosummary::\n" ad += "\n" ad += "\n".join( ["\n".join(" " + _f for _f in modules)] + ["\n".join(" " + _f for _f in interfaces)] + ["\n".join(" " + _f for _f in classes)] + ["\n".join(" " + _f for _f in functions)]) ad += "\n" has_inheritance = False for _c in classes.values() + interfaces.values(): for _b in _c.__bases__: if _b is object: continue else: has_inheritance = True break if has_inheritance: ad += '\nInheritance diagram for ``%s``:\n\n' % uri ad += '.. inheritance-diagram:: %s \n' % uri ad += ' :parts: 3\n' ad += '\n.. automodule:: ' + uri + '\n' if self.labels and modules: ad += '\n' + 'Modules' + '\n' + \ self.rst_section_levels[2] * 7 + '\n' for m in modules: if self.labels: m = "~" + ".".join([uri, m]) else: m = ".%s<%s>" % (m, ".".join([uri, m])) if self.headings: ad += '\n:mod:`' + m + '`\n' \ + self.rst_section_levels[self.labels + 2] * \ (len(m) + 7) + '\n\n' if self.labels and interfaces: ad += '\n' + 'Interfaces' + '\n' + \ self.rst_section_levels[2] * 10 + '\n' for i in interfaces: if self.headings: ad += '\n:class:`' + i + '`\n' \ + self.rst_section_levels[self.labels + 2] * \ (len(i) + 9) + '\n\n' ad += '\n.. autointerface:: ' + i + '\n' # must NOT exclude from index to keep cross-refs working ad += ' :members:\n' \ ' :undoc-members:\n' \ ' :show-inheritance:\n' if self.inherited_members: ad += ' :inherited-members:\n' if self.labels and classes: ad += '\n' + 'Classes' + '\n' + \ self.rst_section_levels[2] * 7 + '\n' for c in classes: if self.headings: ad += '\n:class:`' + c + '`\n' \ + self.rst_section_levels[self.labels + 2] * \ (len(c) + 9) + '\n\n' ad += '\n.. autoclass:: ' + c + '\n' # must NOT exclude from index to keep cross-refs working ad += ' :members:\n' \ ' :undoc-members:\n' \ ' :show-inheritance:\n' if self.inherited_members: ad += ' :inherited-members:\n' if isinstance(classes[c], type): # New style class ad += '\n' \ ' .. automethod:: __init__\n' if self.labels and functions: ad += '\n' + 'Functions' + '\n' + \ self.rst_section_levels[2] * 9 + '\n\n' for f in functions: if self.headings: ad += '\n:func:`' + f + '`\n' \ + self.rst_section_levels[self.labels + 2] * \ (len(f) + 8) + '\n\n' # must NOT exclude from index to keep cross-refs working ad += '\n.. autofunction:: ' + uri + '.' + f + '\n\n' return ad def _survives_exclude(self, matchstr, match_type): ''' Returns True if *matchstr* does not match patterns ``self.package_name`` removed from front of string if present Examples -------- >>> dw = ApiDocWriter('sphinx') >>> dw._survives_exclude('sphinx.okpkg', 'package') True >>> dw.package_skip_patterns.append('^\\.badpkg$') >>> dw._survives_exclude('sphinx.badpkg', 'package') False >>> dw._survives_exclude('sphinx.badpkg', 'module') True >>> dw._survives_exclude('sphinx.badmod', 'module') True >>> dw.module_skip_patterns.append('^\\.badmod$') >>> dw._survives_exclude('sphinx.badmod', 'module') False ''' if match_type == 'module': patterns = self.module_skip_patterns elif match_type == 'package': patterns = self.package_skip_patterns else: raise ValueError('Cannot interpret match type "%s"' % match_type) # Match to URI without package name L = len(self.package_name) if matchstr[:L] == self.package_name: matchstr = matchstr[L:] for pat in patterns: try: pat.search except AttributeError: pat = re.compile(pat) if pat.search(matchstr): return False return True def discover_modules(self): ''' Return module sequence discovered from ``self.package_name`` Parameters ---------- None Returns ------- mods : sequence Sequence of module names within ``self.package_name`` Examples -------- >>> dw = ApiDocWriter('sphinx') >>> mods = dw.discover_modules() >>> 'sphinx.util' in mods True >>> dw.package_skip_patterns.append('\.util$') >>> 'sphinx.util' in dw.discover_modules() False >>> ''' modules = [self.package_name] # raw directory parsing for dirpath, dirnames, filenames in os.walk(self.root_path): # Check directory names for packages root_uri = self._path2uri(os.path.join(self.root_path, dirpath)) for dirname in dirnames[:]: # copy list - we modify inplace package_uri = '.'.join((root_uri, dirname)) if (self._uri2path(package_uri) and self._survives_exclude(package_uri, 'package')): modules.append(package_uri) else: dirnames.remove(dirname) # Check filenames for modules for filename in filenames: if not filename.endswith(".py"): continue module_name = filename[:-3] module_uri = '.'.join((root_uri, module_name)) if (self._uri2path(module_uri) and self._survives_exclude(module_uri, 'module')): modules.append(module_uri) return sorted(modules) def write_modules_api(self, modules, outdir): # write the list written_modules = [] for m in modules: api_str = self.generate_api_doc(m) if not api_str: continue outfile = os.path.join(outdir, m + self.rst_extension) self.write_if_changed(outfile, api_str) written_modules.append(m) self.written_modules = written_modules def write_api_docs(self, outdir): """Generate API reST files. Parameters ---------- outdir : string Directory name in which to store files We create automatic filenames for each module Returns ------- None Notes ----- Sets self.written_modules to list of written modules """ if not os.path.exists(outdir): os.makedirs(outdir) # compose list of modules modules = self.discover_modules() self.write_modules_api(modules,outdir) def write_index(self, outdir, froot='gen', relative_to=None): """Make a reST API index file from written files Parameters ---------- path : string Filename to write index to outdir : string Directory to which to write generated index file froot : string, optional root (filename without extension) of filename to write to Defaults to 'gen'. We add ``self.rst_extension``. relative_to : string path to which written filenames are relative. This component of the written file path will be removed from outdir, in the generated index. Default is None, meaning, leave path as it is. """ if self.written_modules is None: raise ValueError('No modules written') # Get full filename path index_file_name = os.path.join(outdir, froot+self.rst_extension) # Path written into index is relative to rootpath if relative_to is not None: relative_to = relative_to.rstrip(os.path.sep) outdir = outdir.rstrip(os.path.sep) + os.path.sep relpath = outdir.replace(relative_to + os.path.sep, '') else: relpath = outdir lines = [] w = lines.append w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n') w('*****************************') w(' Generated API Documentation') w('*****************************\n') w('.. toctree::\n') for f in self.written_modules: w(' %s' % os.path.join(relpath, f)) self.write_if_changed(index_file_name, "\n".join(lines) + "\n") def write_if_changed(self, filename, contents): r"""Write the contents to file if they have changed, otherwise leave the file alone so as not to trigger a reformatting of the documentation.""" # First check if outfile exists: old_contents = "" file = None try: file = open(filename, 'r') old_contents = file.read() except IOError: pass finally: if file: file.close() if old_contents != contents: # Only modify the file if something has changed to # prevent forcing the documentation to be re-typeset. file = open(filename, 'wt') file.write(contents) file.close() else: pass
[docs]class ApiDocWriter(_ApiDocWriter): r"""My version that actually imports the modules.""" def _parse_module(self, uri): """Parse module defined in *uri*""" modules = [] functions = [] classes = OrderedDict() interfaces = OrderedDict() others = [] __all__defined = False __apigen__ = dict(nowarn=False, include=[], exclude=[]) __all__ = [] __dict__ = {} try: module = __import__(uri) _names = uri.split('.')[:0:-1] while _names: # __import__ returns the top level module. module = getattr(module, _names.pop()) __apigen__.update(getattr(module, '__apigen__', {})) __dict__.update(getattr(module, '__dict__', {})) if hasattr(module, '__all__'): __all__.extend(getattr(module, '__all__')) __all__defined = True else: if (not __apigen__['nowarn'] and [_n for _n in __dict__ if not _n.startswith('_')]): # Only warn if there is something in the module so that # empty __init__.py files do not trigger logger = logging.getLogger("mmf.sphinx.ext.apigen") logger.warning( "Module %s does not define '__all__': " % uri + "(Generated docs will included all imported " + "objects too!)") except Exception, err: logger = logging.getLogger("mmf.sphinx.ext.apigen") logger.warning("Could not import - " + uri) exc_type, exc_value, exc_traceback = sys.exc_info() logger.debug("\n".join(traceback.format_exception( exc_type, exc_value, exc_traceback, limit=1))) logger.warning("\n".join(traceback.format_exception_only( type(err), err))) if not __all__: # Respect __all__ but if not defined then get non-private # keys from dict. __all__ = [_k for _k in __dict__ if not _k.startswith('_')] # Add and remove anything as specified in __apigen__ for _r in __apigen__['include']: for _n in [_n for _n in module if getattr(_r, 'match', _r.__eq__)(_n)]: if _n not in __all__: __all__.append(_n) for _r in __apigen__['exclude']: for _n in [_n for _n in __all__ if getattr(_r, 'match', _r.__eq__)(_n)]: __all__.remove(_n) for name in __all__: obj = getattr(module, name, NotImplemented) if obj is NotImplemented: try: # Maybe it is a delayed module. Try importing obj = __import__(".".join([uri, name])) except: pass if (obj is not NotImplemented and (__all__defined or (getattr(obj, '__module__', module.__name__) == module.__name__))): # The last condition is that, unless __all__ is defined, we only # document object defined in the current module (as specified by # __module___. Data members (things without __module__) are # always included. if inspect.ismodule(obj): # Modules don't have __module__. We check the name instead # to see if it starts with module.__name__ if (__all__defined or obj.__name__.startswith(module.__name__)): modules.append(name) elif inspect.isfunction(obj): functions.append(name) elif isclass(obj): classes[name] = obj elif isinterface(obj): interfaces[name] = obj else: others.append(name) return (modules, functions, classes, interfaces, others, __apigen__['nowarn'])
[docs]def generate_api(app): r"""This function actually generates the API documentation.""" base_path = app.srcdir logger = logging.getLogger("mmf.sphinx.ext.apigen") if app.config.apigen_outdir: outdir = app.config.apigen_outdir if not outdir.startswith(os.path.sep): outdir = os.path.join(base_path, outdir) else: outdir = os.path.join(app.doctreedir, '_generated', 'api') if app.config.apigen_packages: for package in app.config.apigen_packages: logger.info( "Generating documentation for package '%s'" % package) docwriter = ApiDocWriter( package, package_skip_patterns=app.config.apigen_package_skip_patterns, module_skip_patterns=app.config.apigen_module_skip_patterns, rst_extension=app.config.source_suffix, inherited_members=app.config.apigen_inherited_members ) docwriter.write_api_docs(outdir) docwriter.write_index(outdir, 'index_' + package, relative_to=outdir) app.info('[apigen] %d files written for package %s' % (len(docwriter.written_modules), package)) else: app.warn("[apigen] No packages specified in `conf.py`.\n" + "Please specify the list of packages `apigen_packages`")
import sphinx.ext.autodoc
[docs]class MonkeypatchAutodoc: r"""This fixes some behaviour in :mod:`sphinx.ext.autodoc` that causes our generated documentation to fail. In particular: 1) Some :meth:`sphinx.ext.autodoc.Documenter.can_document_member` instances treat `parent` as a :class:`sphinx.ext.autodoc.Documenter` whereas :mod:`sphinx.ext.autosummary` typically passes objects (such as modules). Thus, functions in a module attempt to be documented by :class:`sphinx.ext.autodoc.MethodDocumenter` rather than :class:`sphinx.ext.autodoc.FunctionDocumenter`. This is fixed by :class:`MyDataDocumenter` and :class:`MyMethodDocumenter` 2) :class:`sphinx.ext.autodoc.ModuleDocumenter` cannot document submodules for some reason (changed in sphinx revision 02f38aa14aaf). I need this functionality, so I restore it in :class:`MyModuleDocumenter`. .. note:: As far as generating documentation, this is not needed if the sub-module is in `__all__`. However, if it is not included, then in some other places I have had problems. For example, module documentation will sometimes be passed to :mod:`numpydoc` with as an attribute or class, so :mod:`numpydoc` tries to parse it and complains about Unknown sections (object docstrings must have a specific set of sections, but module docstrings can have anything). """
[docs] class MyModuleDocumenter(sphinx.ext.autodoc.ModuleDocumenter): @classmethod
[docs] def can_document_member(cls, member, membername, isattr, parent): # Do document submodules! return inspect.ismodule(member)
[docs] class MyAttributeDocumenter(sphinx.ext.autodoc.AttributeDocumenter): @classmethod
[docs] def can_document_member(cls, member, membername, isattr, parent): # Do not allow Attribute Documenter to document modules. isdatadesc = sphinx.ext.autodoc.isdescriptor(member) and not \ isinstance(member, cls.method_types) return isdatadesc or (not inspect.ismodule(parent) and not inspect.isroutine(member) and not inspect.ismodule(member) and not isinstance( member, sphinx.ext.autodoc.class_types))
[docs] class MyDataDocumenter(sphinx.ext.autodoc.DataDocumenter): @classmethod
[docs] def can_document_member(cls, member, membername, isattr, parent): return inspect.ismodule(member) and isattr
[docs] class MyMethodDocumenter(sphinx.ext.autodoc.MethodDocumenter): @classmethod
[docs] def can_document_member(cls, member, membername, isattr, parent): return (sphinx.ext.autodoc.MethodDocumenter.can_document_member( member, membername, isattr, parent) and not inspect.ismodule(parent))
[docs]def setup(app): r"""The guts of the extension. """ if 'sphinx.ext.autosummary' in app._extensions: raise ExtensionError( "The 'mmf.sphinx.ext.apigen' extensions must be setup " + "*before* the 'sphinx.ext.autosummary' extension.\n" + "Please ensure that 'mmf.sphinx.ext.apigen' appears " + "earlier in the list of extensions.") logging.basicConfig(level=logging.INFO) app.add_config_value('apigen_packages', [], 'env') app.add_config_value('apigen_outdir', "", 'env') app.add_config_value('apigen_package_skip_patterns', None, 'env') app.add_config_value('apigen_module_skip_patterns', None, 'env') app.add_config_value('apigen_label', False, 'env') app.add_config_value('apigen_autosummary', True, 'env') app.add_config_value('apigen_inherited_members', False, 'env') app.add_config_value('apigen_headings', False, 'env') app.connect('builder-inited', generate_api) # I need autosummary loaded *after* app.setup_extension('sphinx.ext.autosummary') # My fixes to autodoc. app.setup_extension('sphinx.ext.autodoc') app.add_autodocumenter(MonkeypatchAutodoc.MyModuleDocumenter) app.add_autodocumenter(MonkeypatchAutodoc.MyMethodDocumenter) app.add_autodocumenter(MonkeypatchAutodoc.MyAttributeDocumenter) app.add_autodocumenter(MonkeypatchAutodoc.MyDataDocumenter)