ℹ️ Инфо
Для взаимодействия с платформой botx используется библиотека pybotx. В документации можно посмотреть примеры её использования. Перед прочтением данного туториала следует с ней ознакомиться.
Вне зависимости от решаемых ботами задач, во всех повторяется один и тот же код.
Чтобы разработчикам не приходилось писать базовый однообразный код для каждого нового бота, существует шаблонный проект - Bot Template. Он задает структуру проекта и основной стек технологий. Это значительно упрощает разработку, позволяя сконцентрироваться на реализации бота.
Для развертывания проекта необходимо установить copier и выполнить команду:
$ copier bot-template bot-exampleСтруктура шаблонного бота состоит из нескольких следующих пакетов и модулей:
.
├── app
│ ├── api - реализация http роутов для приложения, включая необходимые для бота
│ ├── bot - команды бота и вспомогательные функции для них
│ ├── caching - классы и функции для работы с in-memory БД
│ ├── db - модели, функции для работы с БД и миграции
│ ├── resources - текстовые или файловые ресурсы бота
│ ├── schemas - сериализаторы, енамы, доменные модели
│ ├── services - сервисы с логикой (бизнес-логика)
│ ├── logger.py - логгер
│ ├── main.py - запуск сервера с инициализацией необходимых сервисов
│ └── settings.py - настройки приложения
├── scripts - скрипты для запуска тестов, форматеров, линтеров
├── tests - тесты, структура которых соответствует структуре проекта, и хелперы для них
├── poetry.lock - конфигурация текущих зависимостей. используется для их установки
├── pyproject.toml - конфигурация зависимостей, мета информация проекта (название, версия, авторы и т.п.)
└── setup.cfg - конфигурация линтеров и тестов
- Устанавливаем зависимости проекта через poetry:
$ poetry install- Определяем переменные окружения в файле
.env. Примеры переменных окружения находятся в файлеexample.env. - Запускаем
postgesиredisиспользуя docker-compose:
$ docker-compose -f docker-compose.dev.yml up -d- Применяем все миграции для инициализации таблиц с помощью alembic:
$ alembic upgrade head- Запускаем бота как приложение FastAPI через gunicorn.
Флаг
--reloadиспользуется только при разработке для автоматического перезапуска сервера при изменениях в коде:
$ gunicorn "app.main:get_application()" --worker-class uvicorn.workers.UvicornWorkerПо необходимости добавить флаг --workers и их колличество, в данном случае 4 рабочих процесса:
$ gunicorn "app.main:get_application()" --worker-class uvicorn.workers.UvicornWorker --workers 4Команды бота находятся в пакете app.bot.commands и группируются в отдельные модули в зависимости от логики. Команды добавляются с помощью коллекторов pybotx.
Основные команды, такие как /help и системные команды, находятся в модуле common.py. Для команд, относящихся к определенной задаче, создается свой модуль. Например, для интеграции с Atlassian Jira будет создан модуль jira.py. В результате структура пакета app.bot.commands будет выглядеть так:
bot
├── commands
│ ├── common.py
│ ├── jira.py
Если в модуле становится слишком много команд, следует разбить его на новые модули и сложить в один пакет с названием старого модуля. Например, так:
bot
├── commands
│ ├── common.py
│ ├── jira
│ ├── projects.py
│ ├── issues.py
Для добавления модуля с командами нужно импортировать collector в app/bot/bot.py и добавить его в инстанс бота:
from app.bot.commands import common
bot.include_handlers(common.collector)Взаимодействовать с новыми таблицами можно через модели sqlalchemy. С примерами использования можно ознакомиться тут. Модели располагаются в пакете app.db.package_name. Там же хранятся crud функции и репозитории. Структура пакета выглядит следующим образом:
├── app
│ ├── db
│ ├── migrations
│ ├── exampleapp
│ ├── repo.py - репозиторий/crud функции
│ ├── models.py - модели таблиц
Пример модели:
from sqlalchemy import Column, Integer, String
from app.db.sqlalchemy import Base
class ExampleModel(Base):
__tablename__ = "examples"
id: int = Column(Integer, primary_key=True, autoincrement=True)
text: str = Column(String)Пример репозитория:
from sqlalchemy import insert
from app.db.sqlalchemy import session
from app.db.example.models import ExampleModel
class ExampleRepo:
async def create(self, text: str) -> None:
query = insert(ExampleModel).values(text=text)
async with session.begin():
await session.execute(query)Для генерации миграций используется alembic. Все файлы миграции хранятся в директории app.db.migrations. Для генерации новой миграции необходимо создать модель sqlalchemy и выполнить команду:
$ alembic revision --autogenerate -m "migration message"Новый файл миграции будет создан в следующей директории:
├── app
│ ├── db
│ ├── migrations
│ ├── versions
│ ├── 0123456789ab_migration_message.py
Чтобы применить все миграции, следует выполнить команду:
$ alembic upgrade headили:
$ alembic upgrade 1для применения только одной миграции.
Для отмены одной миграции необходимо выолнить:
$ alembic downgrade -1Вся бизнес-логика проекта выносится в пакет app.services. Бизнес-логика - логика, характерная только для данного проекта. Туда же выносятся запросы, клиенты для использования API сторонних сервисов, обработка данных по заданным (в ТЗ) правилам.
Структура следующая:
├── app
│ ├── services
│ │ ├── errors.py - исключения, вызываемые в клиенте
│ │ ├── client.py - клиент для обращения к стороннему сервису
Новые переменные среды можно добавить в класс AppSettings из файла app/settings.py. Если у переменной нет значения по умолчанию, то оно будет браться из файла .env.
Чтобы использовать эту переменную в боте, необходимо:
from app.settings import settings
...
settings.MY_VARℹ️ Инфо Через переменные среды можно указывать окружения, в которых будет запускаться бот.
test,devилиprod. Просто добавьте в файл.envпеременнуюAPP_ENV=prod.
Для запуска всех форматеров необходимо выполнить скрипт:
$ ./scripts/formatДля запуска всех линтеров необходимо выполнить скрипт:
$ ./scripts/lintИспользуется для форматирования кода к единому стилю: разбивает длинные строки, следит за отступами и импортами.
⚠️ Примечание
В некоторых моментах isort конфликтует с black. Конфликт решается настройкой файла конфигурацииsetup.cfg.
Используется для сортировки импортов. Сначала импорты из стандартных библиотек python, затем из внешних библиотек и в конце из модулей данного проекта. Между собой импорты сортируются по алфавиту.
Используется для удаления неиспользуемых импортов и переменных.
Используется для проверки типов. Помогает находить некоторые ошибки еще на стадии разработки.
⚠️ Примечание
К сожалению, не все библиотеки поддерживают типизацию. Чтобы подсказать это mypy необходимо добавить следующие строки в файл конфигурацииsetup.cfg:
[mypy]
# ...
[mypy-your_library_name.*]
ignore_missing_imports = True
Некоторые же наоборот имеют специальные плагины для mypy, например pydantic:
[mypy]
plugins = pydantic.mypy
...
[pydantic-mypy]
init_forbid_extra = True
init_typed = True
warn_required_dynamic_aliases = True
warn_untyped_fields = True
Используется для комплексной проверки. Анализирует допустимые имена перменных и их длину, сложность вложенных конструкций, правильную обработку исключений и многое другое. Для каждого типа ошибок есть свой уникальный номер, объяснение, почему так делать не стоит, и объяснение, как делать правильно. Список ошибок можно посмотреть тут.
ℹ️ Инфо
В некоторых редких случаях можно игнорировать правила линтера. Для этого необходимо либо прописать комментарий с меткойnoqaна проблемной строке:var = problem_function() # noqa: WPS999либо указать
ignoreошибки вsetup.cfg:[flake8] # ... ignore = # f-strings are useful WPS305,Также можно исключать модули и пакеты.
Все тесты пишутся с помощью библиотеки pytest. Запустить тесты можно командой:
$ pytestВо время тестирования поднимается docker-контейнер с БД. Порт выбирается свободный, поэтому запущенная локально БД не будет мешать. Если вы хотите запускать тесты используя вашу локальную БД, необходимо добавить в .env переменную DB=1, либо выполнить команду:
$ DB=1 pytestℹ️ Инфо
Поскольку pytest не умеет в асинхронные тесты, для работы с ними ему необходим плагин pytest-asyncio.
Покрытие показывает процент протестированного исходного кода, как всего, так и отдельных модулей. Покрытие помогает определить какие фрагменты кода не запускались в тестах. Для генерации отчетов покрытия используется плагин pytest-cov.
Чтобы не прописывать все флаги каждый раз, можно использовать эти скрипты:
$ ./scripts/test
$ ./scripts/html-cov-testПервый выводит отчет в терминале, второй генерирует отчет в виде htmlстраниц с подсветкой непокрытых участков кода.