diff options
author | Georg Brandl <georg@python.org> | 2010-01-03 13:59:30 +0100 |
---|---|---|
committer | Georg Brandl <georg@python.org> | 2010-01-03 13:59:30 +0100 |
commit | 2398be848090f7158e79c8c4fd20a8456b531587 (patch) | |
tree | 32b14a70f8d9d95f86613d3d1eecaa8a4b9894fd /sphinx | |
parent | e3f2c4aebddf7923f743b5a7761a10e5cd621573 (diff) | |
download | sphinx-2398be848090f7158e79c8c4fd20a8456b531587.tar.gz |
#280: Autodoc can now document instance attributes assigned in ``__init__`` methods.
Diffstat (limited to 'sphinx')
-rw-r--r-- | sphinx/ext/autodoc.py | 51 | ||||
-rw-r--r-- | sphinx/pycode/__init__.py | 39 |
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): |