Python Sphinx autodoc y miembros decorados.

Estoy intentando usar Sphinx para documentar mi clase de Python. Lo hago usando autodoc:

.. autoclass:: Bus :members: 

Si bien obtiene correctamente las cadenas de documentación de mis métodos, los que están decorados:

  @checkStale def open(self): """ Some docs. """ # Code 

con @checkStale siendo

 def checkStale(f): @wraps(f) def newf(self, *args, **kwargs): if self._stale: raise Exception return f(self, *args, **kwargs) return newf 

tener un prototipo incorrecto, como open(*args, **kwargs) .

¿Cómo puedo arreglar esto? Tenía la impresión de que usar @wraps solucionaría este tipo de cosas.

Para ampliar mi comentario:

¿Ha intentado usar el paquete decorator y poner @decorator en checkStale? Tuve un problema similar al usar epydoc con una función decorada.

Como pidió en su comentario, el paquete decorador no forma parte de la biblioteca estándar.

Puedes retroceder usando un código similar al siguiente (sin probar):

 try: from decorator import decorator except ImportError: # No decorator package available. Create a no-op "decorator". def decorator(f): return f 

Tuve el mismo problema con el decorador @task de apio.

También puede solucionar este problema en su caso agregando la firma de función correcta a su primer archivo, como esto:

 .. autoclass:: Bus :members: .. automethod:: open(self) .. automethod:: some_other_method(self, param1, param2) 

Seguirá documentando los miembros no decoradores automáticamente.

Esto se menciona en la documentación de la esfinge en http://www.sphinx-doc.org/en/master/ext/autodoc.html#directive-automodule – busque “Esto es útil si la firma del método está oculta por un decorador “.

En mi caso, tuve que usar el funcionamiento automático para especificar la firma de mis tareas de apio en el módulo tasks.py de una aplicación de django:

 .. automodule:: django_app.tasks :members: :undoc-members: :show-inheritance: .. autofunction:: funct1(user_id) .. autofunction:: func2(iterations) 

Agregado en la versión 1.1, ahora puede anular la firma del método proporcionando un valor personalizado en la primera línea de su cadena de documentos.

http://sphinx-doc.org/ext/autodoc.html#confval-autodoc_docstring_signature

 @checkStale def open(self): """ open() Some docs. """ # Code 

Si está particularmente convencido de no agregar otra dependencia, aquí hay un fragmento de código que funciona con el inspector regular inyectando en la cadena de documentos. Es bastante difícil y no se recomienda realmente a menos que haya buenas razones para no agregar otro módulo, pero aquí está.

 # inject the wrapped functions signature at the top of a docstring args, varargs, varkw, defaults = inspect.getargspec(method) defaults = () if defaults is None else defaults defaults = ["\"{}\"".format(a) if type(a) == str else a for a in defaults] l = ["{}={}".format(arg, defaults[(idx+1)*-1]) if len(defaults)-1 >= idx else arg for idx, arg in enumerate(reversed(list(args)))] if varargs: allargs.append('*' + varargs) if varkw: allargs.append('**' + varkw) doc = "{}({})\n{}".format(method.__name__, ', '.join(reversed(l)), method.__doc__) wrapper.__doc__ = doc 

ACTUALIZACIÓN: esto puede ser “imposible” de hacer limpiamente porque la esfinge usa el objeto de código de la función para generar su firma de función. Pero, ya que estás usando la esfinge, hay una solución pirata que funciona.

Es intrépido porque efectivamente desactiva el decorador mientras la esfinge se está ejecutando, pero funciona, por lo que es una solución práctica.

Al principio, seguí la ruta de la construcción de un nuevo types.CodeType objeto de func_code Código, para reemplazar el miembro del objeto de código de código de func_code del envoltorio, que es lo que la esfinge usa cuando genera las firmas.

Pude segfault python al ir por la ruta o al intentar intercambiar los co_varnames , co_nlocals , etc. miembros del objeto de código de la función original, y aunque era atractivo, era demasiado complicado.

La siguiente solución, si bien es un martillo pesado pirateado, también es muy simple =)

El enfoque es el siguiente: cuando se ejecuta dentro de la esfinge, establezca una variable de entorno que el decorador pueda verificar. En el interior del decorador, cuando se detecta la esfinge, no haga ninguna decoración y devuelva la función original.

Dentro de tu esfinge conf.py:

 import os os.environ['SPHINX_BUILD'] = '1' 

Y luego, aquí hay un módulo de ejemplo con un caso de prueba que muestra cómo podría verse:

 import functools import os import types import unittest SPHINX_BUILD = bool(os.environ.get('SPHINX_BUILD', '')) class StaleError(StandardError): """Custom exception for staleness""" pass def check_stale(f): """Raise StaleError when the object has gone stale""" if SPHINX_BUILD: # sphinx hack: use the original function when sphinx is running so that the # documentation ends up with the correct function signatures. # See 'SPHINX_BUILD' in conf.py. return f @functools.wraps(f) def wrapper(self, *args, **kwargs): if self.stale: raise StaleError('stale') return f(self, *args, **kwargs) return wrapper class Example(object): def __init__(self): self.stale = False self.value = 0 @check_stale def get(self): """docstring""" return self.value @check_stale def calculate(self, a, b, c): """docstring""" return self.value + a + b + c class TestCase(unittest.TestCase): def test_example(self): example = Example() self.assertEqual(example.get(), 0) example.value = 1 example.stale = True self.assertRaises(StaleError, example.get) example.stale = False self.assertEqual(example.calculate(1, 1, 1), 4) if __name__ == '__main__': unittest.main()