mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #4366 from tk0miya/refactor_autodoc
Refactor autodoc
This commit is contained in:
commit
3965b1f023
4
CHANGES
4
CHANGES
@ -24,6 +24,10 @@ Deprecated
|
||||
``sphinx.util.inspect.Signature`` instead.
|
||||
* ``sphinx.ext.autodoc.AutodocReporter`` is replaced by ``sphinx.util.docutils.
|
||||
switch_source_input()`` and now deprecated. It will be removed in Sphinx-2.0.
|
||||
* ``sphinx.ext.autodoc.add_documenter()`` and ``AutoDirective._register`` is now
|
||||
deprecated. Please use ``app.add_autodocumenter()`` instead.
|
||||
* ``AutoDirective._special_attrgetters`` is now deprecated. Please use
|
||||
``app.add_autodoc_attrgetter()`` instead.
|
||||
|
||||
Features added
|
||||
--------------
|
||||
|
@ -642,16 +642,14 @@ class Sphinx(object):
|
||||
def add_autodocumenter(self, cls):
|
||||
# type: (Any) -> None
|
||||
logger.debug('[app] adding autodocumenter: %r', cls)
|
||||
from sphinx.ext import autodoc
|
||||
from sphinx.ext.autodoc.directive import AutodocDirective
|
||||
autodoc.add_documenter(cls)
|
||||
self.registry.add_documenter(cls.objtype, cls)
|
||||
self.add_directive('auto' + cls.objtype, AutodocDirective)
|
||||
|
||||
def add_autodoc_attrgetter(self, type, getter):
|
||||
# type: (Any, Callable) -> None
|
||||
logger.debug('[app] adding autodoc attrgetter: %r', (type, getter))
|
||||
from sphinx.ext import autodoc
|
||||
autodoc.AutoDirective._special_attrgetters[type] = getter
|
||||
def add_autodoc_attrgetter(self, typ, getter):
|
||||
# type: (Type, Callable[[Any, unicode, Any], Any]) -> None
|
||||
logger.debug('[app] adding autodoc attrgetter: %r', (typ, getter))
|
||||
self.registry.add_autodoc_attrgetter(typ, getter)
|
||||
|
||||
def add_search_language(self, cls):
|
||||
# type: (Any) -> None
|
||||
|
@ -14,16 +14,15 @@
|
||||
import re
|
||||
import sys
|
||||
import inspect
|
||||
import traceback
|
||||
import warnings
|
||||
|
||||
from six import PY2, iterkeys, iteritems, itervalues, text_type, class_types, string_types
|
||||
from six import iteritems, itervalues, text_type, class_types, string_types
|
||||
|
||||
from docutils.statemachine import ViewList
|
||||
|
||||
import sphinx
|
||||
from sphinx.deprecation import RemovedInSphinx20Warning
|
||||
from sphinx.ext.autodoc.importer import mock, import_module
|
||||
from sphinx.ext.autodoc.importer import mock, import_object, get_object_members
|
||||
from sphinx.ext.autodoc.importer import _MockImporter # to keep compatibility # NOQA
|
||||
from sphinx.ext.autodoc.inspector import format_annotation, formatargspec # to keep compatibility # NOQA
|
||||
from sphinx.util import rpartition, force_decode
|
||||
@ -33,7 +32,7 @@ from sphinx.application import ExtensionError
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.inspect import Signature, isdescriptor, safe_getmembers, \
|
||||
safe_getattr, object_description, is_builtin_class_method, \
|
||||
isenumclass, isenumattribute, getdoc
|
||||
isenumattribute, getdoc
|
||||
from sphinx.util.docstrings import prepare_docstring
|
||||
|
||||
if False:
|
||||
@ -257,14 +256,10 @@ class Documenter(object):
|
||||
|
||||
option_spec = {'noindex': bool_option} # type: Dict[unicode, Callable]
|
||||
|
||||
@staticmethod
|
||||
def get_attr(obj, name, *defargs):
|
||||
def get_attr(self, obj, name, *defargs):
|
||||
# type: (Any, unicode, Any) -> Any
|
||||
"""getattr() override for types such as Zope interfaces."""
|
||||
for typ, func in iteritems(AutoDirective._special_attrgetters):
|
||||
if isinstance(obj, typ):
|
||||
return func(obj, name, *defargs)
|
||||
return safe_getattr(obj, name, *defargs)
|
||||
return autodoc_attrgetter(self.env.app, obj, name, *defargs)
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
@ -297,6 +292,12 @@ class Documenter(object):
|
||||
# the module analyzer to get at attribute docs, or None
|
||||
self.analyzer = None # type: Any
|
||||
|
||||
@property
|
||||
def documenters(self):
|
||||
# type: () -> Dict[unicode, Type[Documenter]]
|
||||
"""Returns registered Documenter classes"""
|
||||
return get_documenters(self.env.app)
|
||||
|
||||
def add_line(self, line, source, *lineno):
|
||||
# type: (unicode, unicode, int) -> None
|
||||
"""Append one line of generated reST to the output."""
|
||||
@ -357,56 +358,15 @@ class Documenter(object):
|
||||
|
||||
Returns True if successful, False if an error occurred.
|
||||
"""
|
||||
if self.objpath:
|
||||
logger.debug('[autodoc] from %s import %s',
|
||||
self.modname, '.'.join(self.objpath))
|
||||
# always enable mock import hook
|
||||
# it will do nothing if autodoc_mock_imports is empty
|
||||
with mock(self.env.config.autodoc_mock_imports):
|
||||
try:
|
||||
logger.debug('[autodoc] import %s', self.modname)
|
||||
obj = import_module(self.modname, self.env.config.autodoc_warningiserror)
|
||||
parent = None
|
||||
self.module = obj
|
||||
logger.debug('[autodoc] => %r', obj)
|
||||
for part in self.objpath:
|
||||
parent = obj
|
||||
logger.debug('[autodoc] getattr(_, %r)', part)
|
||||
obj = self.get_attr(obj, part)
|
||||
logger.debug('[autodoc] => %r', obj)
|
||||
self.object_name = part
|
||||
self.parent = parent
|
||||
self.object = obj
|
||||
ret = import_object(self.modname, self.objpath, self.objtype,
|
||||
attrgetter=self.get_attr,
|
||||
warningiserror=self.env.config.autodoc_warningiserror)
|
||||
self.module, self.parent, self.object_name, self.object = ret
|
||||
return True
|
||||
except (AttributeError, ImportError) as exc:
|
||||
if self.objpath:
|
||||
errmsg = 'autodoc: failed to import %s %r from module %r' % \
|
||||
(self.objtype, '.'.join(self.objpath), self.modname)
|
||||
else:
|
||||
errmsg = 'autodoc: failed to import %s %r' % \
|
||||
(self.objtype, self.fullname)
|
||||
|
||||
if isinstance(exc, ImportError):
|
||||
# import_module() raises ImportError having real exception obj and
|
||||
# traceback
|
||||
real_exc, traceback_msg = exc.args
|
||||
if isinstance(real_exc, SystemExit):
|
||||
errmsg += ('; the module executes module level statement ' +
|
||||
'and it might call sys.exit().')
|
||||
elif isinstance(real_exc, ImportError):
|
||||
errmsg += ('; the following exception was raised:\n%s' %
|
||||
real_exc.args[0])
|
||||
else:
|
||||
errmsg += ('; the following exception was raised:\n%s' %
|
||||
traceback_msg)
|
||||
else:
|
||||
errmsg += ('; the following exception was raised:\n%s' %
|
||||
traceback.format_exc())
|
||||
|
||||
if PY2:
|
||||
errmsg = errmsg.decode('utf-8') # type: ignore
|
||||
logger.debug(errmsg)
|
||||
self.directive.warn(errmsg)
|
||||
except ImportError as exc:
|
||||
self.directive.warn(exc.args[0])
|
||||
self.env.note_reread()
|
||||
return False
|
||||
|
||||
@ -579,57 +539,24 @@ class Documenter(object):
|
||||
If *want_all* is True, return all members. Else, only return those
|
||||
members given by *self.options.members* (which may also be none).
|
||||
"""
|
||||
analyzed_member_names = set()
|
||||
if self.analyzer:
|
||||
attr_docs = self.analyzer.find_attr_docs()
|
||||
namespace = '.'.join(self.objpath)
|
||||
for item in iteritems(attr_docs):
|
||||
if item[0][0] == namespace:
|
||||
analyzed_member_names.add(item[0][1])
|
||||
members = get_object_members(self.object, self.objpath, self.get_attr, self.analyzer)
|
||||
if not want_all:
|
||||
if not self.options.members:
|
||||
return False, []
|
||||
# specific members given
|
||||
members = []
|
||||
for mname in self.options.members:
|
||||
try:
|
||||
members.append((mname, self.get_attr(self.object, mname)))
|
||||
except AttributeError:
|
||||
if mname not in analyzed_member_names:
|
||||
self.directive.warn('missing attribute %s in object %s'
|
||||
% (mname, self.fullname))
|
||||
selected = []
|
||||
for name in self.options.members:
|
||||
if name in members:
|
||||
selected.append((name, members[name].value))
|
||||
else:
|
||||
self.directive.warn('missing attribute %s in object %s' %
|
||||
(name, self.fullname))
|
||||
return False, sorted(selected)
|
||||
elif self.options.inherited_members:
|
||||
# safe_getmembers() uses dir() which pulls in members from all
|
||||
# base classes
|
||||
members = safe_getmembers(self.object, attr_getter=self.get_attr)
|
||||
return False, sorted((m.name, m.value) for m in itervalues(members))
|
||||
else:
|
||||
# __dict__ contains only the members directly defined in
|
||||
# the class (but get them via getattr anyway, to e.g. get
|
||||
# unbound method objects instead of function objects);
|
||||
# using list(iterkeys()) because apparently there are objects for which
|
||||
# __dict__ changes while getting attributes
|
||||
try:
|
||||
obj_dict = self.get_attr(self.object, '__dict__')
|
||||
except AttributeError:
|
||||
members = []
|
||||
else:
|
||||
members = [(mname, self.get_attr(self.object, mname, None))
|
||||
for mname in list(iterkeys(obj_dict))]
|
||||
|
||||
# Py34 doesn't have enum members in __dict__.
|
||||
if isenumclass(self.object):
|
||||
members.extend(
|
||||
item for item in self.object.__members__.items()
|
||||
if item not in members
|
||||
)
|
||||
|
||||
membernames = set(m[0] for m in members)
|
||||
# add instance attributes from the analyzer
|
||||
for aname in analyzed_member_names:
|
||||
if aname not in membernames and \
|
||||
(want_all or aname in self.options.members):
|
||||
members.append((aname, INSTANCEATTR))
|
||||
return False, sorted(members)
|
||||
return False, sorted((m.name, m.value) for m in itervalues(members)
|
||||
if m.directly_defined)
|
||||
|
||||
def filter_members(self, members, want_all):
|
||||
# type: (List[Tuple[unicode, Any]], bool) -> List[Tuple[unicode, Any, bool]]
|
||||
@ -741,7 +668,7 @@ class Documenter(object):
|
||||
# document non-skipped members
|
||||
memberdocumenters = [] # type: List[Tuple[Documenter, bool]]
|
||||
for (mname, member, isattr) in self.filter_members(members, want_all):
|
||||
classes = [cls for cls in itervalues(AutoDirective._registry)
|
||||
classes = [cls for cls in itervalues(self.documenters)
|
||||
if cls.can_document_member(member, mname, isattr, self)]
|
||||
if not classes:
|
||||
# don't know how to document this member
|
||||
@ -1477,26 +1404,44 @@ class InstanceAttributeDocumenter(AttributeDocumenter):
|
||||
AttributeDocumenter.add_content(self, more_content, no_docstring=True)
|
||||
|
||||
|
||||
class DeprecatedDict(dict):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
super(DeprecatedDict, self).__init__()
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
warnings.warn(self.message, RemovedInSphinx20Warning)
|
||||
super(DeprecatedDict, self).__setitem__(key, value)
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
warnings.warn(self.message, RemovedInSphinx20Warning)
|
||||
super(DeprecatedDict, self).setdefault(key, default)
|
||||
|
||||
def update(self, other=None):
|
||||
warnings.warn(self.message, RemovedInSphinx20Warning)
|
||||
super(DeprecatedDict, self).update(other)
|
||||
|
||||
|
||||
class AutodocRegistry(object):
|
||||
"""
|
||||
A registry of Documenters and attrgetters.
|
||||
|
||||
The *_registry* attribute is used to store registered Documenters.
|
||||
|
||||
The *_special_attrgetters* attribute is used to customize ``getattr()``
|
||||
calls that the Documenters make; its entries are of the form ``type:
|
||||
getattr_function``.
|
||||
|
||||
Note: When importing an object, all items along the import chain are
|
||||
accessed using the descendant's *_special_attrgetters*, thus this
|
||||
dictionary should include all necessary functions for accessing
|
||||
attributes of the parents.
|
||||
"""
|
||||
# a registry of objtype -> documenter class
|
||||
_registry = {} # type: Dict[unicode, Type[Documenter]]
|
||||
# a registry of objtype -> documenter class (Deprecated)
|
||||
_registry = DeprecatedDict(
|
||||
'AutoDirective._registry has been deprecated. '
|
||||
'Please use app.add_autodocumenter() instead.'
|
||||
) # type: Dict[unicode, Type[Documenter]]
|
||||
|
||||
# a registry of type -> getattr function
|
||||
_special_attrgetters = {} # type: Dict[Type, Callable]
|
||||
_special_attrgetters = DeprecatedDict(
|
||||
'AutoDirective._special_attrgetters has been deprecated. '
|
||||
'Please use app.add_autodoc_attrgetter() instead.'
|
||||
) # type: Dict[Type, Callable]
|
||||
|
||||
|
||||
AutoDirective = AutodocRegistry # for backward compatibility
|
||||
@ -1505,6 +1450,10 @@ AutoDirective = AutodocRegistry # for backward compatibility
|
||||
def add_documenter(cls):
|
||||
# type: (Type[Documenter]) -> None
|
||||
"""Register a new Documenter."""
|
||||
warnings.warn('sphinx.ext.autodoc.add_documenter() has been deprecated. '
|
||||
'Please use app.add_autodocumenter() instead.',
|
||||
RemovedInSphinx20Warning)
|
||||
|
||||
if not issubclass(cls, Documenter):
|
||||
raise ExtensionError('autodoc documenter %r must be a subclass '
|
||||
'of Documenter' % cls)
|
||||
@ -1515,6 +1464,29 @@ def add_documenter(cls):
|
||||
AutoDirective._registry[cls.objtype] = cls
|
||||
|
||||
|
||||
def get_documenters(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Type[Documenter]]
|
||||
"""Returns registered Documenter classes"""
|
||||
classes = dict(AutoDirective._registry) # registered directly
|
||||
if app:
|
||||
classes.update(app.registry.documenters) # registered by API
|
||||
return classes
|
||||
|
||||
|
||||
def autodoc_attrgetter(app, obj, name, *defargs):
|
||||
# type: (Sphinx, Any, unicode, Any) -> Any
|
||||
"""Alternative getattr() for types"""
|
||||
candidates = dict(AutoDirective._special_attrgetters)
|
||||
if app:
|
||||
candidates.update(app.registry.autodoc_attrgettrs)
|
||||
|
||||
for typ, func in iteritems(candidates):
|
||||
if isinstance(obj, typ):
|
||||
return func(obj, name, *defargs)
|
||||
|
||||
return safe_getattr(obj, name, *defargs)
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
app.add_autodocumenter(ModuleDocumenter)
|
||||
|
@ -12,7 +12,7 @@ from docutils.parsers.rst import Directive
|
||||
from docutils.statemachine import ViewList
|
||||
from docutils.utils import assemble_option_dict
|
||||
|
||||
from sphinx.ext.autodoc import AutoDirective
|
||||
from sphinx.ext.autodoc import get_documenters
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.docutils import switch_source_input
|
||||
from sphinx.util.nodes import nested_parse_with_titles
|
||||
@ -127,7 +127,7 @@ class AutodocDirective(Directive):
|
||||
|
||||
# look up target Documenter
|
||||
objtype = self.name[4:] # strip prefix (auto-).
|
||||
doccls = AutoDirective._registry[objtype]
|
||||
doccls = get_documenters(env.app)[objtype]
|
||||
|
||||
# process the options with the selected documenter's option_spec
|
||||
try:
|
||||
|
@ -13,13 +13,17 @@ import sys
|
||||
import warnings
|
||||
import traceback
|
||||
import contextlib
|
||||
from collections import namedtuple
|
||||
from types import FunctionType, MethodType, ModuleType
|
||||
|
||||
from six import PY2
|
||||
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.inspect import isenumclass, safe_getattr
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Generator, List, Set # NOQA
|
||||
from typing import Any, Callable, Dict, Generator, List, Optional, Set # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -144,3 +148,86 @@ def import_module(modname, warningiserror=False):
|
||||
# Importing modules may cause any side effects, including
|
||||
# SystemExit, so we need to catch all errors.
|
||||
raise ImportError(exc, traceback.format_exc())
|
||||
|
||||
|
||||
def import_object(modname, objpath, objtype='', attrgetter=safe_getattr, warningiserror=False):
|
||||
# type: (str, List[unicode], str, Callable[[Any, unicode], Any], bool) -> Any
|
||||
if objpath:
|
||||
logger.debug('[autodoc] from %s import %s', modname, '.'.join(objpath))
|
||||
else:
|
||||
logger.debug('[autodoc] import %s', modname)
|
||||
|
||||
try:
|
||||
module = import_module(modname, warningiserror=warningiserror)
|
||||
logger.debug('[autodoc] => %r', module)
|
||||
obj = module
|
||||
parent = None
|
||||
object_name = None
|
||||
for attrname in objpath:
|
||||
parent = obj
|
||||
logger.debug('[autodoc] getattr(_, %r)', attrname)
|
||||
obj = attrgetter(obj, attrname)
|
||||
logger.debug('[autodoc] => %r', obj)
|
||||
object_name = attrname
|
||||
return [module, parent, object_name, obj]
|
||||
except (AttributeError, ImportError) as exc:
|
||||
if objpath:
|
||||
errmsg = ('autodoc: failed to import %s %r from module %r' %
|
||||
(objtype, '.'.join(objpath), modname))
|
||||
else:
|
||||
errmsg = 'autodoc: failed to import %s %r' % (objtype, modname)
|
||||
|
||||
if isinstance(exc, ImportError):
|
||||
# import_module() raises ImportError having real exception obj and
|
||||
# traceback
|
||||
real_exc, traceback_msg = exc.args
|
||||
if isinstance(real_exc, SystemExit):
|
||||
errmsg += ('; the module executes module level statement '
|
||||
'and it might call sys.exit().')
|
||||
elif isinstance(real_exc, ImportError):
|
||||
errmsg += '; the following exception was raised:\n%s' % real_exc.args[0]
|
||||
else:
|
||||
errmsg += '; the following exception was raised:\n%s' % traceback_msg
|
||||
else:
|
||||
errmsg += '; the following exception was raised:\n%s' % traceback.format_exc()
|
||||
|
||||
if PY2:
|
||||
errmsg = errmsg.decode('utf-8') # type: ignore
|
||||
logger.debug(errmsg)
|
||||
raise ImportError(errmsg)
|
||||
|
||||
|
||||
Attribute = namedtuple('Attribute', ['name', 'directly_defined', 'value'])
|
||||
|
||||
|
||||
def get_object_members(subject, objpath, attrgetter, analyzer=None):
|
||||
# type: (Any, List[unicode], Callable, Any) -> Dict[str, Attribute] # NOQA
|
||||
"""Get members and attributes of target object."""
|
||||
# the members directly defined in the class
|
||||
obj_dict = attrgetter(subject, '__dict__', {})
|
||||
|
||||
# Py34 doesn't have enum members in __dict__.
|
||||
if sys.version_info[:2] == (3, 4) and isenumclass(subject):
|
||||
obj_dict = dict(obj_dict)
|
||||
for name, value in subject.__members__.items():
|
||||
obj_dict[name] = value
|
||||
|
||||
members = {}
|
||||
for name in dir(subject):
|
||||
try:
|
||||
value = attrgetter(subject, name)
|
||||
directly_defined = name in obj_dict
|
||||
members[name] = Attribute(name, directly_defined, value)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
if analyzer:
|
||||
# append instance attributes (cf. self.attr1) if analyzer knows
|
||||
from sphinx.ext.autodoc import INSTANCEATTR
|
||||
|
||||
namespace = '.'.join(objpath)
|
||||
for (ns, name) in analyzer.find_attr_docs():
|
||||
if namespace == ns and name not in members:
|
||||
members[name] = Attribute(name, True, INSTANCEATTR)
|
||||
|
||||
return members
|
||||
|
@ -72,6 +72,7 @@ from sphinx import addnodes
|
||||
from sphinx.environment.adapters.toctree import TocTree
|
||||
from sphinx.util import import_object, rst, logging
|
||||
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||
from sphinx.ext.autodoc import get_documenters
|
||||
from sphinx.ext.autodoc.directive import DocumenterBridge, Options
|
||||
from sphinx.ext.autodoc.importer import import_module
|
||||
|
||||
@ -158,8 +159,8 @@ class FakeDirective(DocumenterBridge):
|
||||
super(FakeDirective, self).__init__({}, None, Options(), 0) # type: ignore
|
||||
|
||||
|
||||
def get_documenter(obj, parent):
|
||||
# type: (Any, Any) -> Type[Documenter]
|
||||
def get_documenter(app, obj, parent):
|
||||
# type: (Sphinx, Any, Any) -> Type[Documenter]
|
||||
"""Get an autodoc.Documenter class suitable for documenting the given
|
||||
object.
|
||||
|
||||
@ -167,8 +168,7 @@ def get_documenter(obj, parent):
|
||||
another Python object (e.g. a module or a class) to which *obj*
|
||||
belongs to.
|
||||
"""
|
||||
from sphinx.ext.autodoc import AutoDirective, DataDocumenter, \
|
||||
ModuleDocumenter
|
||||
from sphinx.ext.autodoc import DataDocumenter, ModuleDocumenter
|
||||
|
||||
if inspect.ismodule(obj):
|
||||
# ModuleDocumenter.can_document_member always returns False
|
||||
@ -176,7 +176,7 @@ def get_documenter(obj, parent):
|
||||
|
||||
# Construct a fake documenter for *parent*
|
||||
if parent is not None:
|
||||
parent_doc_cls = get_documenter(parent, None)
|
||||
parent_doc_cls = get_documenter(app, parent, None)
|
||||
else:
|
||||
parent_doc_cls = ModuleDocumenter
|
||||
|
||||
@ -186,7 +186,7 @@ def get_documenter(obj, parent):
|
||||
parent_doc = parent_doc_cls(FakeDirective(), "")
|
||||
|
||||
# Get the corrent documenter class for *obj*
|
||||
classes = [cls for cls in AutoDirective._registry.values()
|
||||
classes = [cls for cls in get_documenters(app).values()
|
||||
if cls.can_document_member(obj, '', False, parent_doc)]
|
||||
if classes:
|
||||
classes.sort(key=lambda cls: cls.priority)
|
||||
@ -289,7 +289,7 @@ class Autosummary(Directive):
|
||||
full_name = modname + '::' + full_name[len(modname) + 1:]
|
||||
# NB. using full_name here is important, since Documenters
|
||||
# handle module prefixes slightly differently
|
||||
documenter = get_documenter(obj, parent)(self, full_name)
|
||||
documenter = get_documenter(self.env.app, obj, parent)(self, full_name)
|
||||
if not documenter.parse_name():
|
||||
self.warn('failed to parse name %s' % real_name)
|
||||
items.append((display_name, '', '', real_name))
|
||||
@ -615,7 +615,8 @@ def process_generate_options(app):
|
||||
|
||||
generate_autosummary_docs(genfiles, builder=app.builder,
|
||||
warn=logger.warning, info=logger.info,
|
||||
suffix=suffix, base_path=app.srcdir)
|
||||
suffix=suffix, base_path=app.srcdir,
|
||||
app=app)
|
||||
|
||||
|
||||
def setup(app):
|
||||
|
@ -33,24 +33,11 @@ from sphinx import __display_version__
|
||||
from sphinx import package_dir
|
||||
from sphinx.ext.autosummary import import_by_name, get_documenter
|
||||
from sphinx.jinja2glue import BuiltinTemplateLoader
|
||||
from sphinx.registry import SphinxComponentRegistry
|
||||
from sphinx.util.osutil import ensuredir
|
||||
from sphinx.util.inspect import safe_getattr
|
||||
from sphinx.util.rst import escape as rst_escape
|
||||
|
||||
# Add documenters to AutoDirective registry
|
||||
from sphinx.ext.autodoc import add_documenter, \
|
||||
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, \
|
||||
FunctionDocumenter, MethodDocumenter, AttributeDocumenter, \
|
||||
InstanceAttributeDocumenter
|
||||
add_documenter(ModuleDocumenter)
|
||||
add_documenter(ClassDocumenter)
|
||||
add_documenter(ExceptionDocumenter)
|
||||
add_documenter(DataDocumenter)
|
||||
add_documenter(FunctionDocumenter)
|
||||
add_documenter(MethodDocumenter)
|
||||
add_documenter(AttributeDocumenter)
|
||||
add_documenter(InstanceAttributeDocumenter)
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, Dict, Tuple, List # NOQA
|
||||
@ -60,6 +47,30 @@ if False:
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
|
||||
|
||||
class DummyApplication(object):
|
||||
"""Dummy Application class for sphinx-autogen command."""
|
||||
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
self.registry = SphinxComponentRegistry()
|
||||
|
||||
|
||||
def setup_documenters(app):
|
||||
# type: (Any) -> None
|
||||
from sphinx.ext.autodoc import (
|
||||
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
|
||||
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
|
||||
InstanceAttributeDocumenter
|
||||
)
|
||||
documenters = [
|
||||
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
|
||||
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
|
||||
InstanceAttributeDocumenter
|
||||
]
|
||||
for documenter in documenters:
|
||||
app.registry.add_documenter(documenter.objtype, documenter)
|
||||
|
||||
|
||||
def _simple_info(msg):
|
||||
# type: (unicode) -> None
|
||||
print(msg)
|
||||
@ -81,8 +92,8 @@ def _underline(title, line='='):
|
||||
def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
|
||||
warn=_simple_warn, info=_simple_info,
|
||||
base_path=None, builder=None, template_dir=None,
|
||||
imported_members=False):
|
||||
# type: (List[unicode], unicode, unicode, Callable, Callable, unicode, Builder, unicode, bool) -> None # NOQA
|
||||
imported_members=False, app=None):
|
||||
# type: (List[unicode], unicode, unicode, Callable, Callable, unicode, Builder, unicode, bool, Any) -> None # NOQA
|
||||
|
||||
showed_sources = list(sorted(sources))
|
||||
if len(showed_sources) > 20:
|
||||
@ -148,7 +159,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
|
||||
new_files.append(fn)
|
||||
|
||||
with open(fn, 'w') as f:
|
||||
doc = get_documenter(obj, parent)
|
||||
doc = get_documenter(app, obj, parent)
|
||||
|
||||
if template_name is not None:
|
||||
template = template_env.get_template(template_name)
|
||||
@ -167,7 +178,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
|
||||
value = safe_getattr(obj, name)
|
||||
except AttributeError:
|
||||
continue
|
||||
documenter = get_documenter(value, obj)
|
||||
documenter = get_documenter(app, value, obj)
|
||||
if documenter.objtype == typ:
|
||||
if typ == 'method':
|
||||
items.append(name)
|
||||
@ -392,11 +403,14 @@ The format of the autosummary directive is documented in the
|
||||
|
||||
def main(argv=sys.argv[1:]):
|
||||
# type: (List[str]) -> None
|
||||
app = DummyApplication()
|
||||
setup_documenters(app)
|
||||
args = get_parser().parse_args(argv)
|
||||
generate_autosummary_docs(args.source_file, args.output_dir,
|
||||
'.' + args.suffix,
|
||||
template_dir=args.templates,
|
||||
imported_members=args.imported_members)
|
||||
imported_members=args.imported_members,
|
||||
app=app)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -38,6 +38,7 @@ if False:
|
||||
from sphinx.builders import Builder # NOQA
|
||||
from sphinx.domains import Domain, Index # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
from sphinx.ext.autodoc import Documenter # NOQA
|
||||
from sphinx.util.typing import RoleFunction # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -51,7 +52,9 @@ EXTENSION_BLACKLIST = {
|
||||
|
||||
class SphinxComponentRegistry(object):
|
||||
def __init__(self):
|
||||
self.autodoc_attrgettrs = {} # type: Dict[Type, Callable[[Any, unicode, Any], Any]]
|
||||
self.builders = {} # type: Dict[unicode, Type[Builder]]
|
||||
self.documenters = {} # type: Dict[unicode, Type[Documenter]]
|
||||
self.domains = {} # type: Dict[unicode, Type[Domain]]
|
||||
self.domain_directives = {} # type: Dict[unicode, Dict[unicode, Any]]
|
||||
self.domain_indices = {} # type: Dict[unicode, List[Type[Index]]]
|
||||
@ -284,6 +287,14 @@ class SphinxComponentRegistry(object):
|
||||
# type: () -> List[Type[Transform]]
|
||||
return self.post_transforms
|
||||
|
||||
def add_documenter(self, objtype, documenter):
|
||||
# type: (unicode, Type[Documenter]) -> None
|
||||
self.documenters[objtype] = documenter
|
||||
|
||||
def add_autodoc_attrgetter(self, typ, attrgetter):
|
||||
# type: (Type, Callable[[Any, unicode, Any], Any]) -> None
|
||||
self.autodoc_attrgettrs[typ] = attrgetter
|
||||
|
||||
def load_extension(self, app, extname):
|
||||
# type: (Sphinx, unicode) -> None
|
||||
"""Load a Sphinx extension."""
|
||||
|
@ -112,7 +112,7 @@ def skip_member(app, what, name, obj, skip, options):
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_generate():
|
||||
def assert_warns(warn_str, objtype, name, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
assert len(directive.result) == 0, directive.result
|
||||
assert len(_warnings) == 1, _warnings
|
||||
@ -120,7 +120,7 @@ def test_generate():
|
||||
del _warnings[:]
|
||||
|
||||
def assert_works(objtype, name, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
assert directive.result
|
||||
# print '\n'.join(directive.result)
|
||||
@ -134,7 +134,7 @@ def test_generate():
|
||||
assert set(processed_docstrings) | set(processed_signatures) == set(items)
|
||||
|
||||
def assert_result_contains(item, objtype, name, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
# print '\n'.join(directive.result)
|
||||
assert len(_warnings) == 0, _warnings
|
||||
@ -142,7 +142,7 @@ def test_generate():
|
||||
del directive.result[:]
|
||||
|
||||
def assert_order(items, objtype, name, member_order, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.options.member_order = member_order
|
||||
inst.generate(**kw)
|
||||
assert len(_warnings) == 0, _warnings
|
||||
|
@ -121,7 +121,7 @@ def skip_member(app, what, name, obj, skip, options):
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_parse_name():
|
||||
def verify(objtype, name, result):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
assert inst.parse_name()
|
||||
assert (inst.modname, inst.objpath, inst.args, inst.retann) == result
|
||||
|
||||
@ -164,7 +164,7 @@ def test_parse_name():
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_format_signature():
|
||||
def formatsig(objtype, name, obj, args, retann):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.fullname = name
|
||||
inst.doc_as_attr = False # for class objtype
|
||||
inst.object = obj
|
||||
@ -270,7 +270,7 @@ def test_format_signature():
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_get_doc():
|
||||
def getdocl(objtype, obj, encoding=None):
|
||||
inst = AutoDirective._registry[objtype](directive, 'tmp')
|
||||
inst = app.registry.documenters[objtype](directive, 'tmp')
|
||||
inst.object = obj
|
||||
inst.objpath = [obj.__name__]
|
||||
inst.doc_as_attr = False
|
||||
@ -449,7 +449,7 @@ def test_get_doc():
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_docstring_processing():
|
||||
def process(objtype, name, obj):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.object = obj
|
||||
inst.fullname = name
|
||||
return list(inst.process_doc(inst.get_doc()))
|
||||
@ -506,7 +506,7 @@ def test_docstring_property_processing():
|
||||
def genarate_docstring(objtype, name, **kw):
|
||||
del processed_docstrings[:]
|
||||
del processed_signatures[:]
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
results = list(directive.result)
|
||||
docstrings = inst.get_doc()[0]
|
||||
@ -555,7 +555,7 @@ def test_new_documenter():
|
||||
add_documenter(MyDocumenter)
|
||||
|
||||
def assert_result_contains(item, objtype, name, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
# print '\n'.join(directive.result)
|
||||
assert len(_warnings) == 0, _warnings
|
||||
@ -581,7 +581,7 @@ def test_attrgetter_using():
|
||||
AutoDirective._special_attrgetters[type] = special_getattr
|
||||
|
||||
del getattr_spy[:]
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
|
||||
hooked_members = [s[1] for s in getattr_spy]
|
||||
@ -603,7 +603,7 @@ def test_attrgetter_using():
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_generate():
|
||||
def assert_warns(warn_str, objtype, name, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
assert len(directive.result) == 0, directive.result
|
||||
assert len(_warnings) == 1, _warnings
|
||||
@ -611,7 +611,7 @@ def test_generate():
|
||||
del _warnings[:]
|
||||
|
||||
def assert_works(objtype, name, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
assert directive.result
|
||||
# print '\n'.join(directive.result)
|
||||
@ -625,7 +625,7 @@ def test_generate():
|
||||
assert set(processed_docstrings) | set(processed_signatures) == set(items)
|
||||
|
||||
def assert_result_contains(item, objtype, name, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
# print '\n'.join(directive.result)
|
||||
assert len(_warnings) == 0, _warnings
|
||||
@ -633,7 +633,7 @@ def test_generate():
|
||||
del directive.result[:]
|
||||
|
||||
def assert_order(items, objtype, name, member_order, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.options.member_order = member_order
|
||||
inst.generate(**kw)
|
||||
assert len(_warnings) == 0, _warnings
|
||||
|
@ -57,10 +57,14 @@ def test_mangle_signature():
|
||||
|
||||
|
||||
@pytest.mark.sphinx('dummy', **default_kw)
|
||||
def test_get_items_summary(app, status, warning):
|
||||
def test_get_items_summary(make_app, app_params):
|
||||
import sphinx.ext.autosummary
|
||||
import sphinx.ext.autosummary.generate
|
||||
args, kwargs = app_params
|
||||
app = make_app(*args, **kwargs)
|
||||
sphinx.ext.autosummary.generate.setup_documenters(app)
|
||||
# monkey-patch Autosummary.get_items so we can easily get access to it's
|
||||
# results..
|
||||
import sphinx.ext.autosummary
|
||||
orig_get_items = sphinx.ext.autosummary.Autosummary.get_items
|
||||
|
||||
autosummary_items = {}
|
||||
@ -85,7 +89,7 @@ def test_get_items_summary(app, status, warning):
|
||||
finally:
|
||||
sphinx.ext.autosummary.Autosummary.get_items = orig_get_items
|
||||
|
||||
html_warnings = warning.getvalue()
|
||||
html_warnings = app._warning.getvalue()
|
||||
assert html_warnings == ''
|
||||
|
||||
expected_values = {
|
||||
|
@ -10,10 +10,14 @@
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from sphinx.ext.autosummary.generate import setup_documenters
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='templating')
|
||||
def test_layout_overloading(app, status, warning):
|
||||
def test_layout_overloading(make_app, app_params):
|
||||
args, kwargs = app_params
|
||||
app = make_app(*args, **kwargs)
|
||||
setup_documenters(app)
|
||||
app.builder.build_update()
|
||||
|
||||
result = (app.outdir / 'contents.html').text(encoding='utf-8')
|
||||
@ -22,7 +26,10 @@ def test_layout_overloading(app, status, warning):
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='templating')
|
||||
def test_autosummary_class_template_overloading(app, status, warning):
|
||||
def test_autosummary_class_template_overloading(make_app, app_params):
|
||||
args, kwargs = app_params
|
||||
app = make_app(*args, **kwargs)
|
||||
setup_documenters(app)
|
||||
app.builder.build_update()
|
||||
|
||||
result = (app.outdir / 'generated' / 'sphinx.application.TemplateBridge.html').text(
|
||||
|
Loading…
Reference in New Issue
Block a user