Рабочий процесс проекта Python
Настройка проекта
Давайте построим генератор случайных цитат, который будет возвращать случайно выбранную цитату из множества цитат.
Инициализировать проект
Сначала создадим новую папку для нашего проекта:
$ mkdir random-quote-generator
$ cd random-quote-generator
Инициализируйте проект с помощью Poetry:
$ poetry init
Package name [random_quote_generator]:
Version [0.1.0]:
Description []:
Author [Your name <your@email.com>, n to skip]:
License []:
Compatible Python versions [^3.10]:
Would you like to define your main dependencies interactively? (yes/no) [yes] no
Would you like to define your development dependencies interactively? (yes/no) [yes] no
Do you confirm generation? (yes/no) [yes]
Подробнее о поэзии читайте в статье Современные среды Python - управление зависимостями и рабочим пространством.
Имя вашего проекта должно быть уникальным, поскольку вы будете загружать его в PyPI. Поэтому, чтобы избежать столкновений имен, добавьте уникальную строку к имени пакета в pyproject.toml.
Например:
[tool.poetry]
name = "random-quote-generator-9308"
version = "0.1.0"
description = ""
authors = ["Michael Herman <notreal@gmail.com>"]
[tool.poetry.dependencies]
python = "^3.10"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Создайте новый репозиторий на GitHub:
Далее, инициализируйте git-репозиторий внутри вашего проекта:
$ git init
$ git add pyproject.toml
$ git commit -m "first commit"
$ git branch -M main
$ git remote add origin git@github.com:<your-github-username>/random-quote-generator.git
$ git fetch
$ git branch --set-upstream-to=origin/main main
$ git pull origin main --rebase
$ git push -u origin main
После завершения базовой установки, давайте продолжим, добавив следующие зависимости для разработки:
Просмотрите статью Python Code Quality для получения подробной информации об этих зависимостях.
Install:
$ poetry add --dev pytest pytest-cov black isort flake8 bandit safety
Добавьте новый файл poetry.lock, а также обновленный файл pyproject.toml в git:
$ git add poetry.lock pyproject.toml
Создайте проект
После этого создайте новую папку под названием "random_quote_generator". Внутри этой папки добавьте файл __init__.py, чтобы он рассматривался как модуль, а также файл __quotes.py.
random-quote-generator
├── poetry.lock
├── pyproject.toml
└── random_quote_generator
├── __init__.py
└── quotes.py
Внутри quotes.py добавьте:
quotes = [
{
"quote": "A long descriptive name is better than a short "
"enigmatic name. A long descriptive name is better "
"than a long descriptive comment.",
"author": "Robert C. Martin",
},
{
"quote": "You should name a variable using the same "
"care with which you name a first-born child.",
"author": "Robert C. Martin",
},
{
"quote": "Any fool can write code that a computer "
"can understand. Good programmers write code"
" that humans can understand.",
"author": "Martin Fowler",
},
]
Там нет ничего особенного. Просто список словарей, по одному для каждой цитаты. Далее, создайте новую папку в корне проекта под названием "tests" и добавьте следующие файлы:
tests
├── __init__.py
└── test_get_quote.py
test_get_quote.py:
from random_quote_generator import get_quote
from random_quote_generator.quotes import quotes
def test_get_quote():
"""
GIVEN
WHEN get_quote is called
THEN random quote from quotes is returned
"""
quote = get_quote()
assert quote in quotes
Запустите тест:
$ poetry run python -m pytest tests
Должен произойти сбой:
E ImportError: cannot import name 'get_quote' from 'random_quote_generator'
Далее, добавьте новый файл в "random_quote_generator" под названием get_quote.py:
import random
from random_quote_generator.quotes import quotes
def get_quote() -> dict:
"""
Get random quote
Get randomly selected quote from database our programming quotes
:return: selected quote
:rtype: dict
"""
return quotes[random.randint(0, len(quotes) - 1)]
Итак, цитата выбирается путем генерации случайного целого числа со значением random.randint между 0 и последним индексом.
Экспортируйте функцию в random_quote_generator/__init__.py:
"""
Random Quote Generator
======================
Get random quote from our database of programming wisdom
"""
from .get_quote import get_quote
__all__ = ["get_quote"]
Функция импортируется и перечисляется внутри атрибута __all__
, который представляет собой список публичных объектов для модуля. Другими словами, когда кто-то использует from random_quote_generator import *
, будут импортированы только имена, перечисленные в __all__
.
Теперь тест должен пройти:
$ poetry run python -m pytest tests
Создайте файл .gitignore в корне вашего проекта:
__pycache__
Добавьте папки "random_quote_generator" и "tests" в git вместе с файлом .gitignore:
$ git add random_quote_generator/ tests/ .gitignore
Вот и все. Посылка готова к отправке.
Документирование проекта
Наш пакет работает, но нашим пользователям придется проверить его исходный код, чтобы понять, как его использовать. Мы уже включили docstrings, чтобы можно было легко создавать документацию по отдельному проекту с помощью Sphinx.
Если вы не знакомы с docstrings или документацией как отдельным ресурсом, просмотрите статью Documenting Python Code and Projects.
Предполагая, что у вас установлен Sphinx installed, выполните следующее, чтобы разложить файлы и папки для Sphinx в корне проекта:
$ sphinx-quickstart docs
Вы будете продвигаться по службе с некоторыми вопросами:
> Separate source and build directories (y/n) [n]: n
> Project name: Random Quote Generator
> Author name(s): Your Name
> Project release []: 0.1.0
> Project language [en]: en
Далее, давайте обновим конфигурацию проекта. Откройте docs/conf.py и замените следующее:
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
С этим:
import os
import sys
sys.path.insert(0, os.path.abspath('..'))
Теперь autodoc, который используется для извлечения документации из docstrings, будет искать модули в родительской папке "docs".
Добавьте следующие расширения в список расширений:
extensions = [
'sphinx.ext.autodoc',
]
Обновить docs/index.rst следующим образом:
.. Random Quote Generator documentation master file, created by
sphinx-quickstart on Mon Dec 21 22:27:23 2020.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Random Quote Generator's documentation!
==================================================
.. automodule:: random_quote_generator
:members:
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
Этот файл должен быть исключен из Flake8, который мы скоро добавим. Поэтому создайте файл .flake8 в корне проекта:
[flake8]
exclude =
docs/conf.py,
Добавьте папку "docs" и .flake8 в git:
$ git add docs/ .flake8
GitHub Actions
Далее, давайте подключим конвейер CI с помощью GitHub Actions.
Добавьте следующие файлы и папки в корень проекта:
.github
└── workflows
└── branch.yaml
Внутри branch.yaml, добавьте:
name: Push
on: [push]
jobs:
test:
strategy:
fail-fast: false
matrix:
python-version: ['3.10']
poetry-version: ['1.1.13']
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Run image
uses: abatilo/actions-poetry@v2.1.4
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Install dependencies
run: poetry install
- name: Run tests
run: poetry run pytest --cov=./ --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
code-quality:
strategy:
fail-fast: false
matrix:
python-version: ['3.10']
poetry-version: ['1.1.13']
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Run image
uses: abatilo/actions-poetry@v2.1.4
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Install dependencies
run: poetry install
- name: Run black
run: poetry run black . --check
- name: Run isort
run: poetry run isort . --check-only --profile black
- name: Run flake8
run: poetry run flake8 .
- name: Run bandit
run: poetry run bandit .
- name: Run saftey
run: poetry run safety check
Эта конфигурация:
- запускается на каждом push в каждой ветке -
on: [push]
- работает на последней версии Ubuntu -
ubuntu-latest
- использует Python 3.10 -
python-version: [3.10]
,python-version: ${{ matrix.python-version }}
- использует Poetry версии 1.1.13 -
poetry-version: [1.1.13]
,poetry-version: ${{ matrix.poetry-version }}
Определены два рабочих места: test
и code-quality
. Как следует из названий, тесты выполняются в тестовом задании, а наши проверки качества кода - в задании качества кода.
Теперь при каждом запуске в репозиторий GitHub будут запускаться тесты и задания качества кода.
Добавьте ".github" в git:
$ git add .github/
Запустите все проверки качества кода:
$ poetry run black .
$ poetry run isort . --profile black
$ poetry run flake8 .
$ poetry run bandit .
$ poetry run safety check
Обязательно добавьте в git все файлы, которые могли измениться. Затем зафиксируйте и отправьте изменения на GitHub:
$ git add docs/ random_quote_generator/ tests/
$ git commit -m 'Package ready'
$ git push -u origin main
Вы должны увидеть, что ваш рабочий процесс запущен на вкладке "Действия" в вашем репозитории GitHub. Убедитесь, что он прошел, прежде чем двигаться дальше.
CodeCov
Далее мы настроим CodeCov для отслеживания покрытия кода. Перейдите по адресу http://codecov.io/, войдите под своей учетной записью GitHub и найдите свой репозиторий.
Ознакомьтесь с руководством Quick Start, чтобы получить помощь в начале работы с CodeCov.
Снова запустите рабочий процесс GitHub Actions. После этого вы сможете увидеть отчет о покрытии на CodeCov:
Теперь при каждом запуске рабочего процесса будет создаваться отчет о покрытии и загружаться в CodeCov. Вы можете анализировать изменения в процентах покрытия для веток, коммитов и запросов на вытягивание, обращая внимание на увеличение и уменьшение покрытия с течением времени.
Документация
Мы будем использовать Read the Docs для размещения нашей документации. Перейдите по адресу https://readthedocs.org и войдите в систему, используя свою учетную запись GitHub.
Если вы только что зарегистрировались, убедитесь, что вы проверили свой адрес электронной почты, прежде чем продолжить.
Далее нажмите "Импортировать проект". После этого обновите проекты и добавьте проект генератора случайных цитат. Откройте проект и перейдите в раздел "Admin". Затем в разделе "Дополнительные настройки" установите ветвь по умолчанию на main
. Не забудьте сохранить изменения.
На сборку документации Read The Docs уйдет несколько минут. После этого вы сможете просмотреть документацию вашего проекта по адресу https://your-project-slug-on-readthedocs.readthedocs.io
.
По умолчанию документация будет перестраиваться при каждом переносе в ветку main
. После этого остается только опубликовать ваш пакет в PyPI.
PyPI
Наконец, чтобы сделать проект "pip-installable", мы опубликуем его на PyPI.
Начните с добавления следующего раздела в pyproject.toml, чтобы модуль "random_quote_generator" был включен в дистрибутив PyPI:
packages = [
{ include = "random_quote_generator" },
]
Пример файла:
[tool.poetry]
name = "random-quote-generator-93618"
packages = [
{ include = "random_quote_generator" },
]
version = "0.1.0"
description = ""
authors = ["Amir Tadrisi <notreal@gmail.com>"]
[tool.poetry.dependencies]
python = "^3.10"
[tool.poetry.dev-dependencies]
pytest = "^7.1.2"
pytest-cov = "^3.0.0"
black = "^22.3.0"
isort = "^5.10.1"
flake8 = "^4.0.1"
bandit = "^1.7.4"
safety = "^1.10.3"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Добавьте новый файл под названием release.yaml в ".github/workflows":
name: Release
on:
release:
types:
- created
jobs:
publish:
strategy:
fail-fast: false
matrix:
python-version: ['3.10']
poetry-version: ['1.1.13']
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Run image
uses: abatilo/actions-poetry@v2.1.4
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Publish
env:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
run: |
poetry config pypi-token.pypi $PYPI_TOKEN
poetry publish --build
Итак, когда будет создан новый релиз, пакет будет опубликован в PyPI.
Далее нам потребуется создать токен PyPI. Создайте аккаунт на PyPI, если у вас его еще нет. Затем, авторизовавшись, нажмите "Account settings" и добавьте новый токен API. Скопируйте токен. Теперь вам нужно добавить его в секреты вашего репозитория на GitHub. В репозитории перейдите на вкладку "Настройки", затем нажмите "Секреты" и выберите "Действия". Используйте PYPI_TOKEN
для названия секрета и значение токена в качестве секретного значения.
Теперь вы готовы создать свой первый релиз.
Добавьте файл release.yaml в git, а также обновленный файл pyproject.toml, зафиксируйте и вытолкните:
$ git add .github/workflows/release.yaml pyproject.toml
$ git commit -m 'Ready for first release'
$ git push -u origin main
Чтобы создать новый релиз, перейдите в раздел https://github.com/<username>/<project-name>/releases/
. Нажмите "Создать новый релиз". Введите 0.1.0
для тега и Initial Release
для заголовка.
Это запустит новый рабочий процесс на GitHub Actions. После завершения вы увидите свой пакет на PyPI.
Теперь вы должны иметь возможность установить и использовать ваш пакет из PyPI:
>>> from random_quote_generator import get_quote
>>>
>>> print(get_quote())
{'quote': 'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.', 'author': 'Martin Fowler'}
Заключение
Мы создали простой CI/CD конвейер для пакета Python, опубликованного на PyPI. Код в каждой ветке проверяется тестами и качеством кода. Вы можете проверить покрытие кода в CodeCov. При выпуске новые версии развертываются на PyPI, а документация обновляется. Это может значительно упростить вашу жизнь.
Подобные автоматизированные конвейеры помогают обеспечить неизменность рабочего процесса изо дня в день. Тем не менее, вы все равно должны заботиться о культуре работы вашей команды. Тесты запускаются при нажатии, но они должны присутствовать. Автоматизированный запуск тестов мало чем поможет, если у вас нет тестов для запуска. Автоматизация также не будет иметь большого значения для размера ваших изменений. Старайтесь, чтобы они были небольшими, часто объединяясь в main
. Небольшие изменения вместе с тестово-ориентированной разработкой (TDD) и конвейером CI/CD могут иметь огромное значение для качества программного обеспечения, которое вы поставляете. Только не забывайте, что все всегда начинается и заканчивается культурой команды.