Descrevendo código no Sphinx

Nas seções anteriores do tutorial você pode ler como escrever documentação narrativa ou em prosa no Sphinx. Nesta seção você descreverá objetos código.

Sphinx oferece suporte à documentação de objetos código em várias linguagens, inclusive Python, C, C++, JavaScript e reStructuredText. Cada um deles pode ser documentado usando uma série de diretivas e de papéis agrupados por domínio. No restante do tutorial você usará o domínio Python, mas todos os conceitos vistos nesta seção também se aplicam aos outros domínios.

Python

Documentando objetos Python

O Sphinx oferece vários papéis e diretivas para documentar objetos Python, todos agrupados no domínio Python. Por exemplo, você pode usar a diretiva py:function para documentar uma função Python da seguinte forma:

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]

Que será renderizado assim:

Resultado em HTML da documentação de uma função Python no Sphinx

O resultado renderizado da documentação de uma função Python no Sphinx

Observe várias coisas:

  • Sphinx analisou o argumento da diretiva .. py:function e destacou o módulo, o nome da função e os parâmetros apropriadamente.

  • O conteúdo da diretiva inclui uma descrição de uma linha da função, bem como uma lista de campos de informações contendo o parâmetro da função, seu tipo esperado, o valor de retorno e o tipo de retorno.

Nota

O prefixo py: especifica o domínio. Você pode configurar o domínio padrão para poder omitir o prefixo, seja globalmente usando a configuração primary_domain, ou use a diretiva default-domain para alterá-lo desde o ponto em que é chamado até o final do arquivo. Por exemplo, se você definir como py (o padrão), você pode escrever .. function:: diretamente.

Referência cruzada de objetos Python

Por padrão, a maioria dessas diretivas geram entidades que podem ser referenciadas de qualquer parte da documentação usando o papel correspondente. Para o caso de funções, você pode usar py:func para isso, da seguinte forma:

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.

Ao gerar a documentação do código, o Sphinx irá gerar automaticamente uma referência cruzada apenas usando o nome do objeto, sem que você precise usar explicitamente um papel para isso. Por exemplo, você pode descrever a exceção personalizada gerada pela função usando a diretiva py:exception:

docs/source/usage.rst
.. py:exception:: lumache.InvalidKindError

   Raised if the kind is invalid.

Em seguida, adicione esta exceção à descrição original da função:

docs/source/usage.rst
.. 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]

E por fim, o resultado ficaria assim:

Resultado HTML da documentação de uma função Python no Sphinx com referências cruzadas

Resultado HTML da documentação de uma função Python no Sphinx com referências cruzadas

Lindo, não é?

Incluindo doctests em sua documentação

Como agora você está descrevendo o código de uma biblioteca Python, será útil manter a documentação e o código o mais sincronizados possível. Uma das maneiras de fazer isso no Sphinx é incluir trechos de código na documentação, chamados doctests, que são executados quando a documentação é construída.

Para demonstrar doctests e outros recursos do Sphinx abordados neste tutorial, o Sphinx precisará ser capaz de importar o código. Para conseguir isso, escreva isto no início do conf.py:

docs/source/conf.py
# 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())

Nota

Uma alternativa para alterar a variável sys.path é criar um arquivo pyproject.toml e tornar o código instalável, para que ele se comporte como qualquer outra biblioteca Python. Entretanto, a abordagem sys.path é mais simples.

Então, antes de adicionar doctests à sua documentação, habilite a extensão doctest em conf.py:

docs/source/conf.py
extensions = [
    'sphinx.ext.duration',
    'sphinx.ext.doctest',
]

A seguir, escreva um bloco de doctest da seguinte forma:

docs/source/usage.rst
>>> import lumache
>>> lumache.get_random_ingredients()
['shells', 'gorgonzola', 'parsley']

Doctests incluem as instruções Python a serem executadas precedidas por >>>, o prompt padrão do interpretador Python, bem como a saída esperada de cada instrução. Dessa forma, o Sphinx pode verificar se a saída real corresponde à esperada.

Para observar como uma falha do doctest se parece (em vez de um erro de código como acima), vamos primeiro escrever o valor de retorno incorretamente. Portanto, adicione uma função get_random_ingredients como esta:

lumache.py
def get_random_ingredients(kind=None):
    return ["eggs", "bacon", "spam"]

Agora você pode executar make doctest para executar os doctests da sua documentação. Inicialmente isso exibirá um erro, já que o código real não se comporta conforme especificado:

(.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']
**********************************************************************
...
make: *** [Makefile:20: doctest] Error 1

Como você pode ver, o doctest relata os resultados esperados e reais, para facilitar o exame. Agora é hora de corrigir a função:

lumache.py
def get_random_ingredients(kind=None):
    return ["shells", "gorgonzola", "parsley"]

E finalmente, make doctest relata sucesso!

Porém, para grandes projetos, essa abordagem manual pode se tornar um pouco tediosa. Na próxima seção, você verá como automatizar o processo.

Outras linguagens (C, C++, outros)

Documentando e fazendo referência cruzada de objetos

O Sphinx também oferece suporte a documentação e referência cruzada de objetos escritos em outras linguagens de programação. Existem quatro domínios embutidos adicionais: C, C++, JavaScript e reStructuredText. Extensões de terceiros podem definir domínios para mais linguagens, como

Por exemplo, para documentar uma definição de tipo C++, você usaria a diretiva embutida cpp:type, como segue:

.. cpp:type:: std::vector<int> CustomList

   A typedef-like declaration of a type.

O que daria o seguinte resultado:

typedef std::vector<int> CustomList

Uma declaração semelhante a typedef de um tipo.

Todas essas diretivas geram referências que podem ser referenciadas usando o papel correspondente. Por exemplo, para fazer referência à definição de tipo anterior, você pode usar o papel cpp:type da seguinte forma:

Cross reference to :cpp:type:`CustomList`.

O que produziria um hiperlink para a definição anterior: CustomList.