Python Sphinx autodoc и украшенные элементы
Я пытаюсь использовать Sphinx для документирования моего класса Python. Я использую autodoc:
.. autoclass:: Bus
:members:
Пока он правильно извлекает docstrings для моих методов, те, которые украшены:
@checkStale
def open(self):
"""
Some docs.
"""
# Code
с @checkStale
def checkStale(f):
@wraps(f)
def newf(self, *args, **kwargs):
if self._stale:
raise Exception
return f(self, *args, **kwargs)
return newf
имеют неправильный прототип, например open(*args, **kwargs)
.
Как я могу это исправить? У меня создалось впечатление, что использование @wraps
будет исправлять такие вещи.
Ответы
Ответ 1
Развернуть мой комментарий:
Пробовали ли вы использовать пакет декоратора и поставить @decorator на checkStale? я имел аналогичная проблема с использованием epydoc с украшенной функцией.
Как вы сказали в своем комментарии, пакет декоратора не входит в стандартную библиотеку.
Вы можете вернуться с помощью кода, например, следующего (непроверенного):
try:
from decorator import decorator
except ImportError:
# No decorator package available. Create a no-op "decorator".
def decorator(f):
return f
Ответ 2
У меня была такая же проблема с декоратором celery @task.
Вы также можете исправить это в своем случае, добавив правильную подпись функции в свой первый файл, например:
.. autoclass:: Bus
:members:
.. automethod:: open(self)
.. automethod:: some_other_method(self, param1, param2)
Он по-прежнему будет документировать элементы не-декоратора автоматически.
Это упоминается в документации sphinx по адресу http://sphinx-doc.org/ext/autodoc.html#directive-automodule - поиск "Это полезно, если подпись из метода скрыта декоратор".
В моем случае мне пришлось использовать autofunction, чтобы указать подпись моих задач сельдерея в модуле tasks.py приложения django:
.. automodule:: django_app.tasks
:members:
:undoc-members:
:show-inheritance:
.. autofunction:: funct1(user_id)
.. autofunction:: func2(iterations)
Ответ 3
Добавленный в версию 1.1 теперь вы можете переопределить подпись метода, предоставив пользовательское значение в первой строке вашей docstring.
http://sphinx-doc.org/ext/autodoc.html#confval-autodoc_docstring_signature
@checkStale
def open(self):
"""
open()
Some docs.
"""
# Code
Ответ 4
Если вы особенно категоричны в том, что не добавляете другую зависимость здесь, фрагмент кода, который работает с обычным инспектором, вставляя в docstring. Это довольно хаки и не рекомендуется, если нет веских причин не добавлять еще один модуль, но вот он.
# 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
Ответ 5
UPDATE: это может быть "невозможно", потому что sphinx использует объект кода функции для генерации своей сигнатуры функции. Но, поскольку вы используете сфинкс, есть хакерское обходное решение, которое действительно работает.
Он взломан, потому что он эффективно отключает декоратор, пока работает sphinx, но он работает, поэтому это практическое решение.
Сначала я спустился по пути создания нового объекта types.CodeType
, чтобы заменить член-объект оболочки func_code
, который используется сфинксом при создании подписей.
Мне удалось выполнить segfault python, спустившись по маршруту или попытавшись поменять местами в объекте co_varnames
, co_nlocals
и т.д. объекта кода из исходной функции, и при апелляции он был слишком сложным.
Следующее решение, в то время как это тяжелый тяжелый молот, также очень просто =)
Подход выглядит следующим образом: при запуске внутри sphinx задайте переменную среды, которую может проверить декоратор. внутри декоратора, когда обнаруживается sphinx, вообще не занимайтесь декорированием и вместо этого возвращайте исходную функцию.
Внутри вашего sphinx conf.py:
import os
os.environ['SPHINX_BUILD'] = '1'
И вот примерный модуль с тестовым примером, который показывает, как он может выглядеть:
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()