python - automodule - autodoc sphinx
Python Sphinx autodoc y miembros decorados. (5)
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 utiliza la esfinge al generar 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, aunque 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()
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.
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
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 lo 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
Si está particularmente convencido de no agregar otra dependencia, aquí hay un fragmento de código que funciona con el inspector regular insertando 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
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)
Todavía documentará 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)