Add docstrings to autodoc.

This commit is contained in:
Georg Brandl
2009-02-18 23:49:51 +01:00
parent a06813c4ae
commit 1a8169e489

View File

@@ -34,6 +34,7 @@ except NameError:
base_exception = Exception base_exception = Exception
#: extended signature RE: with explicit module name separated by ::
py_ext_sig_re = re.compile( py_ext_sig_re = re.compile(
r'''^ ([\w.]+::)? # explicit module name r'''^ ([\w.]+::)? # explicit module name
([\w.]+\.)? # module and/or class name(s) ([\w.]+\.)? # module and/or class name(s)
@@ -45,6 +46,7 @@ py_ext_sig_re = re.compile(
class DefDict(dict): class DefDict(dict):
"""A dict that returns a default on nonexisting keys."""
def __init__(self, default): def __init__(self, default):
dict.__init__(self) dict.__init__(self)
self.default = default self.default = default
@@ -61,21 +63,25 @@ identity = lambda x: x
class Options(dict): class Options(dict):
"""A dict/attribute hybrid that returns None on nonexisting keys."""
def __getattr__(self, name): def __getattr__(self, name):
try: try:
return self[name.replace('_', '-')] return self[name.replace('_', '-')]
except KeyError: except KeyError:
return False return None
ALL = object() ALL = object()
def members_option(arg): def members_option(arg):
"""Used to convert the :members: option to auto directives."""
if arg is None: if arg is None:
return ALL return ALL
return [x.strip() for x in arg.split(',')] return [x.strip() for x in arg.split(',')]
def bool_option(arg): def bool_option(arg):
"""Used to convert flag options to auto directives. (Instead of
directives.flag(), which returns None.)"""
return True return True
@@ -191,12 +197,30 @@ def isdescriptor(x):
class Documenter(object): class Documenter(object):
# name by which the directive is called (auto...) and the default """
# generated directive name 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.
The *special_attrgetters* attribute is used to customize ``getattr()``
calls that the Documenter makes; its entries are of the form
``type: getattr_function``.
"""
#: name by which the directive is called (auto...) and the default
#: generated directive name
objtype = 'object' objtype = 'object'
# indentation by which to indent the directive content #: indentation by which to indent the directive content
content_indent = u' ' content_indent = u' '
# priority if multiple documenters return True from can_document_member #: priority if multiple documenters return True from can_document_member
priority = 0 priority = 0
option_spec = {'noindex': bool_option} option_spec = {'noindex': bool_option}
@@ -238,16 +262,26 @@ class Documenter(object):
self.analyzer = None self.analyzer = None
def add_line(self, line, source, *lineno): 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) self.directive.result.append(self.indent + line, source, *lineno)
def resolve_name(self, modname, parents, path, base): 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') raise NotImplementedError('must be implemented in subclasses')
def parse_name(self): def parse_name(self):
""" """
Determine what module to import and what attribute to document. Determine what module to import and what attribute to document.
Returns True if parsing and resolving was successful. 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 # first, parse the definition -- auto directives for classes and
# functions can contain a signature which is then used instead of # functions can contain a signature which is then used instead of
@@ -276,11 +310,17 @@ class Documenter(object):
self.args = args self.args = args
self.retann = retann self.retann = retann
self.fullname = (self.modname or '') + (self.objpath and self.fullname = (self.modname or '') + \
'.' + '.'.join(self.objpath) or '') (self.objpath and '.' + '.'.join(self.objpath) or '')
return True return True
def import_object(self): def import_object(self):
"""
Import the object given by *self.modname* and *self.objpath* and sets
it as *self.object*.
Returns True if successful, False if an error occurred.
"""
try: try:
__import__(self.modname) __import__(self.modname)
obj = self.module = sys.modules[self.modname] obj = self.module = sys.modules[self.modname]
@@ -296,18 +336,34 @@ class Documenter(object):
return False return False
def get_real_modname(self): 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 return self.get_attr(self.object, '__module__', None) or self.modname
def check_module(self): 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) modname = self.get_attr(self.object, '__module__', None)
if modname and modname != self.modname: if modname and modname != self.modname:
return False return False
return True return True
def format_args(self): def format_args(self):
"""
Format the argument signature of *self.object*. Should return None if
the object does not have a signature.
"""
return None return None
def format_signature(self): 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: if self.args is not None:
# signature given explicitly # signature given explicitly
args = "(%s)" % self.args args = "(%s)" % self.args
@@ -330,6 +386,7 @@ class Documenter(object):
return '' return ''
def add_directive_header(self, sig): def add_directive_header(self, sig):
"""Add the directive header and options to the generated content."""
directive = getattr(self, 'directivetype', self.objtype) directive = getattr(self, 'directivetype', self.objtype)
# the name to put into the generated directive -- doesn't contain # the name to put into the generated directive -- doesn't contain
# the module (except for module directive of course) # the module (except for module directive of course)
@@ -353,7 +410,7 @@ class Documenter(object):
return [] return []
def process_doc(self, docstrings): def process_doc(self, docstrings):
"""Let the user process the docstrings.""" """Let the user process the docstrings before adding them."""
for docstringlines in docstrings: for docstringlines in docstrings:
if self.env.app: if self.env.app:
# let extensions preprocess docstrings # let extensions preprocess docstrings
@@ -364,6 +421,7 @@ class Documenter(object):
yield line yield line
def add_content(self, more_content, no_docstring=False): 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 # set sourcename and add content from attribute documentation
if self.analyzer: if self.analyzer:
# prevent encoding errors when the file name is non-ASCII # prevent encoding errors when the file name is non-ASCII
@@ -395,6 +453,12 @@ class Documenter(object):
self.add_line(line, src[0], src[1]) self.add_line(line, src[0], src[1])
def get_object_members(self, want_all): def get_object_members(self, want_all):
"""
Return (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 want_all:
if not self.options.members: if not self.options.members:
return False, [] return False, []
@@ -419,6 +483,15 @@ class Documenter(object):
for mname in self.object.__dict__]) for mname in self.object.__dict__])
def filter_members(self, members, want_all): def filter_members(self, members, want_all):
"""
Filter the given member list: members are skipped if
- they are private (except if given explicitly)
- they are undocumented (except if undoc-members is given)
The user can override the skipping decision by connecting to the
``autodoc-skip-member`` event.
"""
ret = [] ret = []
# search for members in source code too # search for members in source code too
@@ -464,6 +537,10 @@ class Documenter(object):
return ret return ret
def document_members(self, all_members=False): 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 # set current namespace for finding members
self.env.autodoc_current_module = self.modname self.env.autodoc_current_module = self.modname
if self.objpath: if self.objpath:
@@ -499,6 +576,14 @@ class Documenter(object):
def generate(self, more_content=None, real_modname=None, def generate(self, more_content=None, real_modname=None,
check_module=False, all_members=False): check_module=False, all_members=False):
"""
Generate reST for the object given by *self.name*, and possibly 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(): if not self.parse_name():
# need a module to import # need a module to import
self.directive.warn( self.directive.warn(
@@ -563,6 +648,9 @@ class Documenter(object):
class ModuleDocumenter(Documenter): class ModuleDocumenter(Documenter):
"""
Specialized Documenter subclass for modules.
"""
objtype = 'module' objtype = 'module'
content_indent = u'' content_indent = u''
@@ -625,6 +713,10 @@ class ModuleDocumenter(Documenter):
class ModuleLevelDocumenter(Documenter): class ModuleLevelDocumenter(Documenter):
"""
Specialized Documenter subclass for objects on module level (functions,
classes, data/constants).
"""
def resolve_name(self, modname, parents, path, base): def resolve_name(self, modname, parents, path, base):
if modname is None: if modname is None:
if path: if path:
@@ -642,6 +734,10 @@ class ModuleLevelDocumenter(Documenter):
class ClassLevelDocumenter(Documenter): class ClassLevelDocumenter(Documenter):
"""
Specialized Documenter subclass for objects on class level (methods,
attributes).
"""
def resolve_name(self, modname, parents, path, base): def resolve_name(self, modname, parents, path, base):
if modname is None: if modname is None:
if path: if path:
@@ -671,6 +767,9 @@ class ClassLevelDocumenter(Documenter):
class FunctionDocumenter(ModuleLevelDocumenter): class FunctionDocumenter(ModuleLevelDocumenter):
"""
Specialized Documenter subclass for functions.
"""
objtype = 'function' objtype = 'function'
@classmethod @classmethod
@@ -701,6 +800,9 @@ class FunctionDocumenter(ModuleLevelDocumenter):
class ClassDocumenter(ModuleLevelDocumenter): class ClassDocumenter(ModuleLevelDocumenter):
"""
Specialized Documenter subclass for classes.
"""
objtype = 'class' objtype = 'class'
option_spec = { option_spec = {
'members': members_option, 'undoc-members': bool_option, 'members': members_option, 'undoc-members': bool_option,
@@ -795,6 +897,9 @@ class ClassDocumenter(ModuleLevelDocumenter):
class ExceptionDocumenter(ClassDocumenter): class ExceptionDocumenter(ClassDocumenter):
"""
Specialized ClassDocumenter subclass for exceptions.
"""
objtype = 'exception' objtype = 'exception'
# needs a higher priority than ClassDocumenter # needs a higher priority than ClassDocumenter
@@ -807,6 +912,9 @@ class ExceptionDocumenter(ClassDocumenter):
class DataDocumenter(ModuleLevelDocumenter): class DataDocumenter(ModuleLevelDocumenter):
"""
Specialized Documenter subclass for data items.
"""
objtype = 'data' objtype = 'data'
@classmethod @classmethod
@@ -818,6 +926,9 @@ class DataDocumenter(ModuleLevelDocumenter):
class MethodDocumenter(ClassLevelDocumenter): class MethodDocumenter(ClassLevelDocumenter):
"""
Specialized Documenter subclass for methods (normal, static and class).
"""
objtype = 'method' objtype = 'method'
@classmethod @classmethod
@@ -855,6 +966,9 @@ class MethodDocumenter(ClassLevelDocumenter):
class AttributeDocumenter(ClassLevelDocumenter): class AttributeDocumenter(ClassLevelDocumenter):
"""
Specialized Documenter subclass for attributes.
"""
objtype = 'attribute' objtype = 'attribute'
@classmethod @classmethod
@@ -867,6 +981,11 @@ class AttributeDocumenter(ClassLevelDocumenter):
class AutoDirective(Directive): 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.
"""
# a registry of objtype -> documenter class # a registry of objtype -> documenter class
_registry = {} _registry = {}
@@ -920,12 +1039,14 @@ class AutoDirective(Directive):
def add_documenter(cls): def add_documenter(cls):
"""Register a new Documenter."""
if not issubclass(cls, Documenter): if not issubclass(cls, Documenter):
raise ExtensionError('autodoc documenter %r must be a subclass ' raise ExtensionError('autodoc documenter %r must be a subclass '
'of Documenter' % cls) 'of Documenter' % cls)
if cls.objtype in AutoDirective._registry: # actually, it should be possible to override Documenters
raise ExtensionError('autodoc documenter for %r is already ' #if cls.objtype in AutoDirective._registry:
'registered' % cls.objtype) # raise ExtensionError('autodoc documenter for %r is already '
# 'registered' % cls.objtype)
AutoDirective._registry[cls.objtype] = cls AutoDirective._registry[cls.objtype] = cls