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

Для начала придумаем проект, который не займёт много времени(надеюсь) и над которым будет интересно посидеть пару вечеров.

С чего начнем?

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


Что нам нужно:

  • ? Карточка-резюме с опытом и навыками

  • ? Ссылка для просмотра

  • ✏️ Редактирование и добавление информации

  • ? База данных для хранения и поиска

Остальное пока останется за бортом. Нам это сейчас не надо.

Инструменты:

python, litestar, coverage, pytest, ruff, docker, make, granian, sqlalchemy, alembic, asyncpg, keydb, msgspec, uv, pyenv. Выглядит как какой-то набор тэгов, но всё это нам понадобиться, постепенно.

Самым интересным для многих будет посмотреть granian, keydb и на litestar и сравнить его с fastapi.

Небольшой дисклеймер: Все персонажи выдуманы, любые совпадения — случайны. Если видите ошибки в коде или в тексте, пишите в комментариях, буду рад всё поправить. Конструктивная критика, любые предложения, какая-то новая фича или что-то еще, чего бы хотелось увидеть в этом проекте — приветствуются.

Проектирование системы

"Системы состоят из подсистем, подсистемы — из подподсистем и так до бесконечности — именно поэтому мы проектируем снизу вверх." (Алан Перлис)

Настраиваем окружение

Итак, займёмся делом. Начнём с установки всего необходимого для проекта. Создадим виртуальное окружение и установим зависимости, сконфигурируем Docker и т. д. Я пользуюсь Ubuntu, так что, отталкиваться будем от этой ОС.

Устанавливаем pyenv.

Pyenv — это инструмент для управления версиями Python, который позволяет пользователям устанавливать и переключаться между несколькими версиями интерпретатора.

Под линукс pyenv рекомендуют устанавливать так:

curl -fsSL https://pyenv.run | bash

Далее настраиваем свой shell для pyenv (у меня это zsh):

  echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
  echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
  echo 'eval "$(pyenv init - zsh)"' >> ~/.zshrc

и перезагружаем шел.

source ~/.zshrc

Перед тем, как пойдём дальше, установим зависимости:

sudo apt update && sudo apt install -y build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev curl git \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev

✅ Ожидаемый результат:

pyenv --version

Вывод:

pyenv 2.x.x

Тут главное не запутаться, потому что ещё есть:

pyenv version # выводит текущую версию питона и источник
pyenv versions # выводит список версий питона доступных в pyenv

Посмотреть доступные для установки версии можно так:

pyenv install -l

А установить нужную — так:

pyenv install 3.13.1

Устанавливаем direnv.

direnv — это менеджер переменных окружения для вашего командного интерпретатора, который автоматически загружает и выгружает переменные окружения в зависимости от текущего каталога. Это позволяет управлять проектными переменными окружения, не загромождая глобальные конфигурационные файлы, такие как ~/.profile.

Основные характеристики Direnv:

  • Автоматическая загрузка переменных: Direnv проверяет наличие файла .envrc в текущем и родительских каталогах перед каждой командной строкой. Если файл найден и разрешен, его содержимое загружается, а переменные становятся доступными в текущем сеансе.

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

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

  • Обработка скриптов: Файл .envrc может содержать произвольные команды оболочки, что позволяет выполнять сложные операции при входе в каталог.

  • Безопасность: Direnv требует явного разрешения на выполнение содержимого .envrc, что предотвращает случайное выполнение вредоносного кода.

  • Интеграция с другими инструментами: Direnv может работать в связке с такими инструментами, как pyenv, для управления версиями Python и создания виртуальных окружений.

Мы будем использовать его для настройки виртуального окружения Python, на основе файла .python-version, и его активации.

sudo apt-get install direnv
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc

И сейчас нам так же понадобиться uv.

UV — это новый пакетный менеджер для Python, написанный на Rust, который предлагает высокую производительность и функциональность, заменяя традиционные инструменты, такие как pip, pip-tools и другие.

Основные особенности UV:

  • Высокая скорость: UV обеспечивает скорость установки и управления пакетами в 10-100 раз быстрее, чем pip, что делает его привлекательным для разработчиков, работающих с большими проектами.

  • Универсальный инструмент: UV объединяет функции нескольких инструментов, включая:

  • Установку и управление версиями Python.

  • Создание виртуальных окружений.

  • Управление зависимостями с использованием lock-файлов.

  • Совместимость: UV предоставляет интерфейс, совместимый с pip, что позволяет легко интегрировать его в существующие проекты без необходимости в значительной переработке кода.

  • Проектное управление: UV использует файл pyproject.toml для определения зависимостей и создает универсальный lock-файл (uv.lock), который упрощает управление зависимостями и их версиями.

  • Простота установки: UV можно установить как через pip, так и с помощью самостоятельных установщиков для различных операционных систем (macOS, Linux, Windows).

  • Поддержка работы с несколькими версиями Python: UV позволяет устанавливать и переключаться между различными версиями Python, что упрощает работу над проектами, требующими разные версии интерпретатора.

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

Его можно установить как через cargo:

cargo install --git https://github.com/astral-sh/uv uv

Так и почти через что угодно:

  • через curl:

curl -LsSf https://astral.sh/uv/install.sh | sh
  • через pip:

pip install uv

Выбирайте любой понравившийся вариант.

Создадим директорию для проекта:

mkdir litestarcatscv
cd $_
uv init

Папка с проектом должна выглядеть так:

.
├── README.md
├── hello.py
└── pyproject.toml

Установим локальную версию питона:

pyenv install 3.13.1
pyenv local 3.13.1

✅ Ожидаемый результат:

cat .python-version

Вывод:

3.13.1

Теперь создадим .envrc

Это файл, который, как говорилось выше, нужен для автоматической активации виртуального окружения. Здесь и далее я буду использовать neovim, вы же можете пользоваться любым удобным текстовым редактором:

nvim .envrc

Скопируем туда следующий код и сохраняем:

# проверим что в директории лежит файл .python-version
if [ -f ".python-version" ] ; then
    if [ ! -d ".venv" ] ; then
        echo "Устанавливаем virtualenv для $(python -V)"
        uv .venv
    fi
    echo "Активируем $(python -V) virtualenv"
    source .venv/bin/activate
fi
# Показываем версию питона и путь к нему ${PATH}
echo "Virtualenv активирован для $(python -V)"
echo "$(which python)"

Если вы делали это в терминале, то можете увидеть следующую ошибку:

direnv: error .envrc is blocked. Run direnv allow to approve its content

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

direnv allow

Также есть возможность отрывать этот файл на редактирование в дефолтном редакторе:

direnv edit

Тогда после сохранения и закрытия файла всё это содержимое будет загружено автоматически без необходимости запуска direnv allow. А еще, вместо дефолтного редактора, для direnv edit, можно подключить свой любимый, с помощью добавления этой строки в ~/.zshrc:

export EDITOR="<yourbesteditor> --wait"

Где вместо , укажите свой редактор.

И ещё один опциональный момент, чтобы в каждом проекте не добавлять эти файлы в gitignore, мы можем поместить их в глобальный gitignore — для того, чтобы ваши конфиденциальные данные, которые могут содержаться в этих файлах, случайно не утекли в сеть. Создайте файл:

touch ~/.gitignore_global

И скопируйте туда следующие строки:

# Direnv files
.direnv
.envrc

# Virtual env
.venv

# Editor specific files and folders
.idea
.vscode

Сохраните и закройте.

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

И выполните команду:

git config --global core.excludesFile "~/.gitignore_global"

Для проверки, что всё сделано правильно, выполните команду:

git config --global core.excludesFile

Она должна вывести на экран путь к вашему глобальному gitignore файлу.
✅ Ожидаемый результат:

~/.gitignore_global

Ещё полезные фишки direnv

в контексте .envrc:

  • path_add - обычно для добавления пути мы используем export PATH=:$PATH, с помощью этой команды вы можете написать PATH_add my-new-path и он будет автоматически добавлен в ваш $PATH.

  • source_env - эта функция загружает другой .envrc указав путь или имя файла.

  • dot_env - загружает .env файл в текущую среду без необходимости установки каких-либо зависимостей. Таким образом, ваш проект занимает меньше места в каталоге.

  • watch_file [ ...] - добавляет один или несколько файлов в список наблюдения. direnv перезагрузит вашу оболочку при следующем запросе, если какой-либо из указанных файлов изменится. Больше фишек тут и тут

Примечание: вы также можете создавать собственные расширения, создавая bash файлы в папке ~/.config/direnv/direnvrc или ~/.config/direnv/lib/*.sh. Эти файлы загружаются до вашего .envrc и, таким образом, позволяют создавать собственные расширения для direnv.

Теперь у нас настроены инструменты, необходимые для управления окружением.

Проверим, что всё работает:

uv run hello.py

✅ Ожидаемый результат:

Hello from litestarcatscv!

Подключим litestar, granian и ruff к проекту

uv add litestar granian ruff

Litestar — это современный и высокопроизводительный ASGI-фреймворк, который предназначен для создания API и веб-приложений.

Основные особенности Litestar:

  • Высокая производительность: Litestar разработан с акцентом на производительность, что позволяет создавать быстрые и отзывчивые приложения.

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

  • Интеграция с SQLAlchemy: Litestar предоставляет встроенную поддержку для SQLAlchemy, что упрощает работу с базами данных и позволяет использовать модели напрямую для валидации и сериализации данных.

  • Автоматическая генерация документации: Litestar автоматически генерирует документацию в формате OpenAPI, что облегчает интеграцию с фронтендом и делает API более доступным для разработчиков.

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

  • Система внедрения зависимостей: Litestar предлагает мощную систему внедрения зависимостей, которая помогает организовать код и уменьшить дублирование.

  • Валидация данных: Используя типизацию Python, Litestar обеспечивает строгую валидацию данных, что помогает минимизировать ошибки во время выполнения.

  • Поддержка различных типов хранилищ: Litestar может работать с различными хранилищами ключ-значение, что позволяет легко интегрировать сторонние расширения.

Обновим структуру проекта:

rm hello.py #он нам больше не нужен
mkdir src && nvim src/app.py

Добавим минимальный пример в app.py:

from litestar import Litestar, get@get("/")async def index() -> str:    return "Hello, world!"app = Litestar([index,])

Проверим работоспособность нашего приложения, запустим его с помощью granian:

Granian— это высокопроизводительный HTTP-сервер, разработанный на Rust для приложений Python. Он построен на основе библиотеки Hyper и предлагает ряд преимуществ для разработчиков, работающих с веб-приложениями.

Основные характеристики Granian:

  • Поддержка нескольких интерфейсов: Granian поддерживает ASGI, RSGI и WSGI интерфейсы, что позволяет использовать его с различными типами приложений и фреймворков.

  • Протоколы HTTP/1 и HTTP/2: Сервер реализует поддержку как для HTTP/1, так и для HTTP/2, что обеспечивает более эффективную передачу данных и улучшенную производительность.

  • Поддержка WebSockets: Granian позволяет работать с WebSockets, что делает его подходящим для разработки приложений реального времени.

  • Производительность: Granian предлагает стабильную производительность по сравнению с традиционными решениями, такими как Gunicorn и Uvicorn, избегая сложной зависимости от нескольких инструментов.

  • Конфигурация потоков и процессов: Granian позволяет настраивать количество процессов и потоков, что дает возможность оптимизировать использование ресурсов в зависимости от требований приложения.

  • Простота установки: Установить Granian можно с помощью pip

Запускаем сервер через Granian

uv run granian --interface asgi src/app:app

✅ Ожидаемый результат:
Сервер запустится и при переходе на http://localhost:8000/ покажет Hello, world!.

Вывод:

INFO] Starting granian (main PID: 880817)
[INFO] Listening at: http://127.0.0.1:8000
[INFO] Spawning worker-1 with pid: 880818
[INFO] Started worker-1
[INFO] Started worker-1 runtime-1

Автоматизация с Makefile

nvim Makefile

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

.
├── Makefile
├── README.md
├── hello.py
├── pyproject.toml
├── src
│   ├── __pycache__
│   │   └── app.cpython-313.pyc
│   └── app.py
└── uv.lock

А теперь сделаем запуск разных команд через make.
Здесь как раз можно задействовать ruff.
Ruff — это современный и высокопроизводительный линтер для Python, разработанный на Rust. Он выделяется своей скоростью, гибкостью и широкими возможностями.

Основные характеристики Ruff:

  • Скорость: Ruff значительно быстрее традиционных линтеров, таких как Flake8, isort и Pylint. Он может выполнять анализ кода в 10-100 раз быстрее, что делает его идеальным для больших кодовых баз и обеспечивает быструю обратную связь.

  • Совместимость: Ruff служит заменой для нескольких популярных инструментов линтинга, включая Flake8, isort, pydocstyle и другие. Это позволяет разработчикам использовать один инструмент вместо множества, упрощая процесс линтинга.

  • Расширяемость: Ruff поддерживает создание пользовательских плагинов, что позволяет адаптировать его под специфические нужды проектов и команды.

  • Автоматические исправления: Linters могут автоматически исправлять множество ошибок, таких как удаление неиспользуемых импортов и реформация строк документации.

  • Поддержка множества правил: Ruff поддерживает более 500 правил линтинга и может быть настроен для работы с различными стилями кодирования.

  • Легкость в использовании: Интерфейс Ruff прост и интуитивно понятен, что делает его доступным даже для новичков.

Добавляем в файл:

.DEFAULT_GOAL := help
SHELL := bash

.PHONY : run test check format db-migrate db-upgrade help

# Запускаем проект
run:
	uv run granian --interface asgi src/app:app

# Запускаем тесты
test:
	uv run pytest
# Проверяем линтером
check:
	@echo Running project linters...
	uv run ruff check src

# Делаем автоформат
format:
	@echo Running project linters...
	uv run ruff --fix src

# Новая миграция
db-migrate:
	alembic revision --autogenerate -m "New migration"

# Применить миграции
db-upgrade:
	alembic upgrade head

# Список команд
help:
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'

Теперь мы можем легко проверять и запускать наш проект с помощью:

make test
make check
make format
make run

Вывод

Итого, у нас получилось сделать минимальный пример, мы подготовили окружение, научились базово пользоваться pyenv, direnv, uv, make. И это уже отличный старт. А нашу заготовку мы будем улучшать в следующей части.

На этом пока что всё.

Комментарии (1)


  1. Sap_ru
    18.02.2025 21:01

    Какой-то огромный набор велосипедов, а в результате никуда и не поехали...
    Не понял, зачем нужен direnv, если потом всё равно делается самопальный скрипт, который может ровно так же сразу и окружение импортировать - вместе с проверками три строчки кода добавится.
    А так вас Pyenv + UV + direnv + самописный скрипт импорта окружения (!!!), и все рулят виртуальными окружениями (!!!). Несколько... странно? Один из велосипедов и вовсе из исходников собран - вот радость для продакшена будет! Неужели такой необходимый велосипед?

    Даже make прикручен, фигачили бы уже сразу scons какой-нибудь, чтобы уже прямо вообще.
    А потом весь этот зоопарк велосипедов нужно будет прикрутить к IDE. А потом к продакшену.
    А сверху ещё и nginx прикрутить придётся, так как веб-сервер не поддерживает HTTP3/QUICK.

    И что интересно, при всём богатстве велосипедов, в реальность поставить несколько версий Python под Linux всё равно будет больно и сложно. На всех велосипедах заявлена установка и поддержка нескольких версий компиляторов, но где вы в реальности эти версии возьмёте? А если вы их уже взяли, то вон в тот скриптовый файл нужно просто ещё две переменные окружения добавить и больше ничего не нужно.

    Может быть, я конечно и придираюсь и сейчас так принято, но как-то это всё так себе выглядит. Ощущение, что каждый последующий велосипед изобретал человек, который толком не разобрался в предыдущем, и которому было просто лень написать скрипт в пять строк. И потому он с энтузиазмом ваялся ещё один велосипед с очередной собственной поддержкой "виртуальных окружений" и "нескольких версий интерпретаторов". Киллер-фичи прямо.