Автодокументация 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
Несколько примеров:
add_module_names = False
таким образом, функции не предваряются именем package/moduleadd_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
значительно экономят время. Приходится беспокоиться только о написании своих строк документации, а все остальное обрабатывается автоматически.