Инструкция пользователя Облака УВА Advanced

Правила пользования Облаком УВА Advanced

  1. Сбор персональных данных с использованием облачных ресурсов УВА запрещен. Перед началом работы, пожалуйста, изучите 152-ФЗ.
  1. Не отключайте опцию подключения к виртуальной машине с правами root. Нарушение этого правила может привести к блокировке вашей машины.
  1. Не храните в базах данных и объектном хранилище информацию, не предназначенную для широкой аудитории. Пользование сервером PostgreSQL и бакетами в Облаке УВА организовано по принципу
    💡

    все видят всех и могут выгружать всё, но редактировать и удалять сущность может только ее создатель

  1. Давайте сущностям осмысленные имена. В названиях однотипных придерживайтесь шаблона/структуры. При создании базы данных заполняйте поле Комментарий. Пожалуйста, думайте об удобстве использования материалов вашими коллегами.
  1. Вопросы по работе сервиса, предложения и обнаруженные ошибки, пожалуйста, направляйте по адресам:

Что вы получаете на руки

Виртуальная машина

  1. Конфигурация: 4 vCPUs, 8 GB RAM, Intel Ice Lake 2.6GHz, диск High I/O 60 GB, Ubuntu 24.04. Интернет 50 Mbps.
  1. Внешний IP для подключения к виртуальной машине по SSH. В примере Подключение к виртуальной машине: 188.72.107.76.
  1. IP вашей виртуальной машины в Облаке УВА. Понадобится, если делать API.
  1. Имя пользователя, состоящего в группе sudo, и его пароль. Пользователь в примере user-demo.

Доступ к серверу PostgreSQL

  1. Имя сервера PostgreSQL и его IP-адрес. В примере Подключение к серверу PostgreSQL: postgres-demo, 10.0.14.19.
  1. Имя вашей учетной записи на сервере и пароль от нее. Пользователь в примере user-demo.

Доступ к объектному хранилищу

  1. Access key, secret key, endpoint и region для управления с помощью клиентов объектных хранилищ или boto3.
  1. Имя созданного для вас бакета. В примере Подключение к объектному хранилищу: user-demo-bucket.

Подключение к виртуальной машине

  1. Сгенерируйте пароль (passphrase) для SSH-ключа и сохраните его в надежном месте. Пожалуйста, не пренебрегайте созданием passphrase — он понадобится для подключения к серверу PostgreSQL. Например, создайте буквенно-цифровую последовательность длиной 50 символов этим генератором.
  1. Сгенерируйте SSH-ключ на локальной машине:
    $ ssh-keygen
    Generating public/private ed25519 key pair.
    Enter file in which to save the key (/c/Users/user/.ssh/id_ed25519):
    Enter passphrase for "/c/Users/user/.ssh/id_ed25519" (empty for no passphrase):
    Enter same passphrase again:
    Your identification has been saved in /c/Users/user/.ssh/id_ed25519
    Your public key has been saved in /c/Users/user/.ssh/id_ed25519.pub
    The key fingerprint is:
    SHA256:apfCa4esUZko7sJjHEwkE2eMpvrRUQlucdrf6RXZ9Y0 user@AsusVivobook
    The key's randomart image is:
    +--[ED25519 256]--+
    |.+o o...        .|
    |+=.. =o      o oo|
    |=.  +..     o E o|
    |.. ... + . . .   |
    |+ ....+ S o .    |
    |.+...o . o .     |
    |o.o...=.o .      |
    |.*.  o++.        |
    |..o .o..         |
    +----[SHA256]-----+

  1. Скопируйте публичную часть ключа, id_ed25519.pub, с локальной машины на виртуальную. Измените путь до ключа на локальной машине на свой.
    $ scp C:/Users/user/.ssh/id_ed25519.pub user-demo@188.72.107.76:/home/user-demo/.ssh/authorized_keys
    user-demo@188.72.107.76's password:
    id_ed25519.pub                                          100%   99     7.3KB/s   00:00

  1. Подключитесь к виртуальной машине по SSH:
    $ ssh user-demo@188.72.107.76
    Enter passphrase for key '/c/Users/user/.ssh/id_ed25519':
    Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-71-generic x86_64)
    
     * Documentation:  https://help.ubuntu.com
     * Management:     https://landscape.canonical.com
     * Support:        https://ubuntu.com/pro
    
     System information as of Wed Jul 30 02:20:05 PM MSK 2025
    
      System load:  0.0               Processes:             113
      Usage of /:   8.6% of 39.26GB   Users logged in:       0
      Memory usage: 10%               IPv4 address for eth0: 10.0.14.15
      Swap usage:   0%
    
    
    Expanded Security Maintenance for Applications is not enabled.
    
    0 updates can be applied immediately.
    
    Enable ESM Apps to receive additional future security updates.
    See https://ubuntu.com/esm or run: sudo pro status
    
    
    
            Welcome to Elastic Cloud Service

Подключение к серверу PostgreSQL

Виртуальная машина как безопасная точка доступа к серверу PostgreSQL

  1. Установите PostgreSQL на локальную машину, включая предлагаемый во время установки pgAdmin.
  1. Выполните в pgAdmin первое подключение к серверу по SSH, следуя подсказкам на скриншотах ниже.

  1. Вход при последующих подключениях:

  1. Создайте базу данных и схему (schema) внутри нее. Внутри схемы вы сможете создавать таблицы и другие сущности. Скриншот ниже показывает пример ошибки, которую получит другой пользователь при попытке изменить вашу таблицу.

  1. Быть владельцем сущности не всегда означает право изменять ее: владелец может делегировать право, хотя сам при этом может им активно и не обладать. Чтобы писать в таблицы, например, с помощью SQLAlchemy, выполните небольшая донастройку по примеру на скришотах ниже. Откройте свойства принадлежащих вам, т.е. созданных вами, сущностей (БД, схема, таблица и т.д.) и дайте самому себе или коллегам необходимые права. Во вкладке Security задаются права на уже созданные сущности, в Default Privileges — на те, которые будут созданы внутри редактируемой сущности.

  1. Если вы используете SQLAlchemy ORM, не забудьте указать имя схемы. Таблица в примере, reviews, живет на схеме bankiru-schema. Если не указать это явно, SQLAlchemy будет искать/писать таблицу на дефолтной для постгреса схеме public, к которой сейчас у пользователей доступа нет, и это приведет к InsufficientPermissionException. Для установки схемы воспользуйтесь одним из двух способов:
    • определите схему в метаданных базового класса Base, если схему нужно распространить на все дочерние модели;
    • переопределите схему в дандер-атрибуте __table_args__ конкретной модели, если схема задается для той или иной таблицы индивидуально.
    # https://github.com/EvgenyMeredelin/bankiru-dbapi/blob/main/models.py
    from sqlalchemy import MetaData
    from sqlalchemy.orm import declarative_base
    
    
    Base = declarative_base(metadata=MetaData(schema="bankiru-schema"))
    
    
    class Review(Base):
        __tablename__ = "reviews"
        __table_args__ = {"schema": "bankiru-schema"}
    
    	...

Подключение к объектному хранилищу

  1. Установите OBS Browser Plus на локальную машину.
  1. Выполните вход, следуя подсказкам на скриншоте.

  1. Найдите по имени созданный для вас бакет.

  1. Скриншоты ниже показывают, что добавлять и удалять объекты можно только из своего бакета.

Что дальше

VM + PG + S3 = 🚀

Пример простого сервиса, который использует все услуги Облака УВА Advanced, доступные на момент написания в августе 2025: виртуальная машина, сервер PostgreSQL и объектное хранилище. Пожалуйста, проходите после выполнения инструкций выше.

Сервис сбора и хранения негативных отзывов с banki.ru состоит из двух частей:

  1. Парсер. В начале наступившего дня собирает данные за истекшие сутки и отправляет их в API базы данных.
  1. API базы данных. Публикует пришедшие от парсера новые записи в базе данных. После каждого коммита (добавления или удаления записей) делает резервную копию базы данных в формате parquet и перезаписывает предыдущую версию в бакете объектного хранилища. Принимает запрос на получение из БД выборки, ограниченной датами, в заданном формате. Выборка предоставляется в ответе в виде прямой ссылки на скачивание файла из бакета объектного хранилища.

Рассмотрим разработку и запуск API как отдельного проекта. Ход работы с парсером аналогичен.

План действий. Разработка API ведется на [локальной] машине с Windows. Площадка запуска и постоянной работы — виртуальная машина с Ubuntu. Мы создадим и подготовим проект на локальной машине, создадим для него репозиторий на GitHub, склонируем репозиторий на виртуальную машину, запустим API и посмотрим на его работу.

Инструменты: uv, GitHub CLI (gh), systemd, HTTPie CLI, Pydantic Logfire.

В примере используется SSH-клиент Termius с двумя профилями: PowerShell и bash из поставки Git для Windows (C:\Program Files\Git\bin\bash.exe). Если не указано особо, команда выполняется в bash.

  1. Установка uv в Windows. В PowerShell:
    powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
    Downloading uv 0.8.9 (x86_64-pc-windows-msvc)  Installing to C:\Users\user\.local\bin
      uv.exe
      uvx.exe
      uvw.exe
    everything's installed!
    uv --version
    uv 0.8.9 (68c0bf8a2 2025-08-11)

  1. Создание проекта с помощью uv:
    /d/git
    $ uv init bankiru-dbapi
    Initialized project `bankiru-dbapi` at `D:\git\bankiru-dbapi`

  1. Создание виртуального окружения, установка зависимостей. Изучите документацию uv, если вашему проекту требуется какая-то конкретная версия интерпретатора и чтобы дать виртуальному окружению имя, отличное от .venv по умолчанию.
    /d/git
    $ cd bankiru-dbapi/
    /d/git/bankiru-dbapi (main)
    $ uv add aiobotocore alembic environs fastapi[standard] logfire[fastapi] pandas psycopg[binary,pool] py-spoo-url pyarrow sqlalchemy
    Using CPython 3.13.2
    Creating virtual environment at: .venv
    Resolved 104 packages in 814ms
    Installed 102 packages in 3.93s
     + aiobotocore==2.24.0
     + aiohappyeyeballs==2.6.1
     + aiohttp==3.12.15
     ...
     + wrapt==1.17.3
     + yarl==1.20.1
     + zipp==3.23.0

    pyproject.toml:

    /d/git/bankiru-dbapi (main)
    $ cat pyproject.toml
    [project]
    name = "bankiru-dbapi"
    version = "0.1.0"
    description = "Add your description here"
    readme = "README.md"
    requires-python = ">=3.13"
    dependencies = [
        "aiobotocore>=2.24.0",
        "alembic>=1.16.4",
        "environs>=14.3.0",
        "fastapi[standard]>=0.116.1",
        "logfire[fastapi]>=4.3.3",
        "pandas>=2.3.1",
        "psycopg[binary,pool]>=3.2.9",
        "py-spoo-url>=0.0.6",
        "pyarrow>=21.0.0",
        "sqlalchemy>=2.0.43",
    ]

    Заполним поле description. В проекте есть alembic, но создание системы миграций здесь не рассматривается. Для чтения переменных окружения используется environs. Обратите также внимание на замечательную библиотеку dataclass-wizard.

  1. Мы поработали и написали в корневой папке проекта несколько py-модулей и/или пакетов. Также мы добавим:
    • файл с переменными окружения .env ;
    • файл .env.example, чтобы использующие код знали, какие переменные окружения он ожидает;
    • файл .gitignore, исключающий из отслеживания .env и .venv ;
    • юнит systemd bankiru-dbapi.service для запуска API на виртуальной машине.
    /d/git/bankiru-dbapi (main)
    $ ls -a -1
    .
    ..
    .env
    .env.example
    .git
    .gitignore
    .python-version
    .venv
    README.md
    alembic
    alembic.ini
    bankiru-dbapi.service
    botocore_client.py
    database.py
    handlers.py
    logfire_auto_tracing.py
    main.py
    models.py
    pyproject.toml
    schemas.py
    uv.lock

  1. .env.example :
    /d/git/bankiru-dbapi (main)
    $ cat .env.example
    API_TOKEN=database_api_access_token
    LOGFIRE_TOKEN=logfire_project_write_token
    # all placeholders refer to the postgres server
    POSTGRES_URL=postgresql+psycopg://username:password@ip/db_name
    
    AWS_REQUEST_CHECKSUM_CALCULATION=when_required
    AWS_RESPONSE_CHECKSUM_VALIDATION=when_required
    OBS_BUCKET=obs_bucket_name
    OBS_ACCESS_KEY=obs_access_key
    OBS_SECRET_KEY=obs_secret_key
    OBS_REGION=ru-moscow
    OBS_ENDPOINT=https://obs.ru-moscow-1.hc.sbercloud.ru
    
    ECS_PRIVATE_IP=ecs_internal_ip
    ECS_PUBLIC_IP=ecs_external_ip
    # the port your FastAPI app listens to must be allowed by your security group,
    # contact eimeredelin@sberbank.ru to open port on demand
    ECS_PORT=ecs_port
    REVIEWS_ENDPOINT=http://ecs_external_ip:ecs_port/reviews

    Токен доступа к API и токен Logfire специфичны для этого проекта. Остальные используемые здесь данные ваш отдел получит в момент предоставления облачных ресурсов. Два исключения:

    • db_name в строке подключения к БД: базу(-ы) данных отдел создает самостоятельно;
    • ecs_port : если вы делаете публичный API, который должен быть доступен из интернета, вы самостоятельно выбираете номер порта и делаете заявку на его открытие у вашей виртуальной машины.
  1. bankiru-dbapi.service :
    /d/git/bankiru-dbapi (main)
    $ cat bankiru-dbapi.service
    [Unit]
    Description=bankiru-dbapi
    After=syslog.target
    After=network.target
    
    [Service]
    Type=simple
    User=evm
    WorkingDirectory=/home/evm/bankiru-dbapi
    ExecStart=/home/evm/bankiru-dbapi/.venv/bin/python /home/evm/bankiru-dbapi/logfire_auto_tracing.py
    Restart=always
    
    [Install]
    WantedBy=multi-user.target

  1. Выпуск токена доступа/аутентификации personal access token (PAT) на GitHub здесь. Требование к токену: The minimum required scopes are repo, read:org, admin:public_key.

  1. Установка GitHub CLI в Windows: скачать msi или с помощью winget.
    /d/git/bankiru-dbapi (main)
    $ winget install --id GitHub.cli
    Found GitHub CLI [GitHub.cli] Version 2.76.2
    This application is licensed to you by its owner.
    Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
    Downloading https://github.com/cli/cli/releases/download/v2.76.2/gh_2.76.2_windows_amd64.msi
      ██████████████████████████████  17.4 MB / 17.4 MB
    Successfully verified installer hash
    Starting package install...
    The installer will request to run as administrator. Expect a prompt.
    Successfully installed
    /d/git/bankiru-dbapi (main)
    $ gh --version
    gh version 2.76.2 (2025-07-30)
    https://github.com/cli/cli/releases/tag/v2.76.2

  1. Аутентификация в GitHub CLI с использованием токена из п.7. Публичная часть SSH-ключа будет скопирована и появится здесь с назначенным ему именем.
    /d/git/bankiru-dbapi (main)
    $ gh auth login
    ? Where do you use GitHub? GitHub.com                           
    ? What is your preferred protocol for Git operations on this host? SSH                                  
    ? Upload your SSH public key to your GitHub account? C:\Users\user\.ssh\id_ed25519.pub    
    ? Title for your SSH key: (GitHub CLI) ...
    
    ? Title for your SSH key: ...
    ? How would you like to authenticate GitHub CLI? Paste an authentication token        
    Tip: you can generate a Personal Access Token here https://github.com/settings/tokens
    The minimum required scopes are 'repo', 'read:org', 'admin:public_key'.
    ? Paste your authentication token: ****************************************
    - gh config set -h github.com git_protocol ssh
    ✓ Configured git protocol
    ✓ Uploaded the SSH key to your GitHub account: C:\Users\user\.ssh\id_ed25519.pub
    ✓ Logged in as EvgenyMeredelin

    Токен аутентификации хранится безопасно:

    /d/git/bankiru-dbapi (main)
    $ gh auth status
    github.com
      ✓ Logged in to github.com account EvgenyMeredelin (keyring)
      - Active account: true
      - Git operations protocol: ssh
      - Token: ghp_************************************
      - Token scopes: ...

  1. Коммит проекта. Но перед этим добавим описание в README.md.
    /d/git/bankiru-dbapi (main)
    $ git add .
    /d/git/bankiru-dbapi (main)
    $ git commit -m "First commit"
    [main (root-commit) 3dbd33a] First commit
     19 files changed, 2808 insertions(+)
     create mode 100644 .env.example
     create mode 100644 .gitignore
     create mode 100644 .python-version
     create mode 100644 README.md
     create mode 100644 alembic.ini
     create mode 100644 alembic/README
     create mode 100644 alembic/env.py
     create mode 100644 alembic/script.py.mako
     create mode 100644 alembic/versions/45600a192211_initial_migration.py
     create mode 100644 bankiru-dbapi.service
     create mode 100644 botocore_client.py
     create mode 100644 database.py
     create mode 100644 handlers.py
     create mode 100644 logfire_auto_tracing.py
     create mode 100644 main.py
     create mode 100644 models.py
     create mode 100644 pyproject.toml
     create mode 100644 schemas.py
     create mode 100644 uv.lock

  1. Создание репозитория и пуш коммита в него. Источник — текущая папка, поэтому репозиторий получит ее имя: bankiru-dbapi.
    /d/git/bankiru-dbapi (main)
    $ gh repo create --source=. --private --push
    ✓ Created repository EvgenyMeredelin/bankiru-dbapi on github.com
      https://github.com/EvgenyMeredelin/bankiru-dbapi
    ✓ Added remote git@github.com:EvgenyMeredelin/bankiru-dbapi.git
    Enter passphrase for key '/c/Users/user/.ssh/id_ed25519':
    Enumerating objects: 23, done.
    Counting objects: 100% (23/23), done.
    Delta compression using up to 8 threads
    Compressing objects: 100% (21/21), done.
    Writing objects: 100% (23/23), 99.42 KiB | 1.33 MiB/s, done.
    Total 23 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
    remote: Resolving deltas: 100% (1/1), done.
    To github.com:EvgenyMeredelin/bankiru-dbapi.git
     * [new branch]      HEAD -> main
    branch 'main' set up to track 'origin/main'.
    ✓ Pushed commits to git@github.com:EvgenyMeredelin/bankiru-dbapi.git

  1. Чтение переменных окружения и подключение к виртуальной машине:
    /d/git/bankiru-dbapi (main)
    $ . .env
    /d/git/bankiru-dbapi (main)
    $ ssh evm@$ECS_PUBLIC_IP
    Enter passphrase for key '/c/Users/user/.ssh/id_ed25519':
    Welcome to Ubuntu 24.04.3 LTS (GNU/Linux 6.8.0-71-generic x86_64)
    
     * Documentation:  https://help.ubuntu.com
     * Management:     https://landscape.canonical.com
     * Support:        https://ubuntu.com/pro
     
    ...

  1. Установка uv в Ubuntu:
    evm@ecs-evm:~$ curl -LsSf https://astral.sh/uv/install.sh | sh
    downloading uv 0.8.9 x86_64-unknown-linux-gnu
    no checksums to verify
    installing to /home/evm/.local/bin
      uv
      uvx
    everything's installed!
    evm@ecs-evm:~$ uv --version
    uv 0.8.9

  1. По причине несовершенства механизма хранения гитом секретов в Linux мы не будем устанавливать GitHub CLI в Ubuntu. Создадим SSH-ключ по пп.1-2 раздела Подключение к виртуальной машине — с той лишь разницей, что это нужно сделать не на локальной машине, а на виртуальной. Скопируем руками публичную часть ключа и создадим новый ключ в разделе SSH keys здесь.
    evm@ecs-evm:~$ cat .ssh/id_ed25519.pub
    ssh-ed25519 ...

  1. Клонирование репозитория на виртуальную машину. Ссылку, соответствующую протоколу, в т.ч. SSH, можно скопировать со страницы репозитория (зеленая кнопка Code).
    evm@ecs-evm:~$ git clone git@github.com:EvgenyMeredelin/bankiru-dbapi.git
    Cloning into 'bankiru-dbapi'...
    remote: Enumerating objects: 23, done.
    remote: Counting objects: 100% (23/23), done.
    remote: Compressing objects: 100% (20/20), done.
    remote: Total 23 (delta 1), reused 23 (delta 1), pack-reused 0 (from 0)
    Receiving objects: 100% (23/23), 99.42 KiB | 530.00 KiB/s, done.
    Resolving deltas: 100% (1/1), done.

  1. (Вос)создание виртуального окружения:
    evm@ecs-evm:~$ cd bankiru-dbapi/
    evm@ecs-evm:~/bankiru-dbapi$ uv sync
    Using CPython 3.13.5
    Creating virtual environment at: .venv
    Resolved 104 packages in 26ms
    Prepared 46 packages in 2.63s
    Installed 102 packages in 620ms
     + aiobotocore==2.24.0
     + aiohappyeyeballs==2.6.1
     + aiohttp==3.12.15
     ...
     + wrapt==1.17.3
     + yarl==1.20.1
     + zipp==3.23.0

  1. Откроем второй терминал и скопируем файл с переменными окружения .env с локальной машины на виртуальную:
    /d/git/bankiru-dbapi (main)
    $ . .env
    /d/git/bankiru-dbapi (main)
    $ scp .env evm@$ECS_PUBLIC_IP:/home/evm/bankiru-dbapi/
    Enter passphrase for key '/c/Users/user/.ssh/id_ed25519':
    .env                                                    100%  704    50.7KB/s   00:00

    Теперь весь проект на месте:

    evm@ecs-evm:~/bankiru-dbapi$ ls -a -1
    .
    ..
    alembic
    alembic.ini
    bankiru-dbapi.service
    botocore_client.py
    database.py
    .env
    .env.example
    .git
    .gitignore
    handlers.py
    logfire_auto_tracing.py
    main.py
    models.py
    pyproject.toml
    .python-version
    README.md
    schemas.py
    uv.lock
    .venv

  1. Скопируем подготовленный в п.6 юнит systemd и запустим его:
    evm@ecs-evm:~/bankiru-dbapi$ sudo cp bankiru-dbapi.service /etc/systemd/system
    evm@ecs-evm:~/bankiru-dbapi$ sudo systemctl daemon-reload
    evm@ecs-evm:~/bankiru-dbapi$ sudo systemctl enable bankiru-dbapi.service
    Created symlink /etc/systemd/system/multi-user.target.wants/bankiru-dbapi.service → /etc/systemd/system/bankiru-dbapi.service.
    evm@ecs-evm:~/bankiru-dbapi$ sudo systemctl start bankiru-dbapi.service

    Юнит работает:

    evm@ecs-evm:~$ sudo systemctl status bankiru-dbapi.service
    ● bankiru-dbapi.service - bankiru-dbapi
         Loaded: loaded (/etc/systemd/system/bankiru-dbapi.service; enabled; preset: enabled)
         Active: active (running) since Wed 2025-08-13 21:17:43 MSK; 4s ago
       Main PID: 34936 (python)
          Tasks: 11 (limit: 4144)
         Memory: 124.8M (peak: 125.2M)
            CPU: 1.849s
         CGroup: /system.slice/bankiru-dbapi.service
                 └─34936 /home/evm/bankiru-dbapi/.venv/bin/python /home/evm/bankiru-dbapi/logfire_auto_tracing.py
    
    Aug 13 21:17:43 ecs-evm systemd[1]: Started bankiru-dbapi.service - bankiru-dbapi.
    Aug 13 21:17:43 ecs-evm python[34936]: Logfire project URL: https://logfire-us.pydantic.dev/...
    Aug 13 21:17:44 ecs-evm python[34936]: INFO:     Started server process [34936]
    Aug 13 21:17:44 ecs-evm python[34936]: INFO:     Waiting for application startup.
    Aug 13 21:17:44 ecs-evm python[34936]: INFO:     Application startup complete.
    Aug 13 21:17:44 ecs-evm python[34936]: INFO:     Uvicorn running on http://... (Press CTRL+C to quit)

  1. Итак, API готов к работе. Парсер поднимается аналогично. Мы создали на сервере базу данных, и парсер уже насобирал в нее данные. Установим на локальной машине Chocolatey и HTTPie CLI. В PowerShell:

    Run Get-ExecutionPolicy. If it returns Restricted, then run Set-ExecutionPolicy AllSigned or Set-ExecutionPolicy Bypass -Scope Process.

    Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
    choco --version
    2.5.0
    choco install httpie
    Chocolatey v2.5.0
    Installing the following packages:
    httpie
    By installing, you accept licenses for the packages.
    Downloading package from source 'https://community.chocolatey.org/api/v2/'
    Progress: Downloading httpie 3.2.2... 100%
    ...
    Successfully installed httpie-3.2.2
     The install of httpie was successful.
      Software install location not explicitly set, it could be in package or
      default install location of installer.
    
    Chocolatey installed 1/1 packages.
     See the log for details (C:\ProgramData\chocolatey\logs\chocolatey.log).

  1. Вернемся в bash и рассмотрим несколько примеров работы API.
    Чтение записей. Отзывы за первую неделю августа 2025 в xlsx. Ссылка валидна ограниченное время. Ответ 200 OK.
    /d/git/bankiru-dbapi (main)
    $ http GET $REVIEWS_ENDPOINT API-Token:$API_TOKEN \
    > startDate==20250801 endDate==20250807 outputFormat==xlsx \
    > --format-options json.sort_keys:false
    HTTP/1.1 200 OK
    content-length: 136
    content-type: application/json
    date: Thu, 14 Aug 2025 07:58:48 GMT
    server: uvicorn
    
    {
        "startDate": "2025-08-01",
        "endDate": "2025-08-07",
        "outputFormat": "xlsx",
        "generateUrl": true,
        "url": "https://spoo.me/dv7ta3",
        "comment": null
    }

    То же в HTTPie desktop:

    HTTPie desktop app

    С параметрами по умолчанию получим всю БД в parquet:

    /d/git/bankiru-dbapi (main)
    $ http GET $REVIEWS_ENDPOINT API-Token:$API_TOKEN --unsorted
    HTTP/1.1 200 OK
    date: Thu, 14 Aug 2025 08:00:09 GMT
    server: uvicorn
    content-length: 123
    content-type: application/json
    
    {
        "startDate": null,
        "endDate": null,
        "outputFormat": "parquet",
        "generateUrl": true,
        "url": "https://spoo.me/OuJjmw",
        "comment": null
    }

  1. Добавление записей. Сымитируем работу парсера и добавим в БД одну запись (представим, что парсер отправил список, в котором единственная запись):
    /d/git/bankiru-dbapi (main)
    $ cat reviews.json
    [
        {
            "datePublished": "2026-01-01 01:23:45",
            "reviewBody": "Я закопал монетки, и они пропали...",
            "bankName": "Банк Поле Чудес",
            "url": "https://banki.ru/buratino-review",
            "location": "Сказочный город",
            "product": "Вклад"
        }
    ]
    /d/git/bankiru-dbapi (main)
    $ http POST $REVIEWS_ENDPOINT API-Token:$API_TOKEN < reviews.json
    HTTP/1.1 201 Created
    content-length: 4
    content-type: application/json
    date: Thu, 14 Aug 2025 08:01:50 GMT
    server: uvicorn
    
    null

    Ответ null со статусом 201 Created, потому что парсеру ответ не требуется. Как сказано в описании API выше, после каждого коммита (добавления или удаления записей) API создает бэкап базы данных в parquet и перезаписывает предыдущую версию в бакете объектного хранилища:

    Логика проста. Если это выборка по запросу пользователя, то API дает информативный ответ с полями, описывающими выборку, а файлу дается уникальное uuid-имя. Если эта выборка — автоматический бэкап, спровоцированный парсером, то осмысленный ответ не требуется, а файл в parquet получает [статичное] предопределенное имя bankiru_reviews_db_backup, и это приводит к перезаписи предыдущего бэкапа.

    Так наша запись выглядит в БД. На скриншоте 5 последних записей, начиная с последней.

  1. Удаление записей. Удалим нашу запись, отправив соответствующему эндпоинту массив из одного id, — id записи к удалению:
    /d/git/bankiru-dbapi (main)
    $ echo '[220345]' | http DELETE $REVIEWS_ENDPOINT API-Token:$API_TOKEN
    HTTP/1.1 204 No Content
    content-type: application/json
    date: Thu, 14 Aug 2025 08:06:48 GMT
    server: uvicorn

    Ожидаемый ответ 204 No Content, запись удалена, а бэкап обновлен, что видно по свежей дате последнего изменения в бакете:

  1. Примеры валидации данных. В обоих случаях ответ 422 Unprocessable Content.
    29 февраля у невисокосного 2025 года:
    /d/git/bankiru-dbapi (main)
    $ http GET $REVIEWS_ENDPOINT API-Token:$API_TOKEN \
    > startDate==20250229
    HTTP/1.1 422 Unprocessable Content
    content-length: 152
    content-type: application/json
    date: Thu, 14 Aug 2025 08:09:14 GMT
    server: uvicorn
    
    {
        "detail": [
            {
                "ctx": {
                    "error": {}
                },
                "input": "20250229",
                "loc": [
                    "query",
                    "startDate"
                ],
                "msg": "Value error, day is out of range for month",
                "type": "value_error"
            }
        ]
    }

    Неизвестный формат файла, для которого нет обработчика:

    /d/git/bankiru-dbapi (main)
    $ http GET $REVIEWS_ENDPOINT API-Token:$API_TOKEN \
    > outputFormat==abc
    HTTP/1.1 422 Unprocessable Content
    content-length: 197
    content-type: application/json
    date: Thu, 14 Aug 2025 08:09:54 GMT
    server: uvicorn
    
    {
        "detail": [
            {
                "ctx": {
                    "expected": "'csv', 'json', 'parquet' or 'xlsx'"
                },
                "input": "abc",
                "loc": [
                    "query",
                    "outputFormat"
                ],
                "msg": "Input should be 'csv', 'json', 'parquet' or 'xlsx'",
                "type": "literal_error"
            }
        ]
    }

    Здесь используется симпатичный мне подход “бесшовной валидации”. Новые обработчики, конвертирующие выборку из БД в тот или иной формат, пишутся как потомки базового абстрактного класса. В момент выполнения кода inspect формирует маппинг между расширением файла и соответствующим конвертером. Так, с одной стороны, определяется конвертер по указанному в запросе желаемому выходному формату. С другой — ключи этого маппинга, т.е. расширения, определяют значения typing.Literal, который валидирует выходной формат в запросе. Так в запросе валидны только те форматы, для которых в коде существуют конвертеры. Если разработчик написал новый конвертер, то ничего дополнительно делать не нужно: соблюдение протокола базового класса сразу делает конвертер активным, а соответствующее расширение выходного файла — валидным значением в запросе.

  1. Как это может выглядеть в Pydantic Logfire. Это платформа с огромным функционалом для мониторинга, сбора и анализа телеметрии и логов приложений. Logfire выходит за рамки этого примера и упоминается здесь лишь для того чтобы привлечь ваше внимание к этому замечательному инструменту.
    Pydantic Logfire project

  1. В конце несколько слов о Termius.
    • защищенное подключение к хосту в один клик;
    • хранение профилей и данных в зашифрованном хранилище, доступном со всех устройств, включая мобильные;
    • личное и командное пространства;
    • SFTP-клиент;
    • сниппеты для быстрого запуска скриптов на всем прикрепленном парке хостов;
    • режим синхронизации терминалов;
    • автодополнения как в IDE;
    • удобное управление ключами.
    Termius SSH client

Last update: 2025-08-20, evm