-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New Sphinx tutorial, part III #9534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
tk0miya
merged 27 commits into
sphinx-doc:4.x
from
astrojuanlu:new-tutorial-describing-code
Oct 9, 2021
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
1a1e45f
Add cross reference to role definitions
astrojuanlu 2c1fa83
Add tutorial section on documenting Python objects
astrojuanlu a5c6845
Add section on Python code preparation
astrojuanlu 8334fd9
Add section on doctests
astrojuanlu 8573c28
Add section on automatic documentation generation
astrojuanlu a606db0
Add section on autosummary
astrojuanlu c721d17
Style improvement
astrojuanlu 722629b
Rephrase explanation on square brackets
astrojuanlu da93cfc
Reword primary domains
astrojuanlu d505635
Be more explicit on automatic xref generation
astrojuanlu 878478e
Simplify doctest section
astrojuanlu 70bc61f
Include explanation about doctests
astrojuanlu 32f2f90
Simplify installation
astrojuanlu 2755333
Add connections to the next sections
astrojuanlu 53104ca
Add reference to tutorial TOC
astrojuanlu 5daf336
Style improvements
astrojuanlu 91a605c
Highlight only function docstring
astrojuanlu d7e525b
Insist that automatic is better
astrojuanlu 9aa9d0e
Typo
astrojuanlu c2847c2
Improve text of some links
astrojuanlu 87b8a0b
Remove square brackets
astrojuanlu 1677f41
Point readers to info field lists instead
astrojuanlu ccc95ea
Amend explanation on the default-domain directive
astrojuanlu 9a8d7ae
Use stable Sphinx version
astrojuanlu 02119ab
Fix line width
astrojuanlu ae3eab2
Get function signature right since the beginning
astrojuanlu 4920e4e
Explain that there are several domains
astrojuanlu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| Automatic documentation generation from code | ||
| ============================================ | ||
|
|
||
| In the :ref:`previous section <tutorial-describing-objects>` of the tutorial | ||
| you manually documented a Python function in Sphinx. However, the description | ||
| was out of sync with the code itself, since the function signature was not | ||
| the same. Besides, it would be nice to reuse `Python | ||
| docstrings <https://www.python.org/dev/peps/pep-0257/#what-is-a-docstring>`_ | ||
| in the documentation, rather than having to write the information in two | ||
| places. | ||
|
|
||
| Fortunately, :doc:`the autodoc extension </usage/extensions/autodoc>` provides this | ||
| functionality. | ||
|
|
||
| Reusing signatures and docstrings with autodoc | ||
| ---------------------------------------------- | ||
|
|
||
| To use autodoc, first add it to the list of enabled extensions: | ||
|
|
||
| .. code-block:: python | ||
| :caption: docs/source/conf.py | ||
| :emphasize-lines: 4 | ||
|
|
||
| extensions = [ | ||
| 'sphinx.ext.duration', | ||
| 'sphinx.ext.doctest', | ||
| 'sphinx.ext.autodoc', | ||
| ] | ||
|
|
||
| Next, move the content of the ``.. py:function`` directive to the function | ||
| docstring in the original Python file, as follows: | ||
|
|
||
| .. code-block:: python | ||
| :caption: lumache.py | ||
| :emphasize-lines: 2-11 | ||
|
|
||
| def get_random_ingredients(kind=None): | ||
| """ | ||
| Return a list of random ingredients as strings. | ||
|
|
||
| :param kind: Optional "kind" of ingredients. | ||
| :type kind: list[str] or None | ||
| :raise lumache.InvalidKindError: If the kind is invalid. | ||
| :return: The ingredients list. | ||
| :rtype: list[str] | ||
|
|
||
| """ | ||
| return ["shells", "gorgonzola", "parsley"] | ||
|
|
||
| Finally, replace the ``.. py:function`` directive from the Sphinx documentation | ||
| with :rst:dir:`autofunction`: | ||
|
|
||
| .. code-block:: rst | ||
| :caption: docs/source/usage.rst | ||
| :emphasize-lines: 3 | ||
|
|
||
| you can use the ``lumache.get_random_ingredients()`` function: | ||
|
|
||
| .. autofunction:: lumache.get_random_ingredients | ||
|
|
||
| If you now build the HTML documentation, the output will be the same! | ||
astrojuanlu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| With the advantage that it is generated from the code itself. | ||
| Sphinx took the reStructuredText from the docstring and included it, | ||
| also generating proper cross-references. | ||
|
|
||
| You can also autogenerate documentation from other objects. For example, add | ||
| the code for the ``InvalidKindError`` exception: | ||
|
|
||
| .. code-block:: python | ||
| :caption: lumache.py | ||
|
|
||
| class InvalidKindError(Exception): | ||
| """Raised if the kind is invalid.""" | ||
| pass | ||
|
|
||
| And replace the ``.. py:exception`` directive with :rst:dir:`autoexception` | ||
| as follows: | ||
|
|
||
| .. code-block:: rst | ||
| :caption: docs/source/usage.rst | ||
| :emphasize-lines: 4 | ||
|
|
||
| or ``"veggies"``. Otherwise, :py:func:`lumache.get_random_ingredients` | ||
| will raise an exception. | ||
|
|
||
| .. autoexception:: lumache.InvalidKindError | ||
|
|
||
| And again, after running ``make html``, the output will be the same as before. | ||
|
|
||
| Generating comprehensive API references | ||
| --------------------------------------- | ||
|
|
||
| While using ``sphinx.ext.autodoc`` makes keeping the code and the documentation | ||
| in sync much easier, it still requires you to write an ``auto*`` directive | ||
| for every object you want to document. Sphinx provides yet another level of | ||
| automation: the :doc:`autosummary </usage/extensions/autosummary>` extension. | ||
|
|
||
| The :rst:dir:`autosummary` directive generates documents that contain all the | ||
| necessary ``autodoc`` directives. To use it, first enable the autosummary | ||
| extension: | ||
|
|
||
| .. code-block:: python | ||
| :caption: docs/source/conf.py | ||
| :emphasize-lines: 5 | ||
|
|
||
| extensions = [ | ||
| 'sphinx.ext.duration', | ||
| 'sphinx.ext.doctest', | ||
tk0miya marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 'sphinx.ext.autodoc', | ||
| 'sphinx.ext.autosummary', | ||
| ] | ||
|
|
||
| Next, create a new ``api.rst`` file with these contents: | ||
|
|
||
| .. code-block:: rst | ||
| :caption: docs/source/api.rst | ||
|
|
||
| API | ||
| === | ||
|
|
||
| .. autosummary:: | ||
| :toctree: generated | ||
|
|
||
| lumache | ||
|
|
||
| Remember to include the new document in the root toctree: | ||
|
|
||
| .. code-block:: rst | ||
| :caption: docs/source/index.rst | ||
| :emphasize-lines: 7 | ||
|
|
||
| Contents | ||
| -------- | ||
|
|
||
| .. toctree:: | ||
|
|
||
| usage | ||
| api | ||
|
|
||
| Finally, after you build the HTML documentation running ``make html``, it will | ||
| contain two new pages: | ||
|
|
||
| - ``api.html``, corresponding to ``docs/source/api.rst`` and containing a table | ||
| with the objects you included in the ``autosummary`` directive (in this case, | ||
| only one). | ||
| - ``generated/lumache.html``, corresponding to a newly created reST file | ||
| ``generated/lumache.rst`` and containing a summary of members of the module, | ||
| in this case one function and one exception. | ||
|
|
||
| .. figure:: /_static/tutorial/lumache-autosummary.png | ||
| :width: 80% | ||
| :align: center | ||
| :alt: Summary page created by autosummary | ||
|
|
||
| Summary page created by autosummary | ||
|
|
||
| Each of the links in the summary page will take you to the places where you | ||
| originally used the corresponding ``autodoc`` directive, in this case in the | ||
| ``usage.rst`` document. | ||
|
|
||
| .. note:: | ||
|
|
||
| The generated files are based on `Jinja2 | ||
| templates <https://jinja2docs.readthedocs.io/>`_ that | ||
| :ref:`can be customized <autosummary-customizing-templates>`, | ||
| but that is out of scope for this tutorial. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| Describing code in Sphinx | ||
| ========================= | ||
|
|
||
| In the :doc:`previous sections of the tutorial </tutorial/index>` you can read | ||
| how to write narrative or prose documentation in Sphinx. In this section you | ||
| will describe code objects instead. | ||
|
|
||
| Sphinx supports documenting code objects in several languages, namely Python, | ||
| C, C++, JavaScript, and reStructuredText. Each of them can be documented using | ||
| a series of directives and roles grouped by | ||
| :doc:`domain </usage/restructuredtext/domains>`. For the remainder of the | ||
| tutorial you will use the Python domain, but all the concepts seen in this | ||
| section apply for the other domains as well. | ||
|
|
||
| .. _tutorial-describing-objects: | ||
|
|
||
| Documenting Python objects | ||
| -------------------------- | ||
|
|
||
| Sphinx offers several roles and directives to document Python objects, | ||
| all grouped together in :ref:`the Python domain <python-domain>`. For example, | ||
| you can use the :rst:dir:`py:function` directive to document a Python function, | ||
| as follows: | ||
|
|
||
| .. code-block:: rst | ||
| :caption: docs/source/usage.rst | ||
|
|
||
| Creating recipes | ||
| ---------------- | ||
|
|
||
| To retrieve a list of random ingredients, | ||
| you can use the ``lumache.get_random_ingredients()`` function: | ||
|
|
||
| .. py:function:: lumache.get_random_ingredients(kind=None) | ||
|
|
||
| Return a list of random ingredients as strings. | ||
|
|
||
| :param kind: Optional "kind" of ingredients. | ||
| :type kind: list[str] or None | ||
| :return: The ingredients list. | ||
| :rtype: list[str] | ||
|
|
||
| Which will render like this: | ||
|
|
||
| .. figure:: /_static/tutorial/lumache-py-function.png | ||
| :width: 80% | ||
| :align: center | ||
| :alt: HTML result of documenting a Python function in Sphinx | ||
|
|
||
| The rendered result of documenting a Python function in Sphinx | ||
|
|
||
| Notice several things: | ||
|
|
||
| - Sphinx parsed the argument of the ``.. py:function`` directive and | ||
| highlighted the module, the function name, and the parameters appropriately. | ||
| - The directive content includes a one-line description of the function, | ||
| as well as a :ref:`info field list <info-field-lists>` containing the function | ||
| parameter, its expected type, the return value, and the return type. | ||
|
|
||
| .. note:: | ||
|
|
||
| The ``py:`` prefix specifies the :term:`domain`. You may configure the | ||
| default domain so you can omit the prefix, either globally using the | ||
| :confval:`primary_domain` configuration, or use the | ||
| :rst:dir:`default-domain` directive to change it from the point it is called | ||
| until the end of the file. | ||
| For example, if you set it to ``py`` (the default), you can write | ||
| ``.. function::`` directly. | ||
|
|
||
| Cross-referencing Python objects | ||
| -------------------------------- | ||
|
|
||
| By default, most of these directives generate entities that can be | ||
| cross-referenced from any part of the documentation by using | ||
| :ref:`a corresponding role <python-roles>`. For the case of functions, | ||
| you can use :rst:role:`py:func` for that, as follows: | ||
|
|
||
| .. code-block:: rst | ||
| :caption: docs/source/usage.rst | ||
|
|
||
| The ``kind`` parameter should be either ``"meat"``, ``"fish"``, | ||
| or ``"veggies"``. Otherwise, :py:func:`lumache.get_random_ingredients` | ||
| will raise an exception. | ||
|
|
||
| When generating code documentation, Sphinx will generate a cross-reference automatically just | ||
| by using the name of the object, without you having to explicitly use a role | ||
| for that. For example, you can describe the custom exception raised by the | ||
| function using the :rst:dir:`py:exception` directive: | ||
|
|
||
| .. code-block:: rst | ||
| :caption: docs/source/usage.rst | ||
|
|
||
| .. py:exception:: lumache.InvalidKindError | ||
|
|
||
| Raised if the kind is invalid. | ||
|
|
||
| Then, add this exception to the original description of the function: | ||
|
|
||
| .. code-block:: rst | ||
| :caption: docs/source/usage.rst | ||
| :emphasize-lines: 7 | ||
|
|
||
| .. py:function:: lumache.get_random_ingredients(kind=None) | ||
|
|
||
| Return a list of random ingredients as strings. | ||
|
|
||
| :param kind: Optional "kind" of ingredients. | ||
| :type kind: list[str] or None | ||
| :raise lumache.InvalidKindError: If the kind is invalid. | ||
| :return: The ingredients list. | ||
| :rtype: list[str] | ||
|
|
||
| And finally, this is how the result would look: | ||
|
|
||
| .. figure:: /_static/tutorial/lumache-py-function-full.png | ||
| :width: 80% | ||
| :align: center | ||
| :alt: HTML result of documenting a Python function in Sphinx | ||
| with cross-references | ||
|
|
||
| HTML result of documenting a Python function in Sphinx with cross-references | ||
|
|
||
| Beautiful, isn't it? | ||
|
|
||
| Including doctests in your documentation | ||
| ---------------------------------------- | ||
|
|
||
| Since you are now describing code from a Python library, it will become useful | ||
| to keep both the documentation and the code as synchronized as possible. | ||
| One of the ways to do that in Sphinx is to include code snippets in the | ||
| documentation, called *doctests*, that are executed when the documentation is | ||
| built. | ||
|
|
||
| To demonstrate doctests and other Sphinx features covered in this tutorial, | ||
| Sphinx will need to be able to import the code. To achieve that, write this | ||
| at the beginning of ``conf.py``: | ||
|
|
||
| .. code-block:: python | ||
| :caption: docs/source/conf.py | ||
| :emphasize-lines: 3-5 | ||
|
|
||
| # If extensions (or modules to document with autodoc) are in another directory, | ||
| # add these directories to sys.path here. | ||
| import pathlib | ||
| import sys | ||
| sys.path.insert(0, pathlib.Path(__file__).parents[2].resolve().as_posix()) | ||
|
|
||
| .. note:: | ||
|
|
||
| An alternative to changing the :py:data:`sys.path` variable is to create a | ||
| ``pyproject.toml`` file and make the code installable, | ||
| so it behaves like any other Python library. However, the ``sys.path`` | ||
| approach is simpler. | ||
|
|
||
| Then, before adding doctests to your documentation, enable the | ||
| :doc:`doctest </usage/extensions/doctest>` extension in ``conf.py``: | ||
|
|
||
| .. code-block:: python | ||
| :caption: docs/source/conf.py | ||
| :emphasize-lines: 3 | ||
|
|
||
| extensions = [ | ||
| 'sphinx.ext.duration', | ||
tk0miya marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 'sphinx.ext.doctest', | ||
| ] | ||
|
|
||
| Next, write a doctest block as follows: | ||
|
|
||
| .. code-block:: rst | ||
| :caption: docs/source/usage.rst | ||
|
|
||
| >>> import lumache | ||
| >>> lumache.get_random_ingredients() | ||
| ['shells', 'gorgonzola', 'parsley'] | ||
astrojuanlu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Doctests include the Python instructions to be run preceded by ``>>>``, | ||
| the standard Python interpreter prompt, as well as the expected output | ||
| of each instruction. This way, Sphinx can check whether the actual output | ||
| matches the expected one. | ||
|
|
||
| To observe how a doctest failure looks like (rather than a code error as | ||
| above), let's write the return value incorrectly first. Therefore, add a | ||
| function ``get_random_ingredients`` like this: | ||
|
|
||
| .. code-block:: python | ||
| :caption: lumache.py | ||
|
|
||
| def get_random_ingredients(kind=None): | ||
| return ["eggs", "bacon", "spam"] | ||
|
|
||
| You can now run ``make doctest`` to execute the doctests of your documentation. | ||
| Initially this will display an error, since the actual code does not behave | ||
| as specified: | ||
|
|
||
| .. code-block:: console | ||
|
|
||
| (.venv) $ make doctest | ||
| Running Sphinx v4.2.0 | ||
| loading pickled environment... done | ||
| ... | ||
| running tests... | ||
|
|
||
| Document: usage | ||
| --------------- | ||
| ********************************************************************** | ||
| File "usage.rst", line 44, in default | ||
| Failed example: | ||
| lumache.get_random_ingredients() | ||
| Expected: | ||
| ['shells', 'gorgonzola', 'parsley'] | ||
| Got: | ||
| ['eggs', 'bacon', 'spam'] | ||
| ********************************************************************** | ||
astrojuanlu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ... | ||
| make: *** [Makefile:20: doctest] Error 1 | ||
|
|
||
| As you can see, doctest reports the expected and the actual results, | ||
| for easy examination. It is now time to fix the function: | ||
|
|
||
| .. code-block:: python | ||
| :caption: lumache.py | ||
| :emphasize-lines: 2 | ||
|
|
||
| def get_random_ingredients(kind=None): | ||
| return ["shells", "gorgonzola", "parsley"] | ||
|
|
||
| And finally, ``make test`` reports success! | ||
astrojuanlu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| For big projects though, this manual approach can become a bit tedious. | ||
| In the next section, you will see :doc:`how to automate the | ||
| process </tutorial/automatic-doc-generation>`. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.