Быстрый запуск pet-проекта на Python и PostgreSQL

Photo by charlesdeluvio on Unsplash

Уверен, небольшие pet-проекты полезны не только для прокачивания навыков, но и для отвлечения от рабочей рутины и — нередко — для решения небольших практических задач. 

Курс рубля, как водится, — всегда актуальная тема. И на естественное желание быть в курсе курса рубля, лично у меня, возникает такое же естественное желание реализовать pet-проект. И естественно, это нужно сделать в виде телеграм-бота. 

Здесь вы можете посмотреть пример моего бота. Почти такой же вы можете запустить и сами, пользуясь исходниками. Именно с этим и поможет данная статья.

Про использование Python понятно из сабжа, ну, а добиться максимально быстрого запуска нам позволят встроенные CI/CD процессы от Amvera, которые буквально из кода соберут работающий сервис. 

Функциональность и архитектура бота

Количество строк кода вышло внушительным, так что не будем разбирать их детально в статье. Тем не менее, для понимания заложенной функциональности, опишу в данном разделе как бот проектировался.

Чтобы все выглядело чисто и SOLID«но, естественно нужно начать с проектирования решения.

Забегая вперед, получилось не так SOLID«но, как хотелось бы — расценивайте это как возможность похоливарить и предложить схему получше.

Вот что я накидал в draw.io думая о боте и пытаясь решить о возможных расширениях функциональности в дальнейшем:

Layered-архитектура

Layered-архитектура

Основные используемые библиотеки:

  • PTB (Python Telegram Bot)

  • APS (Advanced Python Scheduler) — на самом деле в рамках python-telegram-bot[job-queue]

Что можно сразу заметить: на схеме нет инфраструктурных компонентов. «А как же PostrgreSQL?», справедливо спросите вы. В нашем случае PostgreSQL нужен для хранения джобов на расписании -, а это реализуется в рамках упомянутого выше APS, внутри которого эти слои и имплементированы. 

Итак, что же может делать бот:

  1. Конвертировать заданную сумму и валюту в рубли

    • Курс на основе официального курса ЦБ РФ

    • Простейшие математические операции (сложение, вычитание, умножение и деление)

  2. Подписаться на регулярные уведомления по запрошенной конвертации

Кажется, достаточно для минимально необходимой пользы: и друзей не стыдно пригласить пользоваться своим ботом. 

Если не нужны регулярные уведомления

Если хочется запустить бот, но без уведомлений и (как следствие) без использования PostgreSQL, то — как водится в SOLID’ной архитектуре — можно буквально в одной строке это скорректировать:

#вместо:
subscriber = PTBScheduler()

#сделать:
subscriber = None

Ну, а теперь переходим к самому интересному — развертыванию бота.

Развертывание бота в Amvera Cloud

Как упомянуто в начале статьи, для развертывания будем использовать сервис Amvera Cloud, который избавит от оверхеда, связанного с деплоем приложений.

1. Пререквизиты

Перед стартом проверьте пререквизиты:

  1. У вас есть аккаунт в Amvera Cloud

  2. На вашей машине установлен Git

Предполагаются базовые знания и навыки в следующих топиках:

  • Git

  • Docker

  • Python

2. Предварительная подготовка

Выделены следующие этапы подготовки:

  1. Создание бота в ТГ (через BotFather)

  2. Запуск и проверка PostgreSQL

Итак, поехали.

2.1. Создание бота в ТГ

Достаточно стандартный набор шагов — можно воспользоваться буквально первым попавшимся туториалом из гугла яндекса, например таким или таким.

В коде предусмотрена возможность работы в inline-режиме, но его нужно активировать с помощью того же BotFather. Для этого:

1. Выберите своего бота из списка в BotFather

2. Нажмите «Bot Settings»:

5147b9bdf7c83d3d354fb1dc26660ab8.png

3. Нажмите «Inline Mode»:

e68c4d79f118aab05157ebc587079d64.png

4. По умолчанию, режим выключен, для его включения необходимо воспользоваться верхней кнопкой:

713c95264ef7b2197bfd1cb6c87c2321.png

Там же можно настроить placeholder — то что будет отображаться в inline-режиме, например:

Ну не красота ли?

Ну не красота ли?

2.2. Запуск и проверка PostgreSQL

Полноценное описание о запуске PostgreSQL в Amvera есть в соответствующем детальном гайде.

Достаточно просто настраивается запуск самой БД:

f07d10e6f91d1e1cd065dfaeb7e63bdd.png

А уже в отдельном проекте — pgAdmin — можно к ней подключиться:

1. Создать новый сервер:

3cace93db815dee4f53a8cee95157ff2.png

2. Ввести данные БД для подключения:

2c8bbb2743a13ea8ec70e519e472d1b9.png

Помните, что адрес подключения (хост) можно найти в настройках проекта с PostgreSQL:

df95dabcddf708ac306ff600c2cb2bae.png

После успешного подключения в интерфейсе будет видна БД:

a8abca0f33be17b1617e29faaab606b5.png

3. Развертывание бота

Для развертывания бота мы пройдем следующие шаги:

  1. Скачать репозиторий с кодом

  2. Создать проект в Amvera

  3. Загрузить исходники в Amvera

    1. Загрузка через Git (рекомендуемый способ)

    2. Загрузка файлов на сайте

  4. Проверка бота

Итак, поехали.

3.1. Скачать репозиторий с кодом

Доступны 2 варианта:

  1. С использованием командной строки

  2. Без использования командной строки

Рекомендуется конечно же первый вариант.

3.1.1. С использование командной строки

Я подразумеваю, что вы воспользовались инструкцией для установки Git, которую я упоминал ранее. В этом случае использование командной строки (а нам нужна только утиллита Git) будет выглядеть одинаково для всех платформ (Linux-Windows-MacOS).

1. Открываем командную строку:

0202eff6e2c16f47f005fa4ec8d2e419.png

2. Я предварительно создал папку bot, в которой и будет размещен код. Создайте и перейдите в нужную вам папку:

e2e0cb7ee4ccde624e85986a3903b45b.png

mkdir bot
cd bot
pwd

3. Скопируйте ссылку на репозиторий и выполните загрузку:

dec023b5724cbc09a9f63134b4bf89ac.png

Поздравляю, вы великолепны :)

3.1.2. Без использования командной строки

Без командной строки все выполняется не менее просто:

1. Создайте папку, в которой будет располагаться код

2. В браузере перейдите по ссылке на репозиторий

3. Скачайте архив с кодом:

a20ef9ac9c2f0efa9cc05b97f4aa881a.png

4. Распакуйте архив в созданную на шаге 1 папку

Поздравляю, вы великолепны :)

3.2. Создать проект в Amvera

Ниже пошаговый гайд по созданию проекта:

1. Залогиньтесь в Amvera и перейдите на вкладку проектов

2. Нажмите «Создать»:

241781bb9409478d248c95bb7fd68d49.png

3. Заполняем общую информацию и нажимаем «Далее»:

594e6e1a7107b054deb5968106cdfd78.png

  • Название — можно выбрать любое какое нравится.

  • Тип сервиса — приложение.

  • Тарифный план — самый минимальный подойдет для целей такого бота. Что удобно в Amvera — в дальнейшем можно будет расширить мощности буквально в пару кликов.

4. Шаг с загрузкой данных пропустим, просто нажав «Далее» — его можно выполнить позднее:

d44bd092c3289b93e8a79bea28ce7ed2.png

5. Задаем настройки конфигурации (по сути — конфигурации сборки проекта) и скачиваем amvera.yml:

c858c900cc39cafb77965519cae33fa0.png22be3ce9866bb195fe85a71cb6f143a8.png

Отмечу, что в репозитории с исходным кодом уже есть Dockerfile, который создает позволяет создать необходимый Docker-образ.

Выбираем:

  • Окружение: docker

  • Инструмент: docker

  • Dockerfile: Dockerfile (название файла в репозитории с кодом)

  • persistenceMount: /app/data

Про persistenceMount расскажу чуть подробнее несколько позже. 

Окружения Amvera

У Amvera есть широкий список окружений:

5e51cc1986814045c75f0468e9db9f92.png

Для каждого окружения есть свой инструмент, с помощью которого будет производиться сборка образа. 

В случае с Python доступен де-факто стандарт: pip. 

У вас может возникнуть справедливый вопрос: «почему же окружение выбрано Docker, а не Python». На самом деле можно выбрать и Python: для этого только потребуется предварительно создать список используемых библиотек — командой:  

pip freeze > requirements.txt

В моем случае использование Docker имеет с одной стороны наследственный характер (раньше не было таких сервисов как Amvera, и все равно приходилось писать Dockerfile самому), с другой — бОльший контроль над созданием образа. Это позволяет производить более тонкую настройку, и встраивать другие процессы — например, регулярную проверку образов на уязвимости (к примеру, с помощью snyk и подобных сервисов).

И последнее что важно отметить в блоке с Docker: можно использовать и уже построенный образ, размещенный на публично доступном ресурсе (тот же dockerhub).

И отдельно отмечу варианты с db:

4ba2472ddf3c9170e42c2bc580e222f5.png

Таким образом, установить можно далеко не только PostgreSQL, но и кучу других БД. 

Безусловно не стоит забывать про вариант с Docker — там полная свобода: можно использоваться абсолютно любой публично доступный образ. 

Нажимаем «Завершить» для создания проекта

6. В созданном проекте переходим на «Переменные», предварительно определившись с их наполнением:

d10b3e856d571bbfa8dac75c98e45179.png

Это специальный раздел, в котором задаются переменные окружения — по сути, конфигурационные параметры. В данном исходном коде предусмотрены следующие параметры:  

  • Относящиеся к боту:

    • BOTNAME — название бота, заданное в BotFather

    • TOKEN — токен, выданный ранее BotFather

    • START_MESSAGE — сообщение, которое будет выведено при вводе команды /start

    • HELP_MESSAGE — сообщение, которое будет выведено при вводе команды /help

    • PERSISTENCE_FILE — путь до файла, в котором будут сохраняться промежуточные объекты для inline-клавиатуры

  • Относящиеся к расписанию:

    • JOB_PERSISTENCE — флаг (1 или 0), подключаем ли базу данных для хранения джобов

    • PSQL_URL — строка подключения к базе данных 

  • Относящиеся к логике разбора запросов пользователей:

    • REGEXP — регулярка, которая позволяет проверить корректность введенного выражения (особенно актуально, если там есть вычисления)

  • Относящиеся к парсеру источника курса валют:

    • URL — адрес, из которого можно забрать актуальные данные по курсу валют

    • TIMEOUT — таймаут ожидания ответа при запросе по указанному URL (на случай неполадок в сети)

Разберем некоторые из них детальнее:

PERSISTENCE_FILE

В боте используются inline-клавиатуры, например:

81edd49bbf2442f72e5bd4d0a87931f9.png

Для корректной обработки нажатия в каждой кнопке необходимо передать контекст:

  • Какое действие будет произведено (подписка или отписка)

  • Если подписка — то на какое выражение (в данном примере: 1 EUR)

  • Если подписка — то какой тип (каждый день / каждую неделю / каждый месяц)

Этот контекст хранится в Python dict-структуре, которая сериализуется в файл. И переменная PERSISTENCE_FILE как раз указывает путь до этого файла. Если не сохранять состояние в файл, то при перезагрузке бота весь контекст по всем кнопкам потеряется. 

Необходимо задать значение:  data/callback_persistence.pickle

Контейнеры

Немного из базовой части Docker: важно помнить, что наше приложение запускается в виде контейнера (из образа), который существует пока приложение работает. Перезагрузка приложения = удаление контейнера и создание нового, то есть «откат» к состоянию в образе. Способы сохранения какого-либо состояния следующие:

  • С помощью примонтированных persistence-областей (тот самый persistenceMount),  

  • С помощью внешних систем, выступающих в качестве Persistence Storage: например, тот же PostgreSQL. 

Наличие PERSISTENCE_FILE напрямую связано с persistenceMount, который упоминался выше в конфигурации. 

А теперь следите за руками:

1. В конфигурации проекта Amvera мы указали: /app/data

  • Это означает, что папка /app/data является примонтированной, и данные хранящиеся в ней не будут стираться вне зависимости от рестарта приложения

2. В Dockerfile в репозитории с кодом указано: WORKDIR /app

  • Это означает, что все команды в файловой системе (в том числе навигация из Python) производится относительно этой стартовой точки (директория /app)

8e9cbb9d20a69e216d19bcfd4d2bc538.png

3. Значение переменной окружения PERSISTENCE_FILE мы указываем: data/callback_persistence.pickle

  • Чтобы получить полный путь, нужно добавить стартовую точку, то есть получится: /app/data/callback_persistence.pickle

  • Таким образом, файл находится внутри примонтированной области, а значит он будет сохранен между рестартами приложения

JOB_PERSISTENCE

Числовой флаг (1 или 0), регулирующий хранение джобов уведомлений. Если стоит 0, то все хранится только в памяти, и между перезапусками бота весь контекст теряется.

Если возможность потерять все джобы на расписании допустима, то можно смело оставлять 0. Важное следствие: PostgreSQL в этом случае не будет использоваться.

PSQL_URL

В боте для подключения к базе данных используется SQL Alchemy — возможно, самый распространенный инструмент работы с БД из Python. 

Строка подключения к PostgreSQL в этом случае должна выглядеть в следующем формате:

postgresql://:@[:]/

Вспоминаем все настройки, которые задавали при подключении к БД из pgAdmin:

17ddd8bc943710f9aa049526ec2f27ec.png

В моем случае получается так:

postgresql://tgbot:***@amvera-dikirilov-***-rw/converter

Вместо *** конечно же должны быть пароль и полный хост соответственно.

REGEXP

Это регулярное выражение призвано проверять, является ли первая часть запроса математически корректным выражением. 

На текущем этапе мне не хотелось в рамках pet-проекта заниматься полноценной реализацией польской записи, поэтому было использовано простое (но привносящее уязвимости) решение: функция eval. Для минимизации уязвимости и в целом проверки, что оно сработает (если выражение неверное — Exception), нужна эта регулярка. 

Даже с проверкой регуляркой я не рекомендую использовать eval () в продуктивных решениях.

Для проверки использую следующее выражение: ^([-+/]?\d+(.\d+)?)$

URL

Бот умеет парсить XML, которая любезно предоставляется ЦБ по следующему адресу:

http://www.cbr.ru/scripts/XML_daily.asp

TIMEOUT

Этот параметр скорее для возможности тюнинга в случае нетривиального доступа в сеть, но в случае с Amvera каких-либо проблем не замечено. 

Я обычно выставляю значение 5 — уж точно не упремся при выгрузке ~100+ курсов валют.

7. Вносим переменные в проект

Теперь все эти переменные необходимо занести в проект в Amvera. В первый раз это немного нудно, зато в дальнейшем позволит изменять ключевые точки в приложении без необходимости изменения кода и пересборки образа (потребуется только рестарт). 

В переменные окружения попадают как переменные, так и секреты:

cbcb1f0337faed6fb4d96620239b83d3.png

С точки зрения приложения разницы между ними нет.

С точки зрения хранения и видимости — разница есть. 

Правило очень простое: любая чувствительная информация должна быть секретом: пароли, токены, etc.

По итогу должно получиться примерно так (список конечно же прокручивается, просто только это умещается в окне 13-дюймового устройства):

e903fe458abbf58225abadda2abed1fd.png

3.3. Загрузка и запуск бота

Для загрузки кода в Amvera можно воспользоваться как Git, так и загрузкой непосредственно на сайте. 

Рекомендуется, конечно же, вариант 1 — с него и начнем.

3.3.1. Загрузка через Git

Ранее мы уже загрузили репозиторий к себе:

4b941abe8c38ca8692e802d4eaa6247f.png

Далее пошаговый гайд:

1. Подключить Amvera репозиторий как удаленный. Команду для этого можно найти на вкладке «Репозиторий» в проекте. В моем случае это выглядит так:

6aa23e93c174811fee26fa26cd4ebe8d.png

Обратите внимание, что в репозитории должен был появиться amvera.yml

В командной строке:

c2c13e643d6e4ceecc813ddb60741211.png

В процессе скорее всего потребуются логин и пароль от аккаунта Amvera. 

2. Выполнить merge веток в своем репозитории, для этого необходимо выполнить команды:

git pull amvera master

В процессе откроется редактор, в котором нужно будет заполнить сообщение для коммита с мерджем. Скорее всего это будет vi (m). Меня устраивает дефолтное сообщение, поэтому просто выхожу с сохранением (набрав :wq):

de3dee448c826ec972e1c89a9f546073.png

Работа в редакторе vi (m) — это отдельная тема (в том числе, для шуток и мемов). Для быстрого ознакомления с минимально необходимым можно посмотреть сюда.

Мой фаворит среди мемов

Мем из интернетов

Мем из интернетов

Если знаете еще более «цепляющие» — кидайте в комменты

После этого произойдет мердж файлов из репозитория Amvera в наш репозиторий:

a6328018fed730a440b73719d9cabcc1.png

Как мы видели, кроме файла конфигурации Amvera.yml там ничего нет (папка .git есть во всех репозиториях, там служебная информация) — только этот файл и загрузился. 

Возможная ошибка

Сразу стоит отметить, что если вы не настраивали Git на своей машине (как я не делал этого на удаленном сервере), то скорее всего вы получите такую ошибку:

1cd408f857ffd2681f747a26b4389eb6.png

Благо, здесь сразу представлена инструкция, что с этим делать: задать почту и имя пользователя Git с помощью команд:

git config --global user.email "you@example.com”
git config --global user.name "Your Name”

Можно и без global, если эти параметры хотите задать только для этого репозитория.

3. Загрузка исходников в Amvera

А теперь мы перешли к финальному шагу, который не просто загрузит файлы в Amvera, но и запустит процесс сборки.

По сути нам нужно просто актуализировать репозиторий в Amvera, что делается одной командой:

git push remote amvera master

fa62231878bd97b65af9373fbd0b7055.png

После выполнения команды на вкладке «Репозиторий» проекта можно увидеть новые файлы, а также автоматически запустившуюся сборку по факту коммита:

85a86846834345d4d7bb7c96df88f402.png

Проверим, что сборка успешна:

0058f44d63c20c49e610090022ac6caa.png

Последние логи («Pushing image…» и «Pushed…») говорят о том, что образ успешно сформирован и загружен во внутреннее хранилище образов. Далее проект автоматически запускается — на дальнейших шагах проверим, что работает сам бот.

3.3.2. Загрузка файлов на сайте

Альтернативный способ загрузки — прямо в браузере. Для этого необходимо зайти в проект на вкладку «Репозиторий» и нажимаем «Загрузить данные»:

b84816e5f9e670544b96e562360c708f.png

Поскольку amvera.yml уже есть в репозитории Amvera, а при загрузке мы только добавляем файлы, а не синхронизируем как в случае с git, нам не нужно предварительно скачивать этот файл.

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

caf09ca4f28453cd264cf8c9fbcae4d2.png

При загрузке файлов не нужно выбирать .git* папки и файлы, то есть:

251b32d2bf9bfa23455eb9230d3c8c86.png

Нажимаем «Готово»:

a959348778e76f2946bd8fe11b0e56dd.png

Если сборка не началась автоматически, переходим на вкладку «Конфигурация», листаем вниз и нажимаем «Собрать»:

f4ce7e88ca673b8b53eb6168a9d13323.png

Проверим, что сборка успешна:

6f82cb7668e36d887c774d53e4284ce6.png

Последние логи («Pushing image…» и «Pushed…») говорят о том, что образ успешно сформирован и загружен во внутреннее хранилище образов. Далее проект автоматически запускается — на дальнейших шагах проверим, что работает сам бот. 

3.4. Проверка бота

Для начала убедимся, что бот успешно запустился. На вкладке «Лог приложения» должны появиться строки примерно как тут:

0c2a5206a1a9aa50187a38a5c7ab3f50.png

После «Start polling» лога можно непосредственно тестировать бот:

5761cfba29e1e73ba00b3158d5124589.jpeg

А также в inline-режиме:  

9bf5a11943577510d11608b72101f805.jpeg650aa540dda153143b537dcbce318697.jpeg

Из примечательного: как только появятся сообщения с inline-клавиатурой, в разделе с data появится наш файл, который хранит состояние с контекстом кнопок:

ae680457464bbbc538d03d28f76120b5.png

Помимо этого надо проверить, что и подключение к БД произошло успешно.

Для этого зайдем в pgAdmin и проверим, что появилась таблица apscheduler_jobs:

87659e7f07094a9d56f7d305eba8f081.png

Теперь бот можно перезагружать / обновлять сколько угодно — данные о подписках не потеряются.

Вместо заключения

Часть с описанием развертывания бота может показаться достаточно длинной, но это только потому что хотелось покрыть все возможные вопросы. 

Прелесть CI/CD-процесса, любезно предоставляемого Amvera, раскрывается в дальнейшей работе: сопровождении и доработках проекта. Как только изменение готово, одной командой (git push amvera master) можно сразу все раскатить и пользоваться.

One more thing

Кстати, удобно сделать как минимум 2 бота (для TEST и PROD), которые будут соответствовать 2-м разным проектам в Amvera. Оба проекта можно подключить к одному и тому же репозиторию под разными именами (например, amvera-test и amvera-prod). 

Тогда для обновления тестовой версии будет достаточно выполнить команду:

git push amvera-test master

А когда все проверили — раскатить изменения на основной:

git push amvera-prod master

Но это конечно только для самых любимых pet-проектов :)

P.S. Если решите развернуть бота по этому туториалу, и по ходу дела возникнут вопросы — welcome в комменты или в личку.

© Habrahabr.ru