ℹ️ Инфо
Для взаимодействия с платформой botx используется библиотека pybotx. В документации можно посмотреть примеры её использования. Для реализации SmartApp используется библиотека pybotx-smartapp-rpc. Перед прочтением данного туториала следует с ней ознакомиться.
Шаблон решает проблему написания повторяющего кода в самом начале работы над проектом. Уже существует шаблон для разработки ботов bot-template
Но структура бота и SmartApp отличается друг от друга, поэтому был создан этот шаблон.
Для развертывания проекта необходимо установить copier и выполнить команду:
$ copier smartapp-template smartapp-exampleСтруктура шаблонного SmartApp состоит из нескольких следующих пакетов и модулей:
.
├── app
│   ├── api            - реализация http роутов для приложения, включая необходимые для smartapp
│   ├── bot            - команды бота и вспомогательные функции для них
│   ├── caching        - классы и функции для работы с in-memory БД
│   ├── db             - модели, функции для работы с БД и миграции
│   ├── schemas        - сериализаторы, енамы, доменные модели
│   ├── services       - сервисы с логикой (бизнес-логика)
│   ├── smartapp       - rpc - методы и аргументы для smartapp
│   ├── smartapp-files  - статические файлы
│   ├── constants.py   - константы
│   ├── logger.py      - логгер
│   ├── main.py        - запуск сервера с инициализацией необходимых сервисов
│   └── settings.py    - настройки приложения
├── extensions         - вспомогательные расширения для шаблона
├── 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- Запускаем SmartApp как приложение 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.smartapp.rpc_methods и группируются в отдельные модули в зависимости от логики.
Обработчики добавляются с помощью RPCRouter.
Если в модуле становится слишком много команд, следует разбить его на новые модули и сложить в один пакет с названием старого модуля. Например, так:
smartapp
├── rpc_methods
│   ├── common.py
│   ├── jira
│       ├── projects.py
│       ├── issues.py
Для добавления модуля с обработчиками нужно импортировать его из модуля нужного вам модуля в app/smartapp/smartapp.py и добавить его в routers smartapp:
from pybotx_smartapp_rpc import SmartAppRPC
from app.smartapp.middlewares.smartlogger import smart_logger_middleware
from app.smartapp.rpc_methods import common, jira
smartapp = SmartAppRPC(
    routers=[common.rpc, jira.rpc],
    middlewares=[smart_logger_middleware],
)Взаимодействовать с новыми таблицами можно через модели 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ℹ️ Инфо Через переменные среды можно указывать окружения, в которых будет запускаться smartapp.
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страниц с подсветкой непокрытых участков кода.