Skip to content

Commit

Permalink
Deprecate sphinx.ext.autodoc.add_documenter() and AutoDirective._regi…
Browse files Browse the repository at this point in the history
…ster
  • Loading branch information
tk0miya committed Jan 1, 2018
1 parent 1d64ade commit 5d6413b
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 55 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Deprecated
values will be accepted at 2.0.
* ``format_annotation()`` and ``formatargspec()`` is deprecated. Please use
``sphinx.util.inspect.Signature`` instead.
* ``sphinx.ext.autodoc.add_documenter()`` and ``AutoDirective._register`` is now
deprecated. Please use ``app.add_autodocumenter()``

Features added
--------------
Expand Down
6 changes: 3 additions & 3 deletions sphinx/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,9 +642,9 @@ def add_lexer(self, alias, lexer):
def add_autodocumenter(self, cls):
# type: (Any) -> None
logger.debug('[app] adding autodocumenter: %r', cls)
from sphinx.ext import autodoc
autodoc.add_documenter(cls)
self.add_directive('auto' + cls.objtype, autodoc.AutoDirective)
from sphinx.ext.autodoc import AutoDirective
self.add_directive('auto' + cls.objtype, AutoDirective)
self.registry.add_documenter(cls.objtype, cls)

def add_autodoc_attrgetter(self, type, getter):
# type: (Any, Callable) -> None
Expand Down
55 changes: 49 additions & 6 deletions sphinx/ext/autodoc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import re
import sys
import inspect
import warnings

from six import iterkeys, iteritems, itervalues, text_type, class_types, string_types

Expand All @@ -23,6 +24,7 @@
from docutils.statemachine import ViewList

import sphinx
from sphinx.deprecation import RemovedInSphinx20Warning
from sphinx.ext.autodoc.importer import mock, import_object
from sphinx.ext.autodoc.importer import _MockImporter # to keep compatibility # NOQA
from sphinx.ext.autodoc.inspector import format_annotation, formatargspec # to keep compatibility # NOQA
Expand Down Expand Up @@ -323,6 +325,14 @@ def __init__(self, directive, name, indent=u''):
# 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"""
classes = dict(AutoDirective._registry) # registered directly
classes.update(self.env.app.registry.documenters) # registered by API
return classes

def add_line(self, line, source, *lineno):
# type: (unicode, unicode, int) -> None
"""Append one line of generated reST to the output."""
Expand Down Expand Up @@ -727,7 +737,7 @@ def document_members(self, all_members=False):
# 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
Expand Down Expand Up @@ -1463,11 +1473,28 @@ def add_content(self, more_content, no_docstring=False):
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 AutoDirective(Directive):
"""
The AutoDirective class is used for all autodoc directives. It dispatches
most of the work to one of the Documenters, which it selects through its
*_registry* dictionary.
most of the work to one of the Documenters.
The *_special_attrgetters* attribute is used to customize ``getattr()``
calls that the Documenters make; its entries are of the form ``type:
Expand All @@ -1478,8 +1505,11 @@ class AutoDirective(Directive):
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]
Expand Down Expand Up @@ -1521,7 +1551,7 @@ def run(self):

# find out what documenter to call
objtype = self.name[4:]
doc_class = self._registry[objtype]
doc_class = get_documenters(self.env.app)[objtype]
# add default flags
for flag in self._default_flags:
if flag not in doc_class.option_spec:
Expand Down Expand Up @@ -1575,6 +1605,10 @@ def run(self):
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)
Expand All @@ -1585,6 +1619,15 @@ 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 setup(app):
# type: (Sphinx) -> Dict[unicode, Any]
app.add_autodocumenter(ModuleDocumenter)
Expand Down
18 changes: 9 additions & 9 deletions sphinx/ext/autosummary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@
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 Options
from sphinx.ext.autodoc.importer import import_module
from sphinx.ext.autodoc import Options, get_documenters

if False:
# For type annotation
Expand Down Expand Up @@ -158,25 +158,24 @@ class FakeDirective(object):
genopt = Options()


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.
*obj* is the Python object to be documented, and *parent* is an
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
return ModuleDocumenter

# 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

Expand All @@ -186,7 +185,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)
Expand Down Expand Up @@ -289,7 +288,7 @@ def get_items(self, names):
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))
Expand Down Expand Up @@ -615,7 +614,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):
Expand Down
38 changes: 21 additions & 17 deletions sphinx/ext/autosummary/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,13 @@

from sphinx import __display_version__
from sphinx import package_dir
from sphinx.ext.autodoc import add_documenter
from sphinx.ext.autosummary import import_by_name, get_documenter
from sphinx.jinja2glue import BuiltinTemplateLoader
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
Expand All @@ -60,6 +47,22 @@
from sphinx.environment import BuildEnvironment # NOQA


def setup_documenters():
from sphinx.ext.autodoc import (
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)


def _simple_info(msg):
# type: (unicode) -> None
print(msg)
Expand All @@ -81,7 +84,7 @@ 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):
imported_members=False, app=None):
# type: (List[unicode], unicode, unicode, Callable, Callable, unicode, Builder, unicode, bool) -> None # NOQA

showed_sources = list(sorted(sources))
Expand Down Expand Up @@ -148,7 +151,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)
Expand All @@ -167,7 +170,7 @@ def get_members(obj, typ, include_public=[], imported=False):
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)
Expand Down Expand Up @@ -392,6 +395,7 @@ def get_parser():

def main(argv=sys.argv[1:]):
# type: (List[str]) -> None
setup_documenters()
args = get_parser().parse_args(argv)
generate_autosummary_docs(args.source_file, args.output_dir,
'.' + args.suffix,
Expand Down
6 changes: 6 additions & 0 deletions sphinx/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
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__)
Expand All @@ -52,6 +53,7 @@
class SphinxComponentRegistry(object):
def __init__(self):
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]]]
Expand Down Expand Up @@ -284,6 +286,10 @@ def get_post_transforms(self):
# type: () -> List[Type[Transform]]
return self.post_transforms

def add_documenter(self, objtype, documenter):
# type: (unicode, Type[Documenter]) -> None
self.documenters[objtype] = documenter

def load_extension(self, app, extname):
# type: (Sphinx, unicode) -> None
"""Load a Sphinx extension."""
Expand Down
8 changes: 4 additions & 4 deletions tests/py35/test_autodoc_py35.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,15 @@ 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
assert warn_str in _warnings[0], _warnings
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)
Expand All @@ -134,15 +134,15 @@ def assert_processes(items, objtype, name, **kw):
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
assert item in directive.result
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
Expand Down

0 comments on commit 5d6413b

Please sign in to comment.