Skip to content

ExpressApp/bot-template

Repository files navigation

Bot Template Tutorial

Введение

ℹ️ Инфо
Для взаимодействия с платформой botx используется библиотека pybotx. В документации можно посмотреть примеры её использования. Перед прочтением данного туториала следует с ней ознакомиться.


Вне зависимости от решаемых ботами задач, во всех повторяется один и тот же код.

Чтобы разработчикам не приходилось писать базовый однообразный код для каждого нового бота, существует шаблонный проект - Bot Template. Он задает структуру проекта и основной стек технологий. Это значительно упрощает разработку, позволяя сконцентрироваться на реализации бота.


1. Развертывание из шаблона и структура проекта

Для развертывания проекта необходимо установить 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          - конфигурация линтеров и тестов

2. Запуск проекта

Настройка окружения

  1. Устанавливаем зависимости проекта через poetry:
$ poetry install
  1. Определяем переменные окружения в файле .env. Примеры переменных окружения находятся в файле example.env.
  2. Запускаем postges и redis используя docker-compose:
$ docker-compose -f docker-compose.dev.yml up -d
  1. Применяем все миграции для инициализации таблиц с помощью alembic:
$ alembic upgrade head
  1. Запускаем бота как приложение 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

3. Добавление нового функционала

3.1. Команды бота

Структура пакета команд

Команды бота находятся в пакете 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)

3.2. Взаимодействие с БД

Создание новых моделей

Взаимодействовать с новыми таблицами можно через модели 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

3.3. Сервисы и бизнес-логика

Вся бизнес-логика проекта выносится в пакет app.services. Бизнес-логика - логика, характерная только для данного проекта. Туда же выносятся запросы, клиенты для использования API сторонних сервисов, обработка данных по заданным (в ТЗ) правилам.

Структура следующая:

├── app
│   ├── services
│   │     ├── errors.py - исключения, вызываемые в клиенте
│   │     ├── client.py - клиент для обращения к стороннему сервису 

3.4. Конфиги и переменные среды

Новые переменные среды можно добавить в класс AppSettings из файла app/settings.py. Если у переменной нет значения по умолчанию, то оно будет браться из файла .env. Чтобы использовать эту переменную в боте, необходимо:

from app.settings import settings
...
settings.MY_VAR

ℹ️ Инфо Через переменные среды можно указывать окружения, в которых будет запускаться бот. test, dev или prod. Просто добавьте в файл .env переменную APP_ENV=prod.


4. Линтеры и форматирование кода

Запуск

Для запуска всех форматеров необходимо выполнить скрипт:

$ ./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,

Также можно исключать модули и пакеты.


5. Тестирование

5.1. Запуск и добавление тестов

Все тесты пишутся с помощью библиотеки pytest. Запустить тесты можно командой:

$ pytest

Во время тестирования поднимается docker-контейнер с БД. Порт выбирается свободный, поэтому запущенная локально БД не будет мешать. Если вы хотите запускать тесты используя вашу локальную БД, необходимо добавить в .env переменную DB=1, либо выполнить команду:

$ DB=1 pytest

ℹ️ Инфо
Поскольку pytest не умеет в асинхронные тесты, для работы с ними ему необходим плагин pytest-asyncio.

5.2. Покрытие

Покрытие показывает процент протестированного исходного кода, как всего, так и отдельных модулей. Покрытие помогает определить какие фрагменты кода не запускались в тестах. Для генерации отчетов покрытия используется плагин pytest-cov.

Чтобы не прописывать все флаги каждый раз, можно использовать эти скрипты:

$ ./scripts/test
$ ./scripts/html-cov-test

Первый выводит отчет в терминале, второй генерирует отчет в виде htmlстраниц с подсветкой непокрытых участков кода.