Skip to content

Instantly share code, notes, and snippets.

@nizz
Last active July 30, 2018 04:47
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nizz/4589457 to your computer and use it in GitHub Desktop.
Save nizz/4589457 to your computer and use it in GitHub Desktop.
Answer for http://stackoverflow.com/questions/14441081/how-to-generate-web-api-docs-from-python-docstrings/ Replace application.py on sphinx path with the one below (the path on my system is /Library/Frameworks/Python.framework/Versions/Current/lib/python2.7/site-packages/Sphinx-1.1.3-py2.7.egg/sphinx/) Add simpleautodoc.py on the ext folder Add…
# -*- coding: utf-8 -*-
"""
sphinx.application
~~~~~~~~~~~~~~~~~~
Sphinx application object.
Gracefully adapted from the TextPress system by Armin.
:copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import sys
import types
import posixpath
from os import path
from cStringIO import StringIO
from docutils import nodes
from docutils.parsers.rst import convert_directive_function, \
directives, roles
import sphinx
from sphinx import package_dir, locale
from sphinx.roles import XRefRole
from sphinx.config import Config
from sphinx.errors import SphinxError, SphinxWarning, ExtensionError, \
VersionRequirementError
from sphinx.domains import ObjType, BUILTIN_DOMAINS
from sphinx.domains.std import GenericObject, Target, StandardDomain
from sphinx.builders import BUILTIN_BUILDERS
from sphinx.environment import BuildEnvironment, SphinxStandaloneReader
from sphinx.util import pycompat # imported for side-effects
from sphinx.util.tags import Tags
from sphinx.util.osutil import ENOENT
from sphinx.util.console import bold
# List of all known core events. Maps name to arguments description.
events = {
'builder-inited': '',
'env-get-outdated': 'env, added, changed, removed',
'env-purge-doc': 'env, docname',
'source-read': 'docname, source text',
'doctree-read': 'the doctree before being pickled',
'missing-reference': 'env, node, contnode',
'doctree-resolved': 'doctree, docname',
'env-updated': 'env',
'html-collect-pages': 'builder',
'html-page-context': 'pagename, context, doctree or None',
'build-finished': 'exception',
}
CONFIG_FILENAME = 'conf.py'
ENV_PICKLE_FILENAME = 'environment.pickle'
class Sphinx(object):
def __init__(self, srcdir, confdir, outdir, doctreedir, buildername,
confoverrides=None, status=sys.stdout, warning=sys.stderr,
freshenv=False, warningiserror=False, tags=None):
self.next_listener_id = 0
self._extensions = {}
self._listeners = {}
self.domains = BUILTIN_DOMAINS.copy()
self.builderclasses = BUILTIN_BUILDERS.copy()
self.builder = None
self.env = None
self.srcdir = srcdir
self.confdir = confdir
self.outdir = outdir
self.doctreedir = doctreedir
if status is None:
self._status = StringIO()
self.quiet = True
else:
self._status = status
self.quiet = False
if warning is None:
self._warning = StringIO()
else:
self._warning = warning
self._warncount = 0
self.warningiserror = warningiserror
self._events = events.copy()
# say hello to the world
self.info(bold('Running Sphinx v%s' % sphinx.__version__))
# status code for command-line application
self.statuscode = 0
# read config
self.tags = Tags(tags)
self.config = Config(confdir, CONFIG_FILENAME,
confoverrides or {}, self.tags)
self.config.check_unicode(self.warn)
# set confdir to srcdir if -C given (!= no confdir); a few pieces
# of code expect a confdir to be set
if self.confdir is None:
self.confdir = self.srcdir
# backwards compatibility: activate old C markup
self.setup_extension('sphinx.ext.oldcmarkup')
# load all user-given extension modules
for extension in self.config.extensions:
self.setup_extension(extension)
# the config file itself can be an extension
if self.config.setup:
self.config.setup(self)
# now that we know all config values, collect them from conf.py
self.config.init_values()
# check the Sphinx version if requested
if self.config.needs_sphinx and \
self.config.needs_sphinx > sphinx.__version__[:3]:
raise VersionRequirementError(
'This project needs at least Sphinx v%s and therefore cannot '
'be built with this version.' % self.config.needs_sphinx)
# set up translation infrastructure
self._init_i18n()
# set up the build environment
self._init_env(freshenv)
# set up the builder
self._init_builder(buildername)
def _init_i18n(self):
"""Load translated strings from the configured localedirs if enabled in
the configuration.
"""
if self.config.language is not None:
self.info(bold('loading translations [%s]... ' %
self.config.language), nonl=True)
locale_dirs = [None, path.join(package_dir, 'locale')] + \
[path.join(self.srcdir, x) for x in self.config.locale_dirs]
else:
locale_dirs = []
self.translator, has_translation = locale.init(locale_dirs,
self.config.language)
if self.config.language is not None:
if has_translation:
self.info('done')
else:
self.info('locale not available')
def _init_env(self, freshenv):
if freshenv:
self.env = BuildEnvironment(self.srcdir, self.doctreedir,
self.config)
self.env.find_files(self.config)
for domain in self.domains.keys():
self.env.domains[domain] = self.domains[domain](self.env)
else:
try:
self.info(bold('loading pickled environment... '), nonl=True)
self.env = BuildEnvironment.frompickle(self.config,
path.join(self.doctreedir, ENV_PICKLE_FILENAME))
self.env.domains = {}
for domain in self.domains.keys():
# this can raise if the data version doesn't fit
self.env.domains[domain] = self.domains[domain](self.env)
self.info('done')
except Exception, err:
if type(err) is IOError and err.errno == ENOENT:
self.info('not yet created')
else:
self.info('failed: %s' % err)
return self._init_env(freshenv=True)
self.env.set_warnfunc(self.warn)
def _init_builder(self, buildername):
if buildername is None:
print >>self._status, 'No builder selected, using default: html'
buildername = 'html'
if buildername not in self.builderclasses:
raise SphinxError('Builder name %s not registered' % buildername)
builderclass = self.builderclasses[buildername]
if isinstance(builderclass, tuple):
# builtin builder
mod, cls = builderclass
builderclass = getattr(
__import__('sphinx.builders.' + mod, None, None, [cls]), cls)
self.builder = builderclass(self)
self.emit('builder-inited')
def build(self, force_all=False, filenames=None):
try:
if force_all:
self.builder.build_all()
elif filenames:
self.builder.build_specific(filenames)
else:
self.builder.build_update()
except Exception, err:
self.emit('build-finished', err)
raise
else:
self.emit('build-finished', None)
self.builder.cleanup()
def warn(self, message, location=None, prefix='WARNING: '):
if isinstance(location, tuple):
docname, lineno = location
if docname:
location = '%s:%s' % (self.env.doc2path(docname), lineno or '')
else:
location = None
warntext = location and '%s: %s%s\n' % (location, prefix, message) or \
'%s%s\n' % (prefix, message)
if self.warningiserror:
raise SphinxWarning(warntext)
self._warncount += 1
try:
self._warning.write(warntext)
except UnicodeEncodeError:
encoding = getattr(self._warning, 'encoding', 'ascii') or 'ascii'
self._warning.write(warntext.encode(encoding, 'replace'))
def info(self, message='', nonl=False):
try:
self._status.write(message)
except UnicodeEncodeError:
encoding = getattr(self._status, 'encoding', 'ascii') or 'ascii'
self._status.write(message.encode(encoding, 'replace'))
if not nonl:
self._status.write('\n')
self._status.flush()
# general extensibility interface
def setup_extension(self, extension):
"""Import and setup a Sphinx extension module. No-op if called twice."""
if extension in self._extensions:
return
try:
mod = __import__(extension, None, None, ['setup'])
except ImportError, err:
raise ExtensionError('Could not import extension %s' % extension,
err)
if not hasattr(mod, 'setup'):
self.warn('extension %r has no setup() function; is it really '
'a Sphinx extension module?' % extension)
else:
try:
mod.setup(self)
except VersionRequirementError, err:
# add the extension name to the version required
raise VersionRequirementError(
'The %s extension used by this project needs at least '
'Sphinx v%s; it therefore cannot be built with this '
'version.' % (extension, err))
self._extensions[extension] = mod
def require_sphinx(self, version):
# check the Sphinx version if requested
if version > sphinx.__version__[:3]:
raise VersionRequirementError(version)
def import_object(self, objname, source=None):
"""Import an object from a 'module.name' string."""
try:
module, name = objname.rsplit('.', 1)
except ValueError, err:
raise ExtensionError('Invalid full object name %s' % objname +
(source and ' (needed for %s)' % source or ''),
err)
try:
return getattr(__import__(module, None, None, [name]), name)
except ImportError, err:
raise ExtensionError('Could not import %s' % module +
(source and ' (needed for %s)' % source or ''),
err)
except AttributeError, err:
raise ExtensionError('Could not find %s' % objname +
(source and ' (needed for %s)' % source or ''),
err)
# event interface
def _validate_event(self, event):
event = intern(event)
if event not in self._events:
raise ExtensionError('Unknown event name: %s' % event)
def connect(self, event, callback):
self._validate_event(event)
listener_id = self.next_listener_id
if event not in self._listeners:
self._listeners[event] = {listener_id: callback}
else:
self._listeners[event][listener_id] = callback
self.next_listener_id += 1
return listener_id
def disconnect(self, listener_id):
for event in self._listeners.itervalues():
event.pop(listener_id, None)
def emit(self, event, *args):
results = []
if event in self._listeners:
for _, callback in self._listeners[event].iteritems():
results.append(callback(self, *args))
return results
def emit_firstresult(self, event, *args):
for result in self.emit(event, *args):
if result is not None:
return result
return None
# registering addon parts
def add_builder(self, builder):
if not hasattr(builder, 'name'):
raise ExtensionError('Builder class %s has no "name" attribute'
% builder)
if builder.name in self.builderclasses:
if isinstance(self.builderclasses[builder.name], tuple):
raise ExtensionError('Builder %r is a builtin builder' %
builder.name)
else:
raise ExtensionError(
'Builder %r already exists (in module %s)' % (
builder.name, self.builderclasses[builder.name].__module__))
self.builderclasses[builder.name] = builder
def add_config_value(self, name, default, rebuild):
if name in self.config.values:
raise ExtensionError('Config value %r already present' % name)
if rebuild in (False, True):
rebuild = rebuild and 'env' or ''
self.config.values[name] = (default, rebuild)
def add_event(self, name):
if name in self._events:
raise ExtensionError('Event %r already present' % name)
self._events[name] = ''
def add_node(self, node, **kwds):
nodes._add_node_class_names([node.__name__])
for key, val in kwds.iteritems():
try:
visit, depart = val
except ValueError:
raise ExtensionError('Value for key %r must be a '
'(visit, depart) function tuple' % key)
if key == 'html':
from sphinx.writers.html import HTMLTranslator as translator
elif key == 'latex':
from sphinx.writers.latex import LaTeXTranslator as translator
elif key == 'text':
from sphinx.writers.text import TextTranslator as translator
elif key == 'man':
from sphinx.writers.manpage import ManualPageTranslator \
as translator
elif key == 'texinfo':
from sphinx.writers.texinfo import TexinfoTranslator \
as translator
else:
# ignore invalid keys for compatibility
continue
setattr(translator, 'visit_'+node.__name__, visit)
if depart:
setattr(translator, 'depart_'+node.__name__, depart)
def _directive_helper(self, obj, content=None, arguments=None, **options):
if isinstance(obj, (types.FunctionType, types.MethodType)):
obj.content = content
obj.arguments = arguments or (0, 0, False)
obj.options = options
return convert_directive_function(obj)
else:
if content or arguments or options:
raise ExtensionError('when adding directive classes, no '
'additional arguments may be given')
return obj
def add_directive(self, name, obj, content=None, arguments=None, **options):
directives.register_directive(
name, self._directive_helper(obj, content, arguments, **options))
def add_role(self, name, role):
roles.register_local_role(name, role)
def add_generic_role(self, name, nodeclass):
# don't use roles.register_generic_role because it uses
# register_canonical_role
role = roles.GenericRole(name, nodeclass)
roles.register_local_role(name, role)
def add_domain(self, domain):
if domain.name in self.domains:
raise ExtensionError('domain %s already registered' % domain.name)
self.domains[domain.name] = domain
def override_domain(self, domain):
if domain.name not in self.domains:
raise ExtensionError('domain %s not yet registered' % domain.name)
if not issubclass(domain, self.domains[domain.name]):
raise ExtensionError('new domain not a subclass of registered '
'domain' % domain.name)
self.domains[domain.name] = domain
def add_directive_to_domain(self, domain, name, obj,
content=None, arguments=None, **options):
if domain not in self.domains:
raise ExtensionError('domain %s not yet registered' % domain)
self.domains[domain].directives[name] = \
self._directive_helper(obj, content, arguments, **options)
def add_role_to_domain(self, domain, name, role):
if domain not in self.domains:
raise ExtensionError('domain %s not yet registered' % domain)
self.domains[domain].roles[name] = role
def add_index_to_domain(self, domain, index):
if domain not in self.domains:
raise ExtensionError('domain %s not yet registered' % domain)
self.domains[domain].indices.append(index)
def add_object_type(self, directivename, rolename, indextemplate='',
parse_node=None, ref_nodeclass=None, objname='',
doc_field_types=[]):
StandardDomain.object_types[directivename] = \
ObjType(objname or directivename, rolename)
# create a subclass of GenericObject as the new directive
new_directive = type(directivename, (GenericObject, object),
{'indextemplate': indextemplate,
'parse_node': staticmethod(parse_node),
'doc_field_types': doc_field_types})
StandardDomain.directives[directivename] = new_directive
# XXX support more options?
StandardDomain.roles[rolename] = XRefRole(innernodeclass=ref_nodeclass)
# backwards compatible alias
add_description_unit = add_object_type
def add_crossref_type(self, directivename, rolename, indextemplate='',
ref_nodeclass=None, objname=''):
StandardDomain.object_types[directivename] = \
ObjType(objname or directivename, rolename)
# create a subclass of Target as the new directive
new_directive = type(directivename, (Target, object),
{'indextemplate': indextemplate})
StandardDomain.directives[directivename] = new_directive
# XXX support more options?
StandardDomain.roles[rolename] = XRefRole(innernodeclass=ref_nodeclass)
def add_transform(self, transform):
SphinxStandaloneReader.transforms.append(transform)
def add_javascript(self, filename):
from sphinx.builders.html import StandaloneHTMLBuilder
if '://' in filename:
StandaloneHTMLBuilder.script_files.append(filename)
else:
StandaloneHTMLBuilder.script_files.append(
posixpath.join('_static', filename))
def add_stylesheet(self, filename):
from sphinx.builders.html import StandaloneHTMLBuilder
if '://' in filename:
StandaloneHTMLBuilder.css_files.append(filename)
else:
StandaloneHTMLBuilder.css_files.append(
posixpath.join('_static', filename))
def add_lexer(self, alias, lexer):
from sphinx.highlighting import lexers
if lexers is None:
return
lexers[alias] = lexer
def add_autodocumenter(self, cls):
from sphinx.ext import autodoc
autodoc.add_documenter(cls)
self.add_directive('auto' + cls.objtype, autodoc.AutoDirective)
def add_simpleautodocumenter(self, cls):
from sphinx.ext import simpleautodoc
simpleautodoc.add_documenter(cls)
self.add_directive('autosimple' + cls.objtype, simpleautodoc.AutoDirective)
def add_autodoc_attrgetter(self, type, getter):
from sphinx.ext import autodoc
autodoc.AutoDirective._special_attrgetters[type] = getter
def add_search_language(self, cls):
from sphinx.search import languages, SearchLanguage
assert isinstance(cls, SearchLanguage)
languages[cls.lang] = cls
class TemplateBridge(object):
"""
This class defines the interface for a "template bridge", that is, a class
that renders templates given a template name and a context.
"""
def init(self, builder, theme=None, dirs=None):
"""Called by the builder to initialize the template system.
*builder* is the builder object; you'll probably want to look at the
value of ``builder.config.templates_path``.
*theme* is a :class:`sphinx.theming.Theme` object or None; in the latter
case, *dirs* can be list of fixed directories to look for templates.
"""
raise NotImplementedError('must be implemented in subclasses')
def newest_template_mtime(self):
"""Called by the builder to determine if output files are outdated
because of template changes. Return the mtime of the newest template
file that was changed. The default implementation returns ``0``.
"""
return 0
def render(self, template, context):
"""Called by the builder to render a template given as a filename with
a specified context (a Python dictionary).
"""
raise NotImplementedError('must be implemented in subclasses')
def render_string(self, template, context):
"""Called by the builder to render a template given as a string with a
specified context (a Python dictionary).
"""
raise NotImplementedError('must be implemented in subclasses')
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/Documentation.launch</value>
</dictionary>
</arguments>
</buildCommand>
def get(self, user_key=None):
"""
.. http:get:: /users/<user_key>
Retrieve a User Profile if user_key is present or a list with profiles.
:param <user_key>: The Key of the user we want to retrieve
:type <user_key>: String
**Example request**:
.. sourcecode:: http
GET /api/users/ag9kZXZ-cmFkaW9qYXJjb21yEQsSC1VzZXJQcm9maWxlGAIM HTTP/1.1
Host: xxxxxxx.com
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/javascript
{
"first_name":"MyName",
"last_name":"MySurname",
...
}
"""
# -*- coding: utf-8 -*-
"""
sphinx.ext.simpleautodoc
~~~~~~~~~~~~~~~~~~
Automatically insert docstrings for functions, classes or whole modules into
the doctree, thus avoiding duplication between docstrings and documentation
for those who like elaborate docstrings.
:copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
import sys
import inspect
import traceback
from types import FunctionType, BuiltinFunctionType, MethodType
from docutils import nodes
from docutils.utils import assemble_option_dict
from docutils.statemachine import ViewList
from sphinx.util import rpartition, force_decode
from sphinx.locale import _
from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.application import ExtensionError
from sphinx.util.nodes import nested_parse_with_titles
from sphinx.util.compat import Directive
from sphinx.util.inspect import getargspec, isdescriptor, safe_getmembers, \
safe_getattr, safe_repr
from sphinx.util.pycompat import base_exception, class_types
from sphinx.util.docstrings import prepare_docstring
#: extended signature RE: with explicit module name separated by ::
py_ext_sig_re = re.compile(
r'''^ ([\w.]+::)? # explicit module name
([\w.]+\.)? # module and/or class name(s)
(\w+) \s* # thing name
(?: \((.*)\) # optional: arguments
(?:\s* -> \s* (.*))? # return annotation
)? $ # and nothing more
''', re.VERBOSE)
class DefDict(dict):
"""A dict that returns a default on nonexisting keys."""
def __init__(self, default):
dict.__init__(self)
self.default = default
def __getitem__(self, key):
try:
return dict.__getitem__(self, key)
except KeyError:
return self.default
def __nonzero__(self):
# docutils check "if option_spec"
return True
identity = lambda x: x
class Options(dict):
"""A dict/attribute hybrid that returns None on nonexisting keys."""
def __getattr__(self, name):
try:
return self[name.replace('_', '-')]
except KeyError:
return None
ALL = object()
INSTANCEATTR = object()
def members_option(arg):
"""Used to convert the :members: option to auto directives."""
if arg is None:
return ALL
return [x.strip() for x in arg.split(',')]
def members_set_option(arg):
"""Used to convert the :members: option to auto directives."""
if arg is None:
return ALL
return set(x.strip() for x in arg.split(','))
def bool_option(arg):
"""Used to convert flag options to auto directives. (Instead of
directives.flag(), which returns None).
"""
return True
class AutodocReporter(object):
"""
A reporter replacement that assigns the correct source name
and line number to a system message, as recorded in a ViewList.
"""
def __init__(self, viewlist, reporter):
self.viewlist = viewlist
self.reporter = reporter
def __getattr__(self, name):
return getattr(self.reporter, name)
def system_message(self, level, message, *children, **kwargs):
if 'line' in kwargs and 'source' not in kwargs:
try:
source, line = self.viewlist.items[kwargs['line']]
except IndexError:
pass
else:
kwargs['source'] = source
kwargs['line'] = line
return self.reporter.system_message(level, message,
*children, **kwargs)
def debug(self, *args, **kwargs):
if self.reporter.debug_flag:
return self.system_message(0, *args, **kwargs)
def info(self, *args, **kwargs):
return self.system_message(1, *args, **kwargs)
def warning(self, *args, **kwargs):
return self.system_message(2, *args, **kwargs)
def error(self, *args, **kwargs):
return self.system_message(3, *args, **kwargs)
def severe(self, *args, **kwargs):
return self.system_message(4, *args, **kwargs)
# Some useful event listener factories for autodoc-process-docstring.
def cut_lines(pre, post=0, what=None):
"""Return a listener that removes the first *pre* and last *post*
lines of every docstring. If *what* is a sequence of strings,
only docstrings of a type in *what* will be processed.
Use like this (e.g. in the ``setup()`` function of :file:`conf.py`)::
from sphinx.ext.autodoc import cut_lines
app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
This can (and should) be used in place of :confval:`automodule_skip_lines`.
"""
def process(app, what_, name, obj, options, lines):
if what and what_ not in what:
return
del lines[:pre]
if post:
# remove one trailing blank line.
if lines and not lines[-1]:
lines.pop(-1)
del lines[-post:]
# make sure there is a blank line at the end
if lines and lines[-1]:
lines.append('')
return process
def between(marker, what=None, keepempty=False, exclude=False):
"""Return a listener that either keeps, or if *exclude* is True excludes,
lines between lines that match the *marker* regular expression. If no line
matches, the resulting docstring would be empty, so no change will be made
unless *keepempty* is true.
If *what* is a sequence of strings, only docstrings of a type in *what* will
be processed.
"""
marker_re = re.compile(marker)
def process(app, what_, name, obj, options, lines):
if what and what_ not in what:
return
deleted = 0
delete = not exclude
orig_lines = lines[:]
for i, line in enumerate(orig_lines):
if delete:
lines.pop(i - deleted)
deleted += 1
if marker_re.match(line):
delete = not delete
if delete:
lines.pop(i - deleted)
deleted += 1
if not lines and not keepempty:
lines[:] = orig_lines
# make sure there is a blank line at the end
if lines and lines[-1]:
lines.append('')
return process
class Documenter(object):
"""
A Documenter knows how to autodocument a single object type. When
registered with the AutoDirective, it will be used to document objects
of that type when needed by autodoc.
Its *objtype* attribute selects what auto directive it is assigned to
(the directive name is 'auto' + objtype), and what directive it generates
by default, though that can be overridden by an attribute called
*directivetype*.
A Documenter has an *option_spec* that works like a docutils directive's;
in fact, it will be used to parse an auto directive's options that matches
the documenter.
"""
#: name by which the directive is called (auto...) and the default
#: generated directive name
objtype = 'object'
#: indentation by which to indent the directive content
content_indent = u''
#: priority if multiple documenters return True from can_document_member
priority = 0
#: order if autodoc_member_order is set to 'groupwise'
member_order = 0
#: true if the generated content may contain titles
titles_allowed = False
option_spec = {'noindex': bool_option}
@staticmethod
def get_attr(obj, name, *defargs):
"""getattr() override for types such as Zope interfaces."""
for typ, func in AutoDirective._special_attrgetters.iteritems():
if isinstance(obj, typ):
return func(obj, name, *defargs)
return safe_getattr(obj, name, *defargs)
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
"""Called to see if a member can be documented by this documenter."""
raise NotImplementedError('must be implemented in subclasses')
def __init__(self, directive, name, indent=u''):
self.directive = directive
self.env = directive.env
self.options = directive.genopt
self.name = name
self.indent = indent
# the module and object path within the module, and the fully
# qualified name (all set after resolve_name succeeds)
self.modname = None
self.module = None
self.objpath = None
self.fullname = None
# extra signature items (arguments and return annotation,
# also set after resolve_name succeeds)
self.args = None
self.retann = None
# the object to document (set after import_object succeeds)
self.object = None
self.object_name = None
# the parent/owner of the object to document
self.parent = None
# the module analyzer to get at attribute docs, or None
self.analyzer = None
def add_line(self, line, source, *lineno):
"""Append one line of generated reST to the output."""
self.directive.result.append(self.indent + line, source, *lineno)
def resolve_name(self, modname, parents, path, base):
"""Resolve the module and name of the object to document given by the
arguments and the current module/class.
Must return a pair of the module name and a chain of attributes; for
example, it would return ``('zipfile', ['ZipFile', 'open'])`` for the
``zipfile.ZipFile.open`` method.
"""
raise NotImplementedError('must be implemented in subclasses')
def parse_name(self):
"""Determine what module to import and what attribute to document.
Returns True and sets *self.modname*, *self.objpath*, *self.fullname*,
*self.args* and *self.retann* if parsing and resolving was successful.
"""
# first, parse the definition -- auto directives for classes and
# functions can contain a signature which is then used instead of
# an autogenerated one
try:
explicit_modname, path, base, args, retann = \
py_ext_sig_re.match(self.name).groups()
except AttributeError:
self.directive.warn('invalid signature for auto%s (%r)' %
(self.objtype, self.name))
return False
# support explicit module and class name separation via ::
if explicit_modname is not None:
modname = explicit_modname[:-2]
parents = path and path.rstrip('.').split('.') or []
else:
modname = None
parents = []
self.modname, self.objpath = \
self.resolve_name(modname, parents, path, base)
if not self.modname:
return False
self.args = args
self.retann = retann
self.fullname = (self.modname or '') + \
(self.objpath and '.' + '.'.join(self.objpath) or '')
return True
def import_object(self):
"""Import the object given by *self.modname* and *self.objpath* and set
it as *self.object*.
Returns True if successful, False if an error occurred.
"""
try:
__import__(self.modname)
parent = None
obj = self.module = sys.modules[self.modname]
for part in self.objpath:
parent = obj
obj = self.get_attr(obj, part)
self.object_name = part
self.parent = parent
self.object = obj
return True
# this used to only catch SyntaxError, ImportError and AttributeError,
# but importing modules with side effects can raise all kinds of errors
except Exception, err:
if self.env.app and not self.env.app.quiet:
self.env.app.info(traceback.format_exc().rstrip())
self.directive.warn(
'autodoc can\'t import/find %s %r, it reported error: '
'"%s", please check your spelling and sys.path' %
(self.objtype, str(self.fullname), err))
self.env.note_reread()
return False
def get_real_modname(self):
"""Get the real module name of an object to document.
It can differ from the name of the module through which the object was
imported.
"""
return self.get_attr(self.object, '__module__', None) or self.modname
def check_module(self):
"""Check if *self.object* is really defined in the module given by
*self.modname*.
"""
modname = self.get_attr(self.object, '__module__', None)
if modname and modname != self.modname:
return False
return True
def format_args(self):
"""Format the argument signature of *self.object*.
Should return None if the object does not have a signature.
"""
return None
def format_name(self):
"""Format the name of *self.object*.
This normally should be something that can be parsed by the generated
directive, but doesn't need to be (Sphinx will display it unparsed
then).
"""
# normally the name doesn't contain the module (except for module
# directives of course)
return '.'.join(self.objpath) or self.modname
def format_signature(self):
"""Format the signature (arguments and return annotation) of the object.
Let the user process it via the ``autodoc-process-signature`` event.
"""
if self.args is not None:
# signature given explicitly
args = "(%s)" % self.args
else:
# try to introspect the signature
try:
args = self.format_args()
except Exception, err:
self.directive.warn('error while formatting arguments for '
'%s: %s' % (self.fullname, err))
args = None
retann = self.retann
result = self.env.app.emit_firstresult(
'autodoc-process-signature', self.objtype, self.fullname,
self.object, self.options, args, retann)
if result:
args, retann = result
if args is not None:
return args + (retann and (' -> %s' % retann) or '')
else:
return ''
def add_directive_header(self, sig):
"""Add the directive header and options to the generated content."""
domain = getattr(self, 'domain', 'py')
directive = getattr(self, 'directivetype', self.objtype)
#if self.objtype not in ('class', 'method', 'module'):
name = self.format_name()
if self.objtype not in ('class', 'method', 'module'):
self.add_line(u'.. %s:%s:: %s%s' % (domain, directive, name, sig), '<autodoc>')
else:
return
if self.options.noindex:
self.add_line(u' :noindex:', '<autodoc>')
if self.objpath:
# Be explicit about the module, this is necessary since .. class::
# etc. don't support a prepended module name
self.add_line(u' :module: %s' % self.modname, '<autodoc>')
def get_doc(self, encoding=None, ignore=1):
"""Decode and return lines of the docstring(s) for the object."""
docstring = self.get_attr(self.object, '__doc__', None)
# make sure we have Unicode docstrings, then sanitize and split
# into lines
if isinstance(docstring, unicode):
return [prepare_docstring(docstring, ignore)]
elif docstring:
return [prepare_docstring(force_decode(docstring, encoding),
ignore)]
return []
def process_doc(self, docstrings):
"""Let the user process the docstrings before adding them."""
for docstringlines in docstrings:
if self.env.app:
# let extensions preprocess docstrings
self.env.app.emit('autodoc-process-docstring',
self.objtype, self.fullname, self.object,
self.options, docstringlines)
for line in docstringlines:
yield line
def add_content(self, more_content, no_docstring=False):
"""Add content from docstrings, attribute documentation and user."""
# set sourcename and add content from attribute documentation
if self.analyzer:
# prevent encoding errors when the file name is non-ASCII
if not isinstance(self.analyzer.srcname, unicode):
filename = unicode(self.analyzer.srcname,
sys.getfilesystemencoding(), 'replace')
else:
filename = self.analyzer.srcname
sourcename = u'%s:docstring of %s' % (filename, self.fullname)
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
docstrings = [attr_docs[key]]
for i, line in enumerate(self.process_doc(docstrings)):
self.add_line(line, sourcename, i)
else:
sourcename = u'docstring of %s' % self.fullname
# add content from docstrings
if not no_docstring:
encoding = self.analyzer and self.analyzer.encoding
docstrings = self.get_doc(encoding)
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.data, more_content.items):
self.add_line(line, src[0], src[1])
def get_object_members(self, want_all):
"""Return `(members_check_module, members)` where `members` is a
list of `(membername, member)` pairs of the members of *self.object*.
If *want_all* is True, return all members. Else, only return those
members given by *self.options.members* (which may also be none).
"""
if not want_all:
if not self.options.members:
return False, []
# specific members given
ret = []
for mname in self.options.members:
try:
ret.append((mname, self.get_attr(self.object, mname)))
except AttributeError:
self.directive.warn('missing attribute %s in object %s'
% (mname, self.fullname))
return False, ret
if self.options.inherited_members:
# safe_getmembers() uses dir() which pulls in members from all
# base classes
members = safe_getmembers(self.object)
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 keys() 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 obj_dict.keys()]
membernames = set(m[0] for m in members)
# add instance attributes from the analyzer
if self.analyzer:
attr_docs = self.analyzer.find_attr_docs()
namespace = '.'.join(self.objpath)
for item in attr_docs.iteritems():
if item[0][0] == namespace:
if item[0][1] not in membernames:
members.append((item[0][1], INSTANCEATTR))
return False, sorted(members)
def filter_members(self, members, want_all):
"""Filter the given member list.
Members are skipped if
- they are private (except if given explicitly or the private-members
option is set)
- they are special methods (except if given explicitly or the
special-members option is set)
- they are undocumented (except if the undoc-members option is set)
The user can override the skipping decision by connecting to the
``autodoc-skip-member`` event.
"""
ret = []
# search for members in source code too
namespace = '.'.join(self.objpath) # will be empty for modules
if self.analyzer:
attr_docs = self.analyzer.find_attr_docs()
else:
attr_docs = {}
# process members and determine which to skip
for (membername, member) in members:
# if isattr is True, the member is documented as an attribute
isattr = False
doc = self.get_attr(member, '__doc__', None)
# if the member __doc__ is the same as self's __doc__, it's just
# inherited and therefore not the member's doc
cls = self.get_attr(member, '__class__', None)
if cls:
cls_doc = self.get_attr(cls, '__doc__', None)
if cls_doc == doc:
doc = None
has_doc = bool(doc)
keep = False
if want_all and membername.startswith('__') and \
membername.endswith('__') and len(membername) > 4:
# special __methods__
if self.options.special_members and membername != '__doc__':
keep = has_doc or self.options.undoc_members
elif want_all and membername.startswith('_'):
# ignore members whose name starts with _ by default
keep = self.options.private_members and \
(has_doc or self.options.undoc_members)
elif (namespace, membername) in attr_docs:
# keep documented attributes
keep = True
isattr = True
else:
# ignore undocumented members if :undoc-members: is not given
keep = has_doc or self.options.undoc_members
# give the user a chance to decide whether this member
# should be skipped
if self.env.app:
# let extensions preprocess docstrings
skip_user = self.env.app.emit_firstresult(
'autosimpledoc-skip-member', self.objtype, membername, member,
not keep, self.options)
if skip_user is not None:
keep = not skip_user
if keep:
ret.append((membername, member, isattr))
return ret
def document_members(self, all_members=False):
"""Generate reST for member documentation.
If *all_members* is True, do all members, else those given by
*self.options.members*.
"""
# set current namespace for finding members
self.env.temp_data['autodoc:module'] = self.modname
if self.objpath:
self.env.temp_data['autodoc:class'] = self.objpath[0]
want_all = all_members or self.options.inherited_members or \
self.options.members is ALL
# find out which members are documentable
members_check_module, members = self.get_object_members(want_all)
# remove members given by exclude-members
if self.options.exclude_members:
members = [(membername, member) for (membername, member) in members
if membername not in self.options.exclude_members]
# document non-skipped members
memberdocumenters = []
for (mname, member, isattr) in self.filter_members(members, want_all):
classes = [cls for cls in AutoDirective._registry.itervalues()
if cls.can_document_member(member, mname, isattr, self)]
if not classes:
# don't know how to document this member
continue
# prefer the documenter with the highest priority
classes.sort(key=lambda cls: cls.priority)
# give explicitly separated module name, so that members
# of inner classes can be documented
full_mname = self.modname + '::' + \
'.'.join(self.objpath + [mname])
documenter = classes[-1](self.directive, full_mname, self.indent)
memberdocumenters.append((documenter, isattr))
member_order = self.options.member_order or \
self.env.config.autosimpledoc_member_order
if member_order == 'groupwise':
# sort by group; relies on stable sort to keep items in the
# same group sorted alphabetically
memberdocumenters.sort(key=lambda e: e[0].member_order)
elif member_order == 'bysource' and self.analyzer:
# sort by source order, by virtue of the module analyzer
tagorder = self.analyzer.tagorder
def keyfunc(entry):
fullname = entry[0].name.split('::')[1]
return tagorder.get(fullname, len(tagorder))
memberdocumenters.sort(key=keyfunc)
for documenter, isattr in memberdocumenters:
documenter.generate(
all_members=True, real_modname=self.real_modname,
check_module=members_check_module and not isattr)
# reset current objects
self.env.temp_data['autodoc:module'] = None
self.env.temp_data['autodoc:class'] = None
def generate(self, more_content=None, real_modname=None,
check_module=False, all_members=False):
"""Generate reST for the object given by *self.name*, 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.
"""
if not self.parse_name():
# need a module to import
self.directive.warn(
'don\'t know which module to import for autodocumenting '
'%r (try placing a "module" or "currentmodule" directive '
'in the document, or giving an explicit module name)'
% self.name)
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.
self.real_modname = real_modname or self.get_real_modname()
# try to also get a source code analyzer for attribute docs
try:
self.analyzer = ModuleAnalyzer.for_module(self.real_modname)
# parse right now, to get PycodeErrors on parsing (results will
# be cached anyway)
self.analyzer.find_attr_docs()
except PycodeError:
# no source file -- e.g. for builtin and C modules
self.analyzer = None
# at least add the module.__file__ as a dependency
if hasattr(self.module, '__file__') and self.module.__file__:
self.directive.filename_set.add(self.module.__file__)
else:
self.directive.filename_set.add(self.analyzer.srcname)
# check __module__ of object (for members not given explicitly)
if check_module:
if not self.check_module():
return
# make sure that the result starts with an empty line. This is
# necessary for some situations where another directive preprocesses
# reST and no starting newline is present
self.add_line(u'', '<autodoc>')
# format the object's signature, if any
sig = self.format_signature()
# generate the directive header and options, if applicable
self.add_directive_header(sig)
self.add_line(u'', '<autodoc>')
# e.g. the module directive doesn't have content
self.indent += self.content_indent
# add all content (from docstrings, attribute docs etc.)
self.add_content(more_content)
# document members, if possible
self.document_members(all_members)
class ModuleDocumenter(Documenter):
"""
Specialized Documenter subclass for modules.
"""
objtype = 'module'
content_indent = u''
titles_allowed = True
option_spec = {
'members': members_option, 'undoc-members': bool_option,
'noindex': bool_option, 'inherited-members': bool_option,
'show-inheritance': bool_option, 'synopsis': identity,
'platform': identity, 'deprecated': bool_option,
'member-order': identity, 'exclude-members': members_set_option,
'private-members': bool_option, 'special-members': bool_option,
}
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# don't document submodules automatically
return False
def resolve_name(self, modname, parents, path, base):
if modname is not None:
self.directive.warn('"::" in automodule name doesn\'t make sense')
return (path or '') + base, []
def parse_name(self):
ret = Documenter.parse_name(self)
if self.args or self.retann:
self.directive.warn('signature arguments or return annotation '
'given for automodule %s' % self.fullname)
return ret
def add_directive_header(self, sig):
Documenter.add_directive_header(self, sig)
# add some module-specific options
if self.options.synopsis:
self.add_line(
u' :synopsis: ' + self.options.synopsis, '<autodoc>')
if self.options.platform:
self.add_line(
u' :platform: ' + self.options.platform, '<autodoc>')
if self.options.deprecated:
self.add_line(u' :deprecated:', '<autodoc>')
def get_object_members(self, want_all):
if want_all:
if not hasattr(self.object, '__all__'):
# for implicit module members, check __module__ to avoid
# documenting imported objects
return True, safe_getmembers(self.object)
else:
memberlist = self.object.__all__
else:
memberlist = self.options.members or []
ret = []
for mname in memberlist:
try:
ret.append((mname, safe_getattr(self.object, mname)))
except AttributeError:
self.directive.warn(
'missing attribute mentioned in :members: or __all__: '
'module %s, attribute %s' % (
safe_getattr(self.object, '__name__', '???'), mname))
return False, ret
class ModuleLevelDocumenter(Documenter):
"""
Specialized Documenter subclass for objects on module level (functions,
classes, data/constants).
"""
def resolve_name(self, modname, parents, path, base):
if modname is None:
if path:
modname = path.rstrip('.')
else:
# if documenting a toplevel object without explicit module,
# it can be contained in another auto directive ...
modname = self.env.temp_data.get('autodoc:module')
# ... or in the scope of a module directive
if not modname:
modname = self.env.temp_data.get('py:module')
# ... else, it stays None, which means invalid
return modname, parents + [base]
class ClassLevelDocumenter(Documenter):
"""
Specialized Documenter subclass for objects on class level (methods,
attributes).
"""
def resolve_name(self, modname, parents, path, base):
if modname is None:
if path:
mod_cls = path.rstrip('.')
else:
mod_cls = None
# if documenting a class-level object without path,
# there must be a current class, either from a parent
# auto directive ...
mod_cls = self.env.temp_data.get('autodoc:class')
# ... or from a class directive
if mod_cls is None:
mod_cls = self.env.temp_data.get('py:class')
# ... if still None, there's no way to know
if mod_cls is None:
return None, []
modname, cls = rpartition(mod_cls, '.')
parents = [cls]
# if the module name is still missing, get it like above
if not modname:
modname = self.env.temp_data.get('autodoc:module')
if not modname:
modname = self.env.temp_data.get('py:module')
# ... else, it stays None, which means invalid
return modname, parents + [base]
class DocstringSignatureMixin(object):
"""
Mixin for FunctionDocumenter and MethodDocumenter to provide the
feature of reading the signature from the docstring.
"""
def _find_signature(self, encoding=None):
docstrings = Documenter.get_doc(self, encoding, 2)
if len(docstrings) != 1:
return
doclines = docstrings[0]
setattr(self, '__new_doclines', doclines)
if not doclines:
return
# match first line of docstring against signature RE
match = py_ext_sig_re.match(doclines[0])
if not match:
return
exmod, path, base, args, retann = match.groups()
# the base name must match ours
if not self.objpath or base != self.objpath[-1]:
return
# ok, now jump over remaining empty lines and set the remaining
# lines as the new doclines
i = 1
while i < len(doclines) and not doclines[i].strip():
i += 1
setattr(self, '__new_doclines', doclines[i:])
return args, retann
def get_doc(self, encoding=None, ignore=1):
lines = getattr(self, '__new_doclines', None)
if lines is not None:
return [lines]
return Documenter.get_doc(self, encoding, ignore)
def format_signature(self):
if self.args is None and self.env.config.autosimpledoc_docstring_signature:
# only act if a signature is not explicitly given already, and if
# the feature is enabled
result = self._find_signature()
if result is not None:
self.args, self.retann = result
return Documenter.format_signature(self)
class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
"""
Specialized Documenter subclass for functions.
"""
objtype = 'function'
member_order = 30
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
return isinstance(member, (FunctionType, BuiltinFunctionType))
def format_args(self):
if inspect.isbuiltin(self.object) or \
inspect.ismethoddescriptor(self.object):
# cannot introspect arguments of a C function or method
return None
try:
argspec = getargspec(self.object)
except TypeError:
# if a class should be documented as function (yay duck
# typing) we try to use the constructor signature as function
# signature without the first argument.
try:
argspec = getargspec(self.object.__new__)
except TypeError:
argspec = getargspec(self.object.__init__)
if argspec[0]:
del argspec[0][0]
args = inspect.formatargspec(*argspec)
# escape backslashes for reST
args = args.replace('\\', '\\\\')
return args
def document_members(self, all_members=False):
pass
class ClassDocumenter(ModuleLevelDocumenter):
"""
Specialized Documenter subclass for classes.
"""
objtype = 'class'
member_order = 20
option_spec = {
'members': members_option, 'undoc-members': bool_option,
'noindex': bool_option, 'inherited-members': bool_option,
'show-inheritance': bool_option, 'member-order': identity,
'exclude-members': members_set_option,
'private-members': bool_option, 'special-members': bool_option,
}
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
return isinstance(member, class_types)
def import_object(self):
ret = ModuleLevelDocumenter.import_object(self)
# if the class is documented under another name, document it
# as data/attribute
if ret:
if hasattr(self.object, '__name__'):
self.doc_as_attr = (self.objpath[-1] != self.object.__name__)
else:
self.doc_as_attr = True
return ret
def format_args(self):
# for classes, the relevant signature is the __init__ method's
initmeth = self.get_attr(self.object, '__init__', None)
# classes without __init__ method, default __init__ or
# __init__ written in C?
if initmeth is None or initmeth is object.__init__ or not \
(inspect.ismethod(initmeth) or inspect.isfunction(initmeth)):
return None
try:
argspec = getargspec(initmeth)
except TypeError:
# still not possible: happens e.g. for old-style classes
# with __init__ in C
return None
if argspec[0] and argspec[0][0] in ('cls', 'self'):
del argspec[0][0]
return inspect.formatargspec(*argspec)
def format_signature(self):
if self.doc_as_attr:
return ''
return ModuleLevelDocumenter.format_signature(self)
def add_directive_header(self, sig):
if self.doc_as_attr:
self.directivetype = 'attribute'
Documenter.add_directive_header(self, sig)
# add inheritance info, if wanted
if not self.doc_as_attr and self.options.show_inheritance:
self.add_line(u'', '<autodoc>')
if len(self.object.__bases__):
bases = [b.__module__ == '__builtin__' and
u':class:`%s`' % b.__name__ or
u':class:`%s.%s`' % (b.__module__, b.__name__)
for b in self.object.__bases__]
self.add_line(_(u' Bases: %s') % ', '.join(bases),
'<autodoc>')
def get_doc(self, encoding=None, ignore=1):
content = self.env.config.autosimpleclass_content
docstrings = []
attrdocstring = self.get_attr(self.object, '__doc__', None)
if attrdocstring:
docstrings.append(attrdocstring)
# for classes, what the "docstring" is can be controlled via a
# config value; the default is only the class docstring
if content in ('both', 'init'):
initdocstring = self.get_attr(
self.get_attr(self.object, '__init__', None), '__doc__')
# for new-style classes, no __init__ means default __init__
if initdocstring == object.__init__.__doc__:
initdocstring = None
if initdocstring:
if content == 'init':
docstrings = [initdocstring]
else:
docstrings.append(initdocstring)
doc = []
for docstring in docstrings:
if not isinstance(docstring, unicode):
docstring = force_decode(docstring, encoding)
doc.append(prepare_docstring(docstring))
return doc
def add_content(self, more_content, no_docstring=False):
if self.doc_as_attr:
classname = safe_getattr(self.object, '__name__', None)
if classname:
content = ViewList(
[_('alias of :class:`%s`') % classname], source='')
ModuleLevelDocumenter.add_content(self, content,
no_docstring=True)
else:
ModuleLevelDocumenter.add_content(self, more_content)
def document_members(self, all_members=False):
if self.doc_as_attr:
return
ModuleLevelDocumenter.document_members(self, all_members)
class ExceptionDocumenter(ClassDocumenter):
"""
Specialized ClassDocumenter subclass for exceptions.
"""
objtype = 'exception'
member_order = 10
# needs a higher priority than ClassDocumenter
priority = 10
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
return isinstance(member, class_types) and \
issubclass(member, base_exception)
class DataDocumenter(ModuleLevelDocumenter):
"""
Specialized Documenter subclass for data items.
"""
objtype = 'data'
member_order = 40
priority = -10
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
return isinstance(parent, ModuleDocumenter) and isattr
def add_directive_header(self, sig):
ModuleLevelDocumenter.add_directive_header(self, sig)
try:
objrepr = safe_repr(self.object)
except ValueError:
pass
else:
self.add_line(u' :annotation: = ' + objrepr, '<autodoc>')
def document_members(self, all_members=False):
pass
class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter):
"""
Specialized Documenter subclass for methods (normal, static and class).
"""
objtype = 'method'
member_order = 50
priority = 0
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
return inspect.isroutine(member) and \
not isinstance(parent, ModuleDocumenter)
if sys.version_info >= (3, 0):
def import_object(self):
ret = ClassLevelDocumenter.import_object(self)
obj_from_parent = self.parent.__dict__.get(self.object_name)
if isinstance(obj_from_parent, classmethod):
self.directivetype = 'classmethod'
self.member_order = self.member_order - 1
elif isinstance(obj_from_parent, staticmethod):
self.directivetype = 'staticmethod'
self.member_order = self.member_order - 1
else:
self.directivetype = 'method'
return ret
else:
def import_object(self):
ret = ClassLevelDocumenter.import_object(self)
if isinstance(self.object, classmethod) or \
(isinstance(self.object, MethodType) and
self.object.im_self is not None):
self.directivetype = 'classmethod'
# document class and static members before ordinary ones
self.member_order = self.member_order - 1
elif isinstance(self.object, FunctionType) or \
(isinstance(self.object, BuiltinFunctionType) and
hasattr(self.object, '__self__') and
self.object.__self__ is not None):
self.directivetype = 'staticmethod'
# document class and static members before ordinary ones
self.member_order = self.member_order - 1
else:
self.directivetype = 'method'
return ret
def format_args(self):
if inspect.isbuiltin(self.object) or \
inspect.ismethoddescriptor(self.object):
# can never get arguments of a C function or method
return None
argspec = getargspec(self.object)
if argspec[0] and argspec[0][0] in ('cls', 'self'):
del argspec[0][0]
return inspect.formatargspec(*argspec)
def document_members(self, all_members=False):
pass
class AttributeDocumenter(ClassLevelDocumenter):
"""
Specialized Documenter subclass for attributes.
"""
objtype = 'attribute'
member_order = 60
# must be higher than the MethodDocumenter, else it will recognize
# some non-data descriptors as methods
priority = 10
method_types = (FunctionType, BuiltinFunctionType, MethodType)
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
isdatadesc = isdescriptor(member) and not \
isinstance(member, cls.method_types) and not \
type(member).__name__ == "method_descriptor"
return isdatadesc or (not isinstance(parent, ModuleDocumenter)
and not inspect.isroutine(member)
and not isinstance(member, class_types))
def document_members(self, all_members=False):
pass
def import_object(self):
ret = ClassLevelDocumenter.import_object(self)
if isdescriptor(self.object) and \
not isinstance(self.object, self.method_types):
self._datadescriptor = True
else:
# if it's not a data descriptor
self._datadescriptor = False
return ret
def get_real_modname(self):
return self.get_attr(self.parent or self.object, '__module__', None) \
or self.modname
def add_directive_header(self, sig):
ClassLevelDocumenter.add_directive_header(self, sig)
if not self._datadescriptor:
try:
objrepr = safe_repr(self.object)
except ValueError:
pass
else:
self.add_line(u' :annotation: = ' + objrepr, '<autodoc>')
def add_content(self, more_content, no_docstring=False):
if not self._datadescriptor:
# if it's not a data descriptor, its docstring is very probably the
# wrong thing to display
no_docstring = True
ClassLevelDocumenter.add_content(self, more_content, no_docstring)
class InstanceAttributeDocumenter(AttributeDocumenter):
"""
Specialized Documenter subclass for attributes that cannot be imported
because they are instance attributes (e.g. assigned in __init__).
"""
objtype = 'instanceattribute'
directivetype = 'attribute'
member_order = 60
# must be higher than AttributeDocumenter
priority = 11
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
"""This documents only INSTANCEATTR members."""
return isattr and (member is INSTANCEATTR)
def import_object(self):
"""Never import anything."""
# disguise as an attribute
self.objtype = 'attribute'
self._datadescriptor = False
return True
def add_content(self, more_content, no_docstring=False):
"""Never try to get a docstring from the object."""
AttributeDocumenter.add_content(self, more_content, no_docstring=True)
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.
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 = {}
# a registry of type -> getattr function
_special_attrgetters = {}
# flags that can be given in autodoc_default_flags
_default_flags = set([
'members', 'undoc-members', 'inherited-members', 'show-inheritance',
'private-members', 'special-members',
])
# standard docutils directive settings
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
# allow any options to be passed; the options are parsed further
# by the selected Documenter
option_spec = DefDict(identity)
def warn(self, msg):
self.warnings.append(self.reporter.warning(msg, line=self.lineno))
def run(self):
self.filename_set = set() # a set of dependent filenames
self.reporter = self.state.document.reporter
self.env = self.state.document.settings.env
self.warnings = []
self.result = ViewList()
# find out what documenter to call
objtype = self.name[10:]
doc_class = self._registry[objtype]
# add default flags
for flag in self._default_flags:
if flag not in doc_class.option_spec:
continue
negated = self.options.pop('no-' + flag, 'not given') is None
if flag in self.env.config.autosimpledoc_default_flags and \
not negated:
self.options[flag] = None
# process the options with the selected documenter's option_spec
self.genopt = Options(assemble_option_dict(
self.options.items(), doc_class.option_spec))
# generate the output
documenter = doc_class(self, self.arguments[0])
documenter.generate(more_content=self.content)
if not self.result:
return self.warnings
# record all filenames as dependencies -- this will at least
# partially make automatic invalidation possible
for fn in self.filename_set:
self.state.document.settings.record_dependencies.add(fn)
# use a custom reporter that correctly assigns lines to source
# filename/description and lineno
old_reporter = self.state.memo.reporter
self.state.memo.reporter = AutodocReporter(self.result,
self.state.memo.reporter)
if documenter.titles_allowed:
node = nodes.section()
# necessary so that the child nodes get the right source/line set
node.document = self.state.document
nested_parse_with_titles(self.state, self.result, node)
else:
node = nodes.paragraph()
node.document = self.state.document
self.state.nested_parse(self.result, 0, node)
self.state.memo.reporter = old_reporter
return self.warnings + node.children
def add_documenter(cls):
"""Register a new Documenter."""
if not issubclass(cls, Documenter):
print str(cls)
pass
raise ExtensionError('autodoc documenter %r must be a subclass '
'of Documenter' % cls)
# actually, it should be possible to override Documenters
#if cls.objtype in AutoDirective._registry:
# raise ExtensionError('autodoc documenter for %r is already '
# 'registered' % cls.objtype)
AutoDirective._registry[cls.objtype] = cls
def setup(app):
app.add_simpleautodocumenter(ModuleDocumenter)
app.add_simpleautodocumenter(ClassDocumenter)
app.add_simpleautodocumenter(ExceptionDocumenter)
app.add_simpleautodocumenter(DataDocumenter)
app.add_simpleautodocumenter(FunctionDocumenter)
app.add_simpleautodocumenter(MethodDocumenter)
app.add_simpleautodocumenter(AttributeDocumenter)
app.add_simpleautodocumenter(InstanceAttributeDocumenter)
app.add_config_value('autosimpleclass_content', 'class', True)
app.add_config_value('autosimpledoc_member_order', 'alphabetic', True)
app.add_config_value('autosimpledoc_default_flags', [], True)
app.add_config_value('autosimpledoc_docstring_signature', True, True)
app.add_event('autosimpledoc-process-docstring')
app.add_event('autosimpledoc-process-signature')
app.add_event('autosimpledoc-skip-member')
class testcls:
"""test doc string"""
def __getattr__(self, x):
return x
def __setattr__(self, x, y):
"""Attr setter."""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment