diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index 044a181f7..396841363 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -27,7 +27,8 @@ 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 isdescriptor, safe_getmembers, safe_getattr +from sphinx.util.inspect import getargspec, isdescriptor, safe_getmembers, \ + safe_getattr from sphinx.util.pycompat import base_exception, class_types from sphinx.util.docstrings import prepare_docstring @@ -902,15 +903,15 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # cannot introspect arguments of a C function or method pass try: - argspec = inspect.getargspec(self.object) + 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 = inspect.getargspec(self.object.__new__) + argspec = getargspec(self.object.__new__) except TypeError: - argspec = inspect.getargspec(self.object.__init__) + argspec = getargspec(self.object.__init__) if argspec[0]: del argspec[0][0] args = inspect.formatargspec(*argspec) @@ -960,7 +961,7 @@ class ClassDocumenter(ModuleLevelDocumenter): (inspect.ismethod(initmeth) or inspect.isfunction(initmeth)): return None try: - argspec = inspect.getargspec(initmeth) + argspec = getargspec(initmeth) except TypeError: # still not possible: happens e.g. for old-style classes # with __init__ in C @@ -1117,7 +1118,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): inspect.ismethoddescriptor(self.object): # can never get arguments of a C function or method return None - argspec = inspect.getargspec(self.object) + argspec = getargspec(self.object) if argspec[0] and argspec[0][0] in ('cls', 'self'): del argspec[0][0] return inspect.formatargspec(*argspec) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index a05740038..6b2beffaf 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -9,6 +9,44 @@ :license: BSD, see LICENSE for details. """ +import sys + +# this imports the standard library inspect module without resorting to +# relatively import this module +inspect = __import__('inspect') + + +if sys.version_info >= (2, 5): + from functools import partial + def getargspec(func): + """Like inspect.getargspec but supports functools.partial as well.""" + if inspect.ismethod(func): + func = func.im_func + parts = 0, () + if type(func) is partial: + parts = len(func.args), func.keywords.keys() + func = func.func + if not inspect.isfunction(func): + raise TypeError('%r is not a Python function' % func) + args, varargs, varkw = inspect.getargs(func.func_code) + func_defaults = func.func_defaults + if func_defaults: + func_defaults = list(func_defaults) + if parts[0]: + args = args[parts[0]:] + if parts[1]: + for arg in parts[1]: + i = args.index(arg) - len(args) + del args[i] + try: + del func_defaults[i] + except IndexError: + pass + return inspect.ArgSpec(args, varargs, varkw, func_defaults) +else: + getargspec = inspect.getargspec + + def isdescriptor(x): """Check if the object is some kind of descriptor.""" for item in '__get__', '__set__', '__delete__': diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 3e9b4f25c..b0da900eb 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -10,6 +10,9 @@ :license: BSD, see LICENSE for details. """ +import sys +from StringIO import StringIO + from util import * from docutils.statemachine import ViewList @@ -17,7 +20,6 @@ from docutils.statemachine import ViewList from sphinx.ext.autodoc import AutoDirective, add_documenter, \ ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL -from StringIO import StringIO def setup_module(): global app, lid, options, directive @@ -422,12 +424,14 @@ def test_generate(): ('attribute', 'test_autodoc.Class.udocattr'), ('attribute', 'test_autodoc.Class.mdocattr'), ('attribute', 'test_autodoc.Class.inst_attr_comment'), - ('attribute', 'test_autodoc.Class.inst_attr_string') + ('attribute', 'test_autodoc.Class.inst_attr_string'), + ('method', 'test_autodoc.Class.moore'), ]) options.members = ALL assert_processes(should, 'class', 'Class') options.undoc_members = True - should.append(('method', 'test_autodoc.Class.undocmeth')) + should.extend((('method', 'test_autodoc.Class.undocmeth'), + ('method', 'test_autodoc.Class.roger'))) assert_processes(should, 'class', 'Class') options.inherited_members = True should.append(('method', 'test_autodoc.Class.inheritedmeth')) @@ -490,6 +494,8 @@ def test_generate(): ' .. py:attribute:: Class.docattr', ' .. py:attribute:: Class.udocattr', ' .. py:attribute:: Class.mdocattr', + ' .. py:classmethod:: Class.roger(a, e=5, f=6)', + ' .. py:classmethod:: Class.moore(a, e, f) -> happiness', ' .. py:attribute:: Class.inst_attr_comment', ' .. py:attribute:: Class.inst_attr_string', ' .. py:method:: Class.inheritedmeth()', @@ -509,6 +515,9 @@ def test_generate(): 'test_autodoc.DocstringSig.meth') assert_result_contains( ' rest of docstring', 'method', 'test_autodoc.DocstringSig.meth') + assert_result_contains( + '.. py:classmethod:: Class.moore(a, e, f) -> happiness', 'method', + 'test_autodoc.Class.moore') # --- generate fodder ------------ @@ -534,6 +543,21 @@ class CustomDataDescriptor(object): return self return 42 +def _funky_classmethod(name, b, c, d, docstring=None): + """Generates a classmethod for a class from a template by filling out + some arguments.""" + def template(cls, a, b, c, d=4, e=5, f=6): + return a, b, c, d, e, f + if sys.version_info >= (2, 5): + from functools import partial + function = partial(template, b=b, c=c, d=d) + else: + def function(cls, a, e=5, f=6): + return template(a, b, c, d, e, f) + function.__name__ = name + function.__doc__ = docstring + return classmethod(function) + class Base(object): def inheritedmeth(self): """Inherited function.""" @@ -576,6 +600,11 @@ class Class(Base): mdocattr = StringIO() """should be documented as well - süß""" + roger = _funky_classmethod("roger", 2, 3, 4) + + moore = _funky_classmethod("moore", 9, 8, 7, + docstring="moore(a, e, f) -> happiness") + def __init__(self, arg): #: a documented instance attribute self.inst_attr_comment = None