2010-09-10 15 views
22

Sto tentando di usare Sphinx per documentare la mia classe Python. Lo faccio utilizzando autodoc:Python Sphinx autodoc e membri decorati

.. autoclass:: Bus 
    :members: 

Mentre recupera correttamente i docstrings per i miei metodi, quelli che sono decorate:

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

con @checkStale essendo

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

hanno un prototipo non corretta, ad esempio open(*args, **kwargs).

Come posso risolvere questo? Avevo l'impressione che l'uso di @wraps risolvesse questo genere di cose.

+1

La documentazione nel stdlib e Sfinge entrambi sembrano implicare che stai facendo tutto bene. :( –

+0

Hai provato a usare il [pacchetto decoratore] (http://pypi.python.org/pypi/decorator) e a mettere '@ decorator' su' checkStale'? Ho avuto un problema simile usando 'epydoc' con una decorata – bstpierre

+0

@bstpierre Suppongo che il pacchetto decoratore non faccia parte di una normale distribuzione Python? Mi chiedo se sia possibile usarlo dove disponibile, e altrimenti rimando a quello che ho? –

risposta

14

Per espandere il mio commento:

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

Come hai chiesto nel tuo commento, il pacchetto decoratore non fa parte della libreria standard.

si può ripiegare utilizzando il codice simile al seguente (non testato):

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

Questo non è Sfortunatamente decorator e functools.wraps hanno diverse firme, altrimenti il ​​metodo preferito sarebbe 'try: from decorator import decorator come wraps, tranne ImportError: from functools import wraps'. –

+1

se aggiungo che funziona per sfinge ma a volte s (ad esempio, quando eseguo i test) ottengo questo errore 'user_required() richiede esattamente 1 argomento (2 dato)'. in pratica dovrei importare il devoror solo quando la sfinge compila i documenti altrimenti l'altra funzione "finta" ... qualche idea? – EsseTi

-2

UPDATE: questo può essere "impossibile" per fare in modo pulito perché sfinge utilizza codice oggetto della funzione di generare la sua firma la funzione. Ma, dal momento che stai usando la sfinge, c'è una soluzione hacky che funziona.

È hacky perché disattiva efficacemente il decoratore mentre la sfinge è in esecuzione, ma funziona, quindi è una soluzione pratica.

Inizialmente ho intrapreso la strada della costruzione di un nuovo oggetto types.CodeType, per sostituire il membro dell'oggetto codice func_code del wrapper, che è ciò che la sfinge utilizza durante la generazione delle firme.

Sono stato in grado di segfault python scendendo lungo il percorso o tentando di scambiare i membri co_varnames, co_nlocals, ecc. Dell'oggetto codice dalla funzione originale, e mentre accattivante, era troppo complicato.

La seguente soluzione, mentre è un pesante martello hacky, è anche molto semplice =)

L'approccio è il seguente: durante l'esecuzione all'interno sphinx, impostare una variabile di ambiente che il decoratore può controllare. all'interno del decoratore, quando viene rilevata la sfinge, non eseguire alcuna decorazione e restituire la funzione originale.

All'interno della vostra sfinge conf.py:

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

E poi qui è un modulo di esempio con un banco di prova che mostra quello che potrebbe essere simile:

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

Credo che tu sia stato eliminato perché la domanda riguardava la firma del metodo, non i documenti. Inoltre, la funzionalità che descrivi è esattamente ciò che viene fornito dalla lib di Python standard con http://docs.python.org/2/library/functools.html#functools.wraps – adam

+0

In ogni caso non è sufficiente. –

+0

Grazie per la nota, devo aver letto male la domanda e non l'ho testata. La risposta è stata sostituita con una soluzione che affronta le firme del metodo. L'ho verificato anche con un progetto sfinge locale (omesso per brevità). Saluti! – davvid

0

Se siete particolarmente risoluto circa non aggiungere un altro dipendenza ecco un frammento di codice che funziona con l'ispettore regolare da iniettare nella docstring. È abbastanza hackey e non è veramente consigliato a meno che non ci siano buone ragioni per non aggiungere un altro modulo, ma eccolo qui.

# 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

Ho avuto lo stesso problema con il decoratore @task sedano.

È inoltre possibile risolvere questo problema nel tuo caso con l'aggiunta della firma della funzione corretto per il file prima, in questo modo:

.. autoclass:: Bus 
    :members: 

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

Sarà ancora documentare automaticamente i membri non-decoratore.

Questo è menzionato nella documentazione della sfinge al http://sphinx-doc.org/ext/autodoc.html#directive-automodule - cerca "Questo è utile se la firma del metodo è nascosta da un decoratore".

Nel mio caso, ho dovuto usare autofunzione per specificare la firma dei miei compiti di sedano nel modulo tasks.py di un'applicazione django:

.. automodule:: django_app.tasks 
    :members: 
    :undoc-members: 
    :show-inheritance: 

    .. autofunction:: funct1(user_id) 
    .. autofunction:: func2(iterations) 
+1

Oltre a questa risposta (che è grandiosa, grazie!) Ho dovuto anche escludere la funzione decorata usando ': exclude-members: funcname' per evitare che appaia due volte. – hayavuk

+0

Sedano ora include un'estensione Sfinge: ['celery.contrib.sphinx'] (http://docs.celeryproject.org/en/latest/reference/celery.contrib.sphinx.html), che può automaticamente documentare le attività. – 153957

Problemi correlati