2010-09-10 2 views
22

Ich versuche Sphinx zu verwenden, um meine Python-Klasse zu dokumentieren. Ich mit so Autodoc:Python Sphinx Autodoc und dekorierte Mitglieder

.. autoclass:: Bus 
    :members: 

Während es richtig die Docstrings für meine Methoden abruft, jene, die eingerichtet:

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

mit @checkStale sein

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

einen falschen Prototyp haben, wie open(*args, **kwargs).

Wie kann ich das beheben? Ich hatte den Eindruck, dass die Verwendung von @wraps diese Art von Sache beheben würde.

+1

Die Dokumentation in der stdlib und in Sphinx beide scheinen zu implizieren dass du alles richtig machst. :( –

+0

Haben Sie versucht, mit dem [Decorator-Paket] (http://pypi.python.org/pypi/decorator) und '@ Decorator' auf' checkStale' zu ​​setzen? Ich hatte ein ähnliches Problem mit 'epydoc' mit einem dekoriert Funktion – bstpierre

+0

@bstpierre Ich nehme an, dass das Dekorator-Paket nicht Teil einer normalen Python-Distribution ist? Ich frage mich, ob es möglich ist, es wo verfügbar zu verwenden, und sonst Fallback zu dem, was ich habe? –

Antwort

14

auf meinem Kommentar erweitern:

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

Wie Sie in Ihrem Kommentar gefragt, ist das Dekorateur Paket der Standardbibliothek nicht teil.

können Sie fallen zurück Code etwas mit wie folgt (nicht getestet):

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

Das isn ' t genau ein Fallback Leider haben Decorator und functools.wraps unterschiedliche Signaturen, sonst wäre die bevorzugte Methode "Versuch: vom Decorator Dekorator als Wraps importieren; außer ImportError: von functools Import Wraps." –

+1

wenn ich hinzufüge, dass es für sphinx aber funktioniert irgendwann s (z. B., wenn ich die Tests ausführen) bekomme ich diesen Fehler 'user_required() nimmt genau 1 Argument (2 gegeben)'. grundsätzlich sollte ich den devoktor nur importieren, wenn sphinx die docs kompiliert sonst die andere "gefälschte" funktion .. keine idee? – EsseTi

-2

UPDATE: das kann „unmöglich“ sein sauber zu tun, weil Sphinx der Code des Objekts Funktion verwendet die Funktion Signatur zu erzeugen. Aber da Sie Sphinx verwenden, gibt es einen hacky Workaround, der funktioniert.

Es ist hacky, weil es den Dekorateur effektiv deaktiviert, während sphinx läuft, aber es funktioniert, also ist es eine praktische Lösung.

Zuerst ging ich den Weg der Konstruktion eines neuen types.CodeType Objekts, um das func_code Code-Objekt-Mitglied des Wrappers zu ersetzen, was Sphinx verwendet, wenn die Signaturen erzeugt werden.

konnte ich Python segfault durch die Strecke gehen oder versuchen, in den co_varnames, co_nlocals usw. Mitglieder des Codeobjekts aus der ursprünglichen Funktion zu tauschen, und während ansprechend, es war zu kompliziert.

Die folgende Lösung, während es ein Hacky schwerer Hammer ist, ist auch sehr einfach =)

Der Ansatz ist wie folgt: Wenn innerhalb Sphinx ausgeführt wird, eine Umgebungsvariable festgelegt, dass der Dekorateur überprüfen. Im Inneren des Dekorators, wenn Sphinx erkannt wird, verziere keine Dekoration und gebe stattdessen die ursprüngliche Funktion zurück.

In Ihrem Sphinx conf.py:

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

Und dann ist hier ein Beispiel-Modul mit einem Testfall, der zeigt, wie es aussehen könnte:

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

Ich glaube, dass Sie abgelehnt werden, weil die Frage in Bezug auf die Methodensignatur gestellt wurde, nicht die Dokumente. Darüber hinaus ist die von Ihnen beschriebene Funktionalität genau das, was die Python-Standardbibliothek mit http://docs.python.org/2/library/functools.html#functools.wraps – adam

+0

bietet. –

+0

Danke für den Hinweis, ich habe die Frage falsch gelesen und nicht getestet. Die Antwort wurde durch eine Lösung ersetzt, die die Methodensignaturen adressiert. Ich habe es auch mit einem lokalen Sphinx-Projekt verifiziert (der Kürze halber weggelassen). Prost! – davvid

0

Wenn Sie besonders unnachgiebig sind dabei kein Code-Schnipsel eine weitere Abhängigkeit hier hinzufügen, die durch die Injektion in die docstring mit dem regulären Inspektor arbeitet. Es ist ziemlich hackey und nicht wirklich empfohlen, es sei denn es gibt gute Gründe, kein anderes Modul hinzuzufügen, aber hier ist es.

# 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

Ich hatte das gleiche Problem mit dem Sellerie @task Decorator.

Sie können auch in Ihrem Fall dieses Problem beheben, indem die korrekte Funktion Signatur Ihre erste Datei hinzufügen, wie folgt aus:

.. autoclass:: Bus 
    :members: 

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

Es wird dokumentiert noch die Nicht-Dekorateur Mitglieder automatisch.

Dies wird in der Sphinx-Dokumentation unter http://sphinx-doc.org/ext/autodoc.html#directive-automodule erwähnt - Suche nach "Dies ist nützlich, wenn die Signatur der Methode von einem Dekorator ausgeblendet wird."

In meinem Fall hatte ich Autofunktion verwenden, um die Unterschrift meiner Sellerie Aufgaben im tasks.py Modul einer django App angeben:

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

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

Zusätzlich zu dieser Antwort (was super ist, danke!) Musste ich auch die dekorierte Funktion mit ': exclude-members: funkname 'ausschliessen, um zu verhindern, dass sie doppelt erscheint. – hayavuk

+0

Sellerie jetzt enthält eine Sphinx-Erweiterung: ["sellery.contrib.sphinx"] (http://docs.selleryproject.org/en/latest/reference/sellery.contrib.sphinx.html), die die Aufgaben automatisch dokumentieren kann. – 153957