Автодокументация Python-проекта с помощью Sphinx autodoc и ReadTheDocs#

ноябрь 24, 2022 автор: Александр Драгункин

На создание документации для пакетов/модулей в проектах Python может уйти довольно много времени, но есть способ автоматически сгенерировать её из docstrings. Этот документ в основном представляет собой краткое изложение труда Сэма Николлса, найденного здесь, но с одним важным дополнением (см. раздел о mocking). Мы будем использовать следующее:

  • Sphinx – Пакет Python для создания документации

  • Sphinx autodoc – Расширение Sphinx для создания документации из docstrings

  • ReadTheDocs – создание и размещение документации онлайн

Прежде чем начать, убедитесь, что в коде написаны строки документации для модулей/функций/методов. Это самая трудоемкая часть, но без этого никак! Ибо компьютеру всё равно как оформлен ваш код. Код пишут для читателей(людей). Так что смиритесь! Вы ОБЯЗАНЫ комментировать и документировать свой код! В качестве примера: допустим, у вас есть модуль со строками документации, которые выглядят как то так, и после завершения этого процесса он автоматически превратится в документацию Python, которая выглядит следующим образом.

Sphinx Autodoc#

Сначала установим пакет Sphinx:

pip install Sphinx

Работаем в командной строке… Затем создадим каталог docs в корне каталога проекта, набираем cd и запускаем sphinx-quickstart

cd /path/to/project
mkdir docs
cd docs/
sphinx-quickstart

После этого начинается процесс настройки. Значения по умолчанию, как правило, в порядке, но единственное, что надо бы ещё сделать, это включить расширение autodoc по запросу.

Предполагая, что все ваши строки документации были написаны, вам нужно создать stubs(закладки) для вашего проекта в вашем каталоге docs (их необходимо создать заново, если вдруг будут добавлены новые модули):

cd docs/
sphinx-apidoc -o source/ ../

Если вдруг не где больше, то можно использовать ReadTheDocs (RTD) для создания и размещения документации Python. Для того, чтобы RTD смог найти файлы вашего пакета, необходимо внести изменения в конфигурацию Sphinx. После описанного выше процесса быстрого запуска Sphinx должен был создать conf.py файл в каталоге docs. В верхней части этого файла вам нужно добавить путь к содержимому пакета (или раскомментировать строки, которые уже есть в файле).:

import os
import sys
sys.path.insert(0, os.path.abspath('../'))

Здесь мы также можем изменить тему страницы документации:

html_theme = 'sphinx_rtd_theme'

Или так. Этот вариант мне больше понравился:

html_theme = 'python_docs_theme'

Большее число вариантов можно найти здесь. В этом случае тему потребуется установить.

$ pip install python-docs-theme

And add extensions:

extensions = ['sphinx.ext.autodoc',
    'sphinx.ext.coverage',
    'sphinx.ext.napoleon',
    'sphinx.ext.viewcode']

napoleon_google_docstring = False
napoleon_use_param = False
napoleon_use_ivar = True

Теперь попытаемся создать документацию локально. Sphinx включает в себя файл make, который можно и нужно использовать для этой цели:

cd docs/
make html

Возможно, вам потребуется установить модули mock и sphinx_rtd_theme или другой темы для работы локальной сборки:

pip install mock
pip install sphinx_rtd_theme

Документация окажется внутри docs/_build/html. Это та самая автоматически сгенерированная документация Python в формате HTML. Проверьте всё ли на месте и что правильно собраны все документы. Но не получится использовать эти файлы напрямую, чтобы RTD обрабатывал процесс сборки сам, но это просто быстрый способ убедиться, что все работает. И вообщем то эту доку можно где то выложить в качестве сайта

ReadTheDocs#

RTD сможет забрать проект из репозитория GitHub и создаст документацию Python непосредственно из содержимого пакета и /docs папок. При желании можно автоматически запускать сборку всякий раз, при фиксации и отправке изменения в репозиторий. Правда, для этого репозиторий должен быть открытым.

Первое, что нужно для этого сделать, это поместить ваш пакет в репозиторий Github, но надо исключить папки локальной документации из репозитория. Добавляем docs/_build/, docs/_static/ и docs/_templates/ в файл репозитория .gitignore. Однако проверим, что docs/sources фиксятся и коммитятся.

Затем надо создать учетную запись ReadTheDocs и импортировать свой репозиторий из списка. Это должно вызвать начальную сборку, которую вы можете увидеть во вкладке Builds .

Если вдруг надо, чтобы RTD автоматически создавал документацию каждый раз, когда пушим изменения в репозиторий GitHub, заходим в настройки репозитория, переходим в Integration & Services и добавляем/выбираем ReadTheDocs из списка доступных служб.

Если всё нормально, то должны увидеть, что RTD создал документацию. Однако может так случиться, что столкнётесь с проблемой, когда RTD неправильно индексирует модули Python (т.е. py-modindex.html отсутствует и выдает ошибку 404 при попытке просмотреть страницу)…

Mocking#

Существует ряд причин, по которым индекс модуля не создается. Основная причина, с которой я столкнулся, заключается в том, что мой пакет зависит от других пакетов, которым требуются библиотеки C (например, numpy или вообще k3). Вы можете прочитать больше об этом в разделе часто задаваемых вопросов RTD.

Решение состоит в том, чтобы имитировать(мОкать) эти импортированные пакеты и модули.

Выясните все зависимости, которые зависят от библиотек C, затем откройте conf.py и добавьте следующее:

from mock import Mock as MagicMock
class Mock(MagicMock):
    @classmethod
    def __getattr__(cls, name):
        return MagicMock()
MOCK_MODULES = ['numpy', 'scipy', 'scipy.linalg', 'scipy.signal','k3']
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)

Обязательно добавьте свои зависимости в MOCK_MODULES, и если ваш пакет импортирует модули из другого пакета, добавьте этот конкретный модуль также в список. Например, если для моего пакета специально требуется scipy.signal, то этот модуль следует добавить в MOCK_MODULES в дополнение к пакету scipy.

Теперь просто закомитьте и отправьте во внешний репозиторий GitHub свои изменения, запустите сборку на RTD, и вы должны увидеть правильно сгенерированный индекс модуля.

Если индекс модуля по-прежнему не работает, это может быть связано с тем, что RTD использует неправильную версию Python для создания документов. У меня была эта проблема с пакетом Python 3.x, который я создал, и индекс модуля не отображался, потому что RTD создавал виртуальную среду с использованием Python 2. Чтобы изменить это, перейдите на панель управления RTD,RTD Dashboard, Admin > Advanced Settings и измените интерпретатор Python внизу.

Настройка#

На этом этапе документация должна быть готова, и мы можем начать настраивать ее различными способами. Наиболее очевидное место для начала - это редактирование файлов в /docs/source, чтобы настроить макет и содержание наших страниц документации.

Sphinx build options#

Можем добавить еще несколько опций к conf.py. Полный список опций см. в документации Sphinx

Несколько примеров:

  1. add_module_names = False таким образом, функции не предваряются именем package/module

  2. add_function_parentheses = True чтобы круглые скобки добавлялись в конец всех имен функций

Установка номера версии#

Номер версии документации может быть установлен “динамически”. Это особенно полезно, если нужно обновить номер версии пакета в нескольких местах (например setup.py для pypi). Самое простое, что я придумал для этого, - создать файл в каталоге верхнего уровня пакета с именем VERSION, а затем написать функцию для чтения из этого файла в conf.py чтобы извлечь номер версии:

def get_version():
    version_file = open('../VERSION')
    return version_file.read().strip()
version = get_version()
release = version

Как добавить файл README#

Если конвертировать из репо README.md в файл .rst (README.rst), он появится его на главной странице документации (index.rst).

При желании можно установить маркер в README.rst, чтобы включать содержимое только после определенного момента.

Начнём с добавления .. inclusion-marker-main-readme в README.rst. Затем в index.rst добавьте:

.. include:: ../README.rst
   :start-after: inclusion-marker-main-readme

По итогам исследования Sphinx научился понимать MD-файлы. Для этого потребуется установить пакет

pip install myst-parser

и добавить в conf.py

source_suffix = {
    '.rst': 'restructuredtext',
    '.txt': 'restructuredtext',
    '.md': 'markdown',
}
exclude_patterns = []

language = 'ru'

myst_html_meta = {
    "description lang=en": "metadata description",
    "description lang=ru": "описание метаданных",
    "keywords": "Sphinx, MyST",
    "property=og:locale":  "ru_RU"
}

Ещё более интересная тема sphinxcontrib-mermaid

Это использование UML диаграмм в тексте документации

Параметры PDF#

Если задача RTD сгенерировать PDF-файл для каждой сборки, иногда может случиться что он создаст дополнительные пустые страницы между главами. Есть 2 способа избежать этого. Во-первых, вместо использования manual в качестве класса document в conf.py > latex_documents, мы можем переключиться на howto, чтобы полностью удалить главы. В качестве альтернативы мы можем установить параметры печати страницы latex, изменив latex_elements:

latex_elements = {
  'extraclassoptions': ',openany,oneside'
}

В целом, скажу так: Sphinx autodoc и RTD значительно экономят время. Приходится беспокоиться только о написании своих строк документации, а все остальное обрабатывается автоматически.