The “Sphinx” decorators

Overview

Developers use the Deprecated library to decorate deprecated functions or classes. This is very practical, but you know that this library does more: you can also document your source code! How? It’s very simple: instead of using the “classic” decorator, you can use one of the “Sphinx” decorators.

The “Sphinx” decorators have the same function as the “classic” decorator but also allow you to add Sphinx directives in your functions or classes documentation (inside the docstring).

Attention

In Python 3.3 and previous versions, the docstring of a class is immutable [1], this means that you cannot use the “Sphinx” decorators. Naturally, this limitation does not exist in Python 3.4 and above.

What is a Sphinx directive?

Sphinx is a tool that makes it easy to create intelligent and beautiful documentation. This tool uses the reStructuredText (or Markdown) syntax to generate the documentation in different formats, the most common being HTML. Developers generally use this syntax to document the source code of their applications.

Sphinx offers several directives allowing to introduce a text block with a predefined role. Among all the directives, the ones that interest us are those related to the functions (or classes) life cycle, namely: versionadded, versionchanged and deprecated.

In the following example, the mean() function can be documented as follows:

def mean(values):
    """
    Compute the arithmetic mean (“average”) of values.

    :type  values: typing.List[float]
    :param values: List of floats
    :return: Mean of values.

    .. deprecated:: 2.5.0
       Since Python 3.4, you can use the standard function :func:`statistics.mean`.
    """
    return sum(values) / len(values)

Therefore, the “Sphinx” decorators allow you to add a Sphinx directive to your functions or classes documentation. In the case of the deprecated directive, it obviously allows you to emit a DeprecationWarning warning.

Using the “Sphinx” decorators

The previous example can be writen using a “Sphinx” decorator:

from deprecated.sphinx import deprecated


@deprecated(
    reason="""Since Python 3.4, you can use the standard function :func:`statistics.mean`.""",
    version="2.5.0",
)
def mean(values):
    """
    Compute the arithmetic mean (“average”) of values.

    :type  values: typing.List[float]
    :param values: List of floats
    :return: Mean of values.
    """
    return sum(values) / len(values)

You can see the generated documentation with this simple call:

from calc_mean_deco import mean

print(mean.__doc__)

The documentation of the mean() function looks like this:

Compute the arithmetic mean (“average”) of values.

:type  values: typing.List[float]
:param values: List of floats
:return: Mean of values.

.. deprecated:: 2.5.0
   Since Python 3.4, you can use the standard function
   :func:`statistics.mean`.

More elaborate example

The Deprecated library offers you 3 decorators:

  • deprecated(): insert a deprecated directive in docstring, and emit a warning on each call.
  • versionadded(): insert a versionadded directive in docstring, don’t emit warning.
  • versionchanged(): insert a versionchanged directive in docstring, don’t emit warning.

The decorators can be combined to reflect the life cycle of a function:

  • When it is added in your API, with the @versionadded decorator,
  • When it has an important change, with the @versionchanged decorator,
  • When it is deprecated, with the @deprecated decorator.

The example bellow illustrate this life cycle:

# coding: utf-8
from deprecated.sphinx import deprecated
from deprecated.sphinx import versionadded
from deprecated.sphinx import versionchanged


@deprecated(
    reason="""
    This is deprecated, really. So you need to use another function.
    But I don\'t know which one.

       - The first,
       - The second.

    Just guess!
    """,
    version='0.3.0',
)
@versionchanged(
    reason='Well, I add a new feature in this function. '
           'It is very useful as you can see in the example below, so try it. '
           'This is a very very very very very long sentence.',
    version='0.2.0',
)
@versionadded(reason='Here is my new function.', version='0.1.0')
def successor(n):
    """
    Calculate the successor of a number.

    :param n: a number
    :return: number + 1
    """
    return n + 1

To see the result, you can use the builtin function help() to display a formatted help message of the successor() function. It is something like this:

Help on function successor in module __main__:

successor(n)
    Calculate the successor of a number.

    :param n: a number
    :return: number + 1


    .. versionadded:: 0.1.0
       Here is my new function.


    .. versionchanged:: 0.2.0
       Well, I add a new feature in this function. It is very useful as
       you can see in the example below, so try it. This is a very very
       very very very long sentence.


    .. deprecated:: 0.3.0
       This is deprecated, really. So you need to use another function.
       But I don't know which one.

          - The first,
          - The second.

       Just guess!

Note

Decorators must be writen in reverse order: recent first, older last.

Building the documentation

The easiest way to build your API documentation is to use the autodoc plugin. The directives like automodule, autoclass, autofunction scan your source code and generate the documentation from your docstrings.

Usually, the first thing that we need to do is indicate where the Python package that contains your source code is in relation to the conf.py file.

But here, that will not work! The reason is that your modules must be imported during build: the Deprecated decorators must be interpreted.

So, to build the API documentation of your project with Sphinx you need to setup a virtualenv, and install Sphinx, external themes and/or plugins and also your project. Nowadays, this is the right way to do it.

For instance, you can configure a documentation building task in your tox.ini file, for instance:

[testenv:docs]
basepython = python
deps =
    sphinx
commands =
    sphinx-build -b html -d {envtmpdir}/doctrees docs/source/ {envtmpdir}/html

Hint

You can see a sample implementation of Sphinx directives in the demo project Deprecated-Demo.Sphinx.

Footnotes

[1]See Issue 12773: classes should have mutable docstrings.