Подтвердить что ты не робот

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 будет исправлять такие вещи.

4b9b3361

Ответ 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()