# Автодокументация Python-проекта с помощью Sphinx autodoc и ReadTheDocs # *ноябрь 24, 2022* **автор:** Александр Драгункин На создание документации для пакетов/модулей в проектах Python может уйти довольно много времени, но есть способ автоматически сгенерировать её из `docstrings`. Этот документ в основном представляет собой краткое изложение труда **Сэма Николлса**, найденного [здесь](https://samnicholls.net/2016/06/15/how-to-sphinx-readthedocs/), но с одним важным дополнением (см. раздел о **mocking**). Мы будем использовать следующее: - [Sphinx](https://www.sphinx-doc.org/en/stable/) – Пакет Python для создания документации - [Sphinx autodoc](https://www.sphinx-doc.org/en/stable/ext/autodoc.html) – Расширение Sphinx для создания документации из docstrings - [ReadTheDocs](https://readthedocs.org/) – создание и размещение документации онлайн Прежде чем начать, убедитесь, что в коде написаны строки документации для модулей/функций/методов. Это самая трудоемкая часть, но без этого никак! Ибо компьютеру всё равно как оформлен ваш код. Код пишут для читателей(людей). Так что смиритесь! Вы ОБЯЗАНЫ комментировать и документировать свой код! В качестве примера: допустим, у вас есть модуль со строками документации, которые выглядят [как то так](https://github.com/sho-87/sensormotion/blob/master/sensormotion/gait.py), и после завершения этого процесса он автоматически превратится в документацию Python, которая выглядит [следующим образом](https://sensormotion.readthedocs.io/en/latest/source/sensormotion.html#module-sensormotion.gait). ## 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`. В верхней части этого файла вам нужно добавить путь к содержимому пакета (или раскомментировать строки, которые уже есть в файле).: ```python import os import sys sys.path.insert(0, os.path.abspath('../')) ``` Здесь мы также можем изменить тему страницы документации: html_theme = 'sphinx_rtd_theme' Или так. Этот вариант мне больше понравился: html_theme = 'python_docs_theme' Большее число вариантов можно найти [здесь](https://sphinx-themes.org/#themes). В этом случае тему потребуется установить. $ pip install python-docs-theme And add extensions: ```python 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` и добавьте следующее: ```python 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](https://www.sphinx-doc.org/en/master/usage/configuration.html) Несколько примеров: 1. `add_module_names = False` таким образом, функции не предваряются именем package/module 1. `add_function_parentheses = True` чтобы круглые скобки добавлялись в конец всех имен функций ## Установка номера версии ## Номер версии документации может быть установлен “динамически”. Это особенно полезно, если нужно обновить номер версии пакета в нескольких местах (например `setup.py` для `pypi`). Самое простое, что я придумал для этого, - создать файл в каталоге верхнего уровня пакета с именем `VERSION`, а затем написать функцию для чтения из этого файла в `conf.py` чтобы извлечь номер версии: ``` python 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-файлы](https://myst-parser.readthedocs.io/en/latest/intro.html). Для этого потребуется установить пакет pip install myst-parser и добавить в conf.py ```python 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](https://mermaid-js.github.io/mermaid/#/) Это использование UML диаграмм в тексте документации ## Параметры PDF ## Если задача RTD сгенерировать PDF-файл для каждой сборки, иногда может случиться что он создаст дополнительные пустые страницы между главами. Есть 2 способа избежать этого. Во-первых, вместо использования `manual` в качестве класса document в conf.py > latex_documents, мы можем переключиться на howto, чтобы полностью удалить главы. В качестве альтернативы мы можем установить параметры печати страницы latex, изменив latex_elements: ```python latex_elements = { 'extraclassoptions': ',openany,oneside' } ``` В целом, скажу так: `Sphinx autodoc` и `RTD` значительно экономят время. Приходится беспокоиться только о написании своих строк документации, а все остальное обрабатывается автоматически.