2010-09-10 9 views
22

Próbuję użyć Sphinx do udokumentowania mojej klasy Pythona. Mam więc korzystanie autodoktora:Autodoc i elementy dekoracyjne Python Sphinx

.. autoclass:: Bus 
    :members: 

Chociaż poprawnie pobiera docstrings dla moich metod, te, które są urządzone:

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

z @checkStale będąc

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

mieć nieprawidłową prototyp, na przykład open(*args, **kwargs).

Jak mogę to naprawić? Miałem wrażenie, że używanie tego rodzaju rzeczy będzie naprawiać za pomocą @wraps.

+1

Dokumentacja w stdlib i Sfinksa oba wydają się sugerować że robisz wszystko dobrze. :( –

+0

Czy próbowałeś użyć [pakietu dekoratora] (http://pypi.python.org/pypi/decorator) i umieszczając '@ decorator' na' checkStale'? Miałem podobny problem używając 'epydoc' z dekorowanym funkcja: – bstpierre

+0

@ bstpierre Rozumiem, że pakiet dekoratora nie jest częścią normalnej dystrybucji Pythona? Zastanawiam się, czy możliwe jest użycie go tam, gdzie jest on dostępny, i w inny sposób może zastąpić to, co mam? –

Odpowiedz

14

Aby rozwinąć na mój komentarz:

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

Ponieważ pytasz w swoim komentarzu, dekorator pakiet nie jest częścią biblioteki standardowej.

Można spaść z powrotem za pomocą kodu coś jak następuje (niesprawdzone):

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

To nie jest " t. Niestety, decorator i functools.wraps mają różne sygnatury, w przeciwnym razie preferowaną metodą byłoby 'try: od dekoratora importu dekoratora jako wrapów, z wyjątkiem ImportError: from functools import wraps'. –

+1

jeśli dodam, że działa to dla sfinksa, ale czasami s (np. gdy uruchamiam testy) otrzymuję ten błąd 'user_required() ma dokładnie 1 argument (2 dane)'. w zasadzie powinienem importować devorator tylko wtedy, gdy sfinks kompiluje dokumenty, w przeciwnym razie inna "fałszywa" funkcja .. jakiś pomysł? – EsseTi

-2

UPDATE: może to być „niemożliwe”, aby zrobić czysto ponieważ sfinks używa kodu wynikowego funkcja do generowania swój podpis funkcji. Ale ponieważ używasz sfinksa, istnieje hacky obejście, które działa.

Jest hacky, ponieważ skutecznie wyłącza dekorator, gdy sfinks działa, ale działa, więc jest to praktyczne rozwiązanie.

Najpierw poszedłem na trasę budowy nowego obiektu types.CodeType, aby zastąpić element kodu obiektu func_code opakowania, który jest używany przez sfinks podczas generowania sygnatur.

Udało mi się zmusić do pęknięcia pytona, pokonując trasę lub próbując zamienić elementy obiektu kodu z pierwotnej funkcji, i jednocześnie odwołując się, było to zbyt skomplikowane.

Poniżej rozwiązanie, podczas gdy jest to hacky ciężki młot, jest również bardzo proste =)

Podejście jest następujący: podczas uruchamiania wewnątrz sfinksa, ustawić zmienną środowiskową, że dekorator można sprawdzić. wewnątrz dekoratora, gdy sfinks zostanie wykryty, nie rób w ogóle dekoracji, a zamiast tego zwróć oryginalną funkcję.

Wewnątrz sfinks conf.py:

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

A potem tu jest modułem przykład z testu, który pokazuje, co to może wyglądać:

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

Uważam, że przegłosowano, ponieważ pytanie dotyczyło podpisu metody, a nie dokumentów. Dodatkowo, opisywana przez ciebie funkcjonalność jest dokładnie tym, co zapewnia standardowa biblioteka Python z http://docs.python.org/2/library/functools.html#functools.wraps – adam

+0

. To i tak nie wystarcza. –

+0

Dzięki za notatkę, musiałem źle odczytać pytanie i nie przetestowałem go. Odpowiedź została zastąpiona rozwiązaniem, które rozwiązuje sygnatury metod. Zweryfikowałem to również z lokalnym projektem sfinksowym (pominięto dla zwięzłości). Twoje zdrowie! – davvid

0

Jeśli jesteś szczególnie nieugięty o nie dodawanie tutaj kolejną zależność jest to fragment kodu, który działa przy regularnym inspektora poprzez wstrzyknięcie do docstring. Jest dość hackey i niezbyt polecany, chyba że istnieją dobre powody, by nie dodawać kolejnego modułu, ale oto jest.

# 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

Miałem ten sam problem z dekoratorem selera @task.

Można również naprawić w przypadku dodając prawidłowego podpisu funkcji do pliku rst, tak:

.. autoclass:: Bus 
    :members: 

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

Będzie on nadal dokumentować członków non-dekoratorów automatycznie.

Jest to wspomniane w dokumentacji sfinksa pod numerem http://sphinx-doc.org/ext/autodoc.html#directive-automodule - jest to przydatne, gdy podpis z metody jest ukryty przez dekoratora. "

W moim przypadku musiałem użyć autofunction określić podpis moich zadań selera w tasks.py modułu app django:

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

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

zawiera teraz rozszerzenie Sphinx: ['celery.contrib.sphinx'] (http://docs.celeryproject.org/en/latest/reference/celery.contrib.sphinx.html), które może automatycznie dokumentować zadania. – hayavuk

Powiązane problemy