2010-09-10 24 views
22

Estoy intentando utilizar Sphinx para documentar mi clase de Python. Yo por lo que usar autodoc:Python Sphinx autodoc y miembros decorados

.. autoclass:: Bus 
    :members: 

A pesar de que va a buscar correctamente las cadenas de documentación para mis métodos, los que están decoradas:

@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 

tienen también un prototipo incorrecto, como open(*args, **kwargs).

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

+1

La documentación en el stdlib y en Sphinx ambos parecen dar a entender que estás haciendo todo bien. :( –

+0

¿Has probado usar el [paquete decorador] (http://pypi.python.org/pypi/decorator) y poner '@ decorator' en' checkStale'? Tuve un problema similar al usar 'epydoc' con un decorado función. – bstpierre

+0

@bstpierre Supongo que el paquete de decorador no es parte de una distribución normal de Python? Me pregunto si es posible usarlo donde esté disponible, y de lo contrario retroceder a lo que tengo? –

Respuesta

14

Para ampliar mi comentario:

Have you tried using the decorator package and putting @decorator on checkStale? I had a similar issue using epydoc with a decorated function.

como lo pidió en su comentario, el paquete decorador no es parte de la biblioteca estándar.

Puede cae de nuevo utilizando el código de algo como lo siguiente (no probado):

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

This isn ' t exactamente un repliegue. Desafortunadamente el decorador y los functools.wraps tienen diferentes firmas, de lo contrario el método preferido sería 'try: from decorator import decorator como wraps; excepto ImportError: from functools import wraps'. –

+1

si agrego que funciona para sphinx pero algun tiempo s (por ejemplo, cuando ejecuto las pruebas) obtengo este error 'user_required() toma exactamente 1 argumento (2 dado)'. Básicamente, debería importar el devorador solo cuando Sphinx compila los documentos, de lo contrario, la otra función "falsa" ... ¿Alguna idea? – EsseTi

-2

ACTUALIZACIÓN: esto puede ser "imposible" hacer limpiamente porque esfinge utiliza objeto de código de la función para generar la firma de la función. Pero, dado que estás usando sphinx, hay una solución hacky que funciona.

Es hacky porque desactiva efectivamente el decorador mientras sphinx se está ejecutando, pero funciona, por lo que es una solución práctica.

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

pude segfault pitón por ir por el camino o tratando de cambiar en el co_varnames, co_nlocals, etc. miembros del objeto código de la función original, y al mismo tiempo atractiva, ya era demasiado complicado.

La siguiente solución, mientras que es un martillo pesado hacky, es también muy simple =)

El enfoque es el siguiente: cuando se ejecuta dentro de Sphinx, establece una variable de entorno de que el decorador puede comprobar. dentro del decorador, cuando se detecta sphinx, no realices ningún tipo de decoración, y en su lugar devuelve la función original.

Dentro de su esfinge conf.py:

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

Y entonces aquí es un módulo de ejemplo con un caso de prueba que muestra lo que podría ser:

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() 
+3

Creo que no se votó porque la pregunta se refería a la firma del método, no a los documentos. Además, la funcionalidad que describes es exactamente lo que proporciona el lib estándar de Python con http://docs.python.org/2/library/functools.html#functools.wraps – adam

+0

De todos modos, es insuficiente. –

+0

Gracias por la nota, debo haber leído mal la pregunta y no la he probado. La respuesta ha sido reemplazada por una solución que aborda las firmas de métodos. Lo verifiqué con un proyecto de esfinge local también (omitido por brevedad). ¡aclamaciones! – davvid

0

Si usted es particularmente inflexible acerca de no añadir otra dependencia He aquí un fragmento de código que funciona con el inspector regulares mediante la inyección en la cadena de documentación. Es bastante hackey y no es muy recomendable 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 
12

Tuve el mismo problema con el apio @task decorator.

También puede solucionar este problema en su caso mediante la adición de la firma de la función correcta en el archivo de primera, así:

.. autoclass:: Bus 
    :members: 

    .. automethod:: open(self) 
    .. automethod:: some_other_method(self, param1, param2) 

Será todavía documentar los miembros no decorador de forma automática.

Esto se menciona en la documentación de esfinge en http://sphinx-doc.org/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 autofunction 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) 
+1

Además de esta respuesta (¡lo cual es genial, gracias!) También tuve que excluir la función decorada usando ': exclude-members: funcname' para evitar que aparezca dos veces. – hayavuk

+0

Apio ahora incluye una extensión de Sphinx: ['apio.contrib.sphinx'] (http://docs.celeryproject.org/en/latest/reference/celery.contrib.sphinx.html), que puede documentar automáticamente las tareas. – 153957

Cuestiones relacionadas