summaryrefslogtreecommitdiff
path: root/sphinx
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2010-01-03 13:59:30 +0100
committerGeorg Brandl <georg@python.org>2010-01-03 13:59:30 +0100
commit2398be848090f7158e79c8c4fd20a8456b531587 (patch)
tree32b14a70f8d9d95f86613d3d1eecaa8a4b9894fd /sphinx
parente3f2c4aebddf7923f743b5a7761a10e5cd621573 (diff)
downloadsphinx-2398be848090f7158e79c8c4fd20a8456b531587.tar.gz
#280: Autodoc can now document instance attributes assigned in ``__init__`` methods.
Diffstat (limited to 'sphinx')
-rw-r--r--sphinx/ext/autodoc.py51
-rw-r--r--sphinx/pycode/__init__.py39
2 files changed, 77 insertions, 13 deletions
diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py
index 5d71d25c..cf38dbb8 100644
--- a/sphinx/ext/autodoc.py
+++ b/sphinx/ext/autodoc.py
@@ -72,6 +72,7 @@ class Options(dict):
ALL = object()
+INSTANCEATTR = object()
def members_option(arg):
"""Used to convert the :members: option to auto directives."""
@@ -474,19 +475,30 @@ class Documenter(object):
self.directive.warn('missing attribute %s in object %s'
% (mname, self.fullname))
return False, ret
- elif self.options.inherited_members:
+
+ if self.options.inherited_members:
# safe_getmembers() uses dir() which pulls in members from all
# base classes
- return False, safe_getmembers(self.object)
+ 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
- return False, sorted([
- (mname, self.get_attr(self.object, mname, None))
- for mname in self.get_attr(self.object, '__dict__').keys()])
+ obj_dict = self.get_attr(self.object, '__dict__')
+ 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):
"""
@@ -1036,6 +1048,34 @@ class AttributeDocumenter(ClassLevelDocumenter):
pass
+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'
+ 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
@@ -1132,6 +1172,7 @@ def setup(app):
app.add_autodocumenter(FunctionDocumenter)
app.add_autodocumenter(MethodDocumenter)
app.add_autodocumenter(AttributeDocumenter)
+ app.add_autodocumenter(InstanceAttributeDocumenter)
app.add_config_value('autoclass_content', 'class', True)
app.add_config_value('autodoc_member_order', 'alphabetic', True)
diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py
index b7473bf2..19fd09e1 100644
--- a/sphinx/pycode/__init__.py
+++ b/sphinx/pycode/__init__.py
@@ -45,22 +45,33 @@ _eq = nodes.Leaf(token.EQUAL, '=')
class AttrDocVisitor(nodes.NodeVisitor):
"""
Visitor that collects docstrings for attribute assignments on toplevel and
- in classes.
+ in classes (class attributes and attributes set in __init__).
The docstrings can either be in special '#:' comments before the assignment
or in a docstring after it.
"""
def init(self, scope, encoding):
self.scope = scope
+ self.in_init = 0
self.encoding = encoding
self.namespace = []
self.collected = {}
def visit_classdef(self, node):
+ """Visit a class."""
self.namespace.append(node[1].value)
self.generic_visit(node)
self.namespace.pop()
+ def visit_funcdef(self, node):
+ """Visit a function (or method)."""
+ # usually, don't descend into functions -- nothing interesting there
+ if node[1].value == '__init__':
+ # however, collect attributes set in __init__ methods
+ self.in_init += 1
+ self.generic_visit(node)
+ self.in_init -= 1
+
def visit_expr_stmt(self, node):
"""Visit an assignment which may have a special comment before it."""
if _eq not in node.children:
@@ -97,20 +108,32 @@ class AttrDocVisitor(nodes.NodeVisitor):
docstring = prepare_docstring(docstring)
self.add_docstring(prev[0], docstring)
- def visit_funcdef(self, node):
- # don't descend into functions -- nothing interesting there
- return
-
def add_docstring(self, node, docstring):
# add an item for each assignment target
for i in range(0, len(node) - 1, 2):
target = node[i]
- if target.type != token.NAME:
- # don't care about complex targets
+ if self.in_init and self.number2name[target.type] == 'power':
+ # maybe an attribute assignment -- check necessary conditions
+ if (# node must have two children
+ len(target) != 2 or
+ # first child must be "self"
+ target[0].type != token.NAME or target[0].value != 'self' or
+ # second child must be a "trailer" with two children
+ self.number2name[target[1].type] != 'trailer' or
+ len(target[1]) != 2 or
+ # first child must be a dot, second child a name
+ target[1][0].type != token.DOT or
+ target[1][1].type != token.NAME):
+ continue
+ name = target[1][1].value
+ elif target.type != token.NAME:
+ # don't care about other complex targets
continue
+ else:
+ name = target.value
namespace = '.'.join(self.namespace)
if namespace.startswith(self.scope):
- self.collected[namespace, target.value] = docstring
+ self.collected[namespace, name] = docstring
class PycodeError(Exception):