Commits

Łukasz Langa committed 80c9fd7

Support for autodocumenting partial functions.

Comments (0)

Files changed (2)

sphinx/ext/autodoc.py

             # 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)
                (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
                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)
     AutoDirective._registry[cls.objtype] = cls
 
 
+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'.format(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 setup(app):
     app.add_autodocumenter(ModuleDocumenter)
     app.add_autodocumenter(ClassDocumenter)

tests/test_autodoc.py

     :license: BSD, see LICENSE for details.
 """
 
+import sys
+from StringIO import StringIO
+
 from util import *
 
 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
                    ('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'))
                   '   .. 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()',
         '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 ------------
             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."""
     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