- BrainTools - https://www.braintools.ru -

С полгода назад я начал чаще использовать для программирования Python [1]. Почему? Конечно, из-за ИИ. Лично для меня очевидно, что сегодня эта сфера связана с очень большими деньгами перспективами во всех направлениях. А какой язык является самым распространённым для ИИ? Да-да, этот самый проныра.
Я уже писал на Python, но только небольшие скрипты. К примеру, вот этот [2] скрейпит метаданные всех видео с моего канала на YouTube [3]. Собранные метаданные выводятся в виде файла JSON [4], который я использую для показа красивой статистики роликов на этой статичной странице [5]. Как можно видеть здесь, этот скромный скрипт через GitHub Actions выполняется в соло-режиме каждый понедельник. Просто реализовать всё это на Python куда проще, чем с помощью того же Batch. И не только из-за более дружественного синтаксиса, но и потому что его интерпретатор нативно интегрирован во все дистрибутивы Unix. Разве не круто?
Так что, да, Python силён и отлично сочетается с распространённым сегодня VSCode [6]. Но всерьёз я начал к нему относиться лишь недавно1 [7], когда решил заняться созданием приложений на базе ИИ (RAG, агентов, генеративных инструментов и прочего) для реальных задач. Тогда я понял, что независимо от того, нравится он вам или нет, для всего этого оптимальным выбором будет именно Python.
В итоге я решил взяться за него всерьёз и к своему удивлению выяснил, что Python вместе со всей окружающей его средой за последние десятилетия существенно развился.
Вот лишь несколько подтверждений тому:
1. Вокруг этого языка выстроилась полноценная экосистема библиотек и инструментов для обработки/анализа данных.2 [8]
2. Python стал быстрее, благодаря использованию оптимизированных статических компиляторов вроде Cython.
3. Разработчики Python проделали старательную работу, скрыв его легаси-уродство (например, __init__, __new__ и аналогичные искажения), сделав синтаксис более удобным для разработчиков с хорошим вкусом [9].
Благодаря всем перечисленным и многим другим доработкам, теперь работать с этим языком мне стало особенно приятно.
Тем не менее за эти полгода я также узнал, что по-прежнему велика разница между пригодностью Python для обычных рабочих потоков на базе блокнотов Jupyter или скриптов и его использованием для создания готовых к продакшену3 [10] приложений.
Так что в этой статье я расскажу об инструментах, библиотеках, конфигурациях и прочих дополнениях для языка, которые дарят мне удобство, и которые я использую для создания приложений.
⚠️ Эта статья ориентирована на инструменты, с которыми работаю лично я. Если вы можете порекомендовать мне какие-то другие полезные решения, поделитесь в комментариях.
Примечание: так вышло, что моя статья набрала на Hacker News более 680 комментариев [11]. Ещё раз убеждаюсь в непредсказуемости жизни.
Я предпочитаю использовать для своих проектов Python структуру монорепозитория, включая в него и бэкенд, и фронтенд.4 [12]
Почему?
1. Из-за сложностей с запоминанием [13]: мне не нравится, когда части кода разбросаны по нескольким репозиториям (это явно не облегчает поиск).
2. Наличие нескольких репозиториев зачастую излишне. Я работаю один и считаю, что если проект дорастёт до потребности [14] его разделения на разные репозитории, это уже будет говорить о чрезмерном усложнении.
3. Ну и я банально ленивый, стараюсь ничего не усложнять — компилирую, тестирую, пакую в контейнеры и развёртываю из одного расположения.5 [15]
Мне бы хотелось иметь инструмент, который бы генерировал структуру проекта, но пока подходящих я не нашёл. Раньше я использовал CCDS [16] — инструмент для инициализации проектов, преимущественно в сфере дата-сайенс. Он очень хорош, но не ориентирован на фулстек разработчиков.
Вот типичная структура проекта с архитектурой фронт-бэк (чуть позже я пробегусь по каждой её части):
project/
│
├── .github/ # Рабочие потоки GitHub Actions для пайплайнов CI/CD.
│ ├── workflows/ # Каталог с файлами YAML для автоматизированных рабочих потоков.
│ └── dependabot.yml # Конфигурация Dependabot для управления зависимостями.
│
├── .vscode/ # Конфигурация VSCode для проекта.
│ ├── launch.json # Отладочные настройки для VSCode.
│ └── settings.json # Настройки проекта для VSCode.
│
├── docs/ # Сайт и документация (статичный SPA с MkDocs).
│
├── project-api/ # API бэкенда для обработки бизнес-логики и основных задач.
│ ├── data/ # Каталог для хранения датасетов и других статичных файлов.
│ ├── notebooks/ # Блокноты Jupyter для оперативных экспериментов и прототипирования.
│ ├── tools/ # Вспомогательные скрипты и инструменты для разработки и развёртывания.
│ ├── src/ # Исходный код для бэкенд-приложения.
│ │ ├── app/ # Основной код приложения.
│ │ └── tests/ # Модульные тесты для бэкенда.
│ │
│ ├── .dockerignore # Указание файлов, исключаемых из сборок Docker.
│ ├── .python-version # Указание версии Python для pyenv.
│ ├── Dockerfile # Конфигурация Docker для контейнеризации бэкенда.
│ ├── Makefile # Автоматические задачи для сборки, тестирования и развёртывания.
│ ├── pyproject.toml # Файл конфигурации проекта Python.
│ ├── README.md # Документация для API бэкенда.
│ └── uv.lock # Lockfile, содержащий список зависимостей, управляемых UV.
│
├── project-ui/ # UI фронтенда для проекта (Next.js, React и так далее).
│
├── .gitignore # Глобальный файл gitignore для репозитория.
├── .pre-commit-config.yaml # Конфигурация для pre-commit хуков.
├── CONTRIBUTING.md # Руководства для участников проекта.
├── docker-compose.yml # Настройки Docker Compose для мульти-контейнерных конфигураций.
├── LICENSE # Лицензионная информация проекта (я всегда выбираю MIT).
├── Makefile # Автоматические задачи для сборки, тестирования и деплоя.
└── README.md # Основная документация проекта (его базовые функции, процесс установки и инструкции к использованию).
Мой project представлен адресом корневого каталога и именем репозитория GitHub. Мне нравится давать проектам короткие названия длиной до 10 символов, без использования snake_case — меня вполне устраивает разделение дефисами. Заметьте, что проект должен быть самодостаточным, то есть включать документацию, инфраструктуру сборки/деплоя и любые другие необходимые файлы для его автономного развёртывания.
Важно не проделывать в project-ui никаких этапов сложной обработки данных, так как я стараюсь отделять логику [17] фронтенда от задач бэкенда. Вместо этого я выполняю HTTP-запросы к серверу project-api, который содержит Python-код. Таким образом мы сохраняем браузерное приложение лёгким, делегируя всю основную работу и бизнес-логику серверу.
В project-api/src/app есть файл __init__.py, указывающий, что app — это модуль Python (его можно импортировать из других модулей).
Для управления пакетами и сборки я использую uv [18]. Этого инструмента мне вполне хватает для установки зависимостей и управления ими.
Вот основные команды для его настройки:
# Глобальная установка uv, если он ещё не установлен.
curl -sSfL <https://astral.sh/install.sh> | sh
# Инициализация нового проекта (добавляет .gitignore, .python-version, pyproject.toml и так далее).
uv init project-api
# Добавление в проект некоторых зависимостей и обновление pyproject.toml.
uv add --dev pytest ruff pre-commit mkdocs gitleaks fastapi pydantic
# Указание в Lockfile последних версий зависимостей (создаёт .venv, если ещё не создан).
uv sync
# Активация .venv.
uv venv activate
Обратите внимание [19], что самым важным для uv файлом является pyproject.toml.6 [20] Он содержит метаданные и список зависимостей, необходимых для сборки и выполнения проекта.
Ruff [21] мне очень нравится. Это супербыстрый линтер и форматер кода для Python, помогающий ленивым разработчикам вроде меня сохранять кодовые базы в чистом и обслуживаемом виде. Он совмещает в себе isort, flake8, autoflake и аналогичные инструменты, доступные через единый интерфейс командной строки:
# Линтинг всех файлов в /path/to/code (и всех подкаталогах).
ruff check path/to/code/
# Форматирование всех файлов в /path/to/code (и всех подкаталогах).
ruff format path/to/code/
Ruff штатно поддерживает руководство по стилю PEP 8 [22].
ty [23] — это модуль проверки типов для Python. При этом он прекрасно сочетается с typing [24], популярным модулем для добавления статической типизации. Думаю, что типизация реально помогает мне перехватывать ошибки [25] типов на ранних стадиях разработки. Я не боюсь писать дополнительный код и с готовностью иду на это, если такое решение повышает качество программы, попутно уменьшая вероятность ошибок в среде выполнения.
Примечание: на момент написания статьи
tyещё находится на ранней стадии разработки, но за время использования этой утилиты я никаких ощутимых недочётов не заметил. Кстати, работают над ней ребята из Astral, той же компании, которая создалаuvиruff.
pytest
pytest [26] — это оптимальная библиотека тестирования для Python, значительно облегчающая написание простых и масштабируемых тестов. Она поддерживает фикстуры, параметризованные тесты, а также имеет богатую экосистему плагинов. Просто создайте в project-api/src/app/tests/ файл с именем test_<unit_or_module>.py и выполните:
uv run pytest
Готово!
Pydantic [27] — это библиотека для валидации данных и управления настройками. Она помогает настраивать всевозможные детали конфигурации, такие как ключи API, подключение к базе данных или параметры модели (жёсткое прописывание этих значений — очень плохая практика, если что).
В частности, Pydantic Settings [28] позволяет определять конфигурацию приложения с помощью моделей Pydantic. Эта утилита может автоматически загружать настройки из переменных среды или специальных файлов .env, проверять их типы и обеспечивать удобный доступ к ним в коде.
Вот пример:
from pydantic import BaseSettings
class Settings(BaseSettings):
api_key: str
db_url: str
class Config:
env_file = ".env"
settings = Settings()
При выполнении этого кода Pydantic будет автоматически загружать значения api_key и db_url из файла .env или переменных среды. В итоге эти значения будут доступны для изменений и проверяться в соответствии с типами из модели Settings. Прекрасно!
С помощью MkDocs [29] я создаю документацию и генерирую статический сайт для проекта.7 [30] Сам я не дизайнер, поэтому предпочитаю просто копировать красивые эскизы из других опенсорсных проектов и вносить в их CSS небольшие доработки (например, изменять шрифты и цвета).
FastAPI [31] я использую для создания API. Лично мне этот инструмент сильно облегчил жизнь. Он позволяет легко создавать RESTful API с автоматической валидацией, сериализацией и документацией. FastAPI построен на базе Starlette и Pydantic, благодаря чему обеспечивает превосходное быстродействие и безопасность типов. Он быстр, прост в использовании и легко интегрируется с Pydantic для валидации данных.
Dataclasses [32] — это не библиотека, а функциональность самого Python, обеспечивающая возможность определения классов, используемых в основном для хранения данных. Она предоставляет простой синтаксис для создания классов, автоматически генерирующих особые методы вроде __init__(), __repr__() и __eq__().
Это существенно сокращает объём шаблонного кода при создании контейнеров данных.
Вот пример:
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
p = Point(1, 2)
print(p) # Вывод: Point(x=1, y=2)
Так что можно попрощаться с бойлерплейтом и загадочным кодом!
Я люблю GitHub Actions [33], особенно за то, что эта платформа обеспечивает непрерывную интеграцию в разных ОС. Рекомендую использовать её для налаживания пайплайнов API и UI.
Вот типичный рабочий поток project-api:
name: CI-API
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Build Docker image
run: docker build -t project-api:ci ./project-api
- name: Run tests
run: docker run --rm project-api:ci pytest
Заметьте, что здесь для выполнения тестов в изолированной среде используется Docker.8 [34] Изменить ОС можно через установку параметра runs-on на windows-latest или macos-latest.
Обработка зависимостей — это всегда боль [35], но Dependabot её облегчает. Он автоматически проверяет актуальность зависимостей и создаёт пул-реквесты для их обновления.
Вот образец конфигурации этого инструмента из файла .github/dependabot.yml:
version: 2
updates:
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "weekly"
Думаю, что одним из основных недочётов, способных повредить нашей репутации, является сохранение чувствительных данных вроде ключей API или паролей непосредственно в репозитории. К счастью, Gitleaks [36] помогает исключить подобные проблемы. Не вижу причин её не использовать.
Я использую pre-commit [37] для выполнения проверок и форматирования кода перед отправкой коммита. Это позволяет сохранять код в опрятной форме и обеспечивать соблюдение им стандартов проекта. К примеру, я использую вот этот фреймворк для выполнения ruff-pre-commit [38] и gitleaks перед отправкой кода в репозиторий.
Вот образец моего файла .pre-commit-config.yaml:
repos:
- repo: <https://github.com/astral-sh/ruff-pre-commit>
rev: v0.12.3 # Версия Ruff.
hooks:
- id: ruff-check # Запуск линтера.
args: [ --fix ]
- id: ruff-format # Запуск форматирования.
- repo: <https://github.com/gitleaks/gitleaks>
rev: v8.27.2
hooks:
- id: gitleaks
Make
Make [39] — это классическая утилита для автоматизации задач, которую можно по праву назвать мультитулом. Я с его помощью создаю простые сокращения для частых команд разработки. Вместо того, чтобы запоминать и вводить длинные выражения для выполнения тестов, сборки образов Docker или запуска сервисов, я определяю все эти задачи в Makefile. Потом остаётся просто выполнять команды вроде make test или make infrastructure-up.
Как вы могли заметить, Makefile есть в каталоге project-api и глобальном каталоге project:
1. project/project-api/Makefile: для линтинга, тестирования и выполнения API.
2. project/Makefile: для сборки и запуска инфраструктуры (через docker-compose).
Вот очень упрощённый пример Makefile для project-api:
DIR := . # project/project-api/Makefile
test:
uv run pytest
format-fix:
uv run ruff format $(DIR)
uv run ruff check --select I --fix
lint-fix:
uv run ruff check --fix
Теперь, если я захочу прогнать тесты, то выполню make test, и программа запустит uv run pytest в текущем каталоге.
Для проекта в целом я использую такой Makefile:
infrastructure-build:
docker compose build
infrastructure-up:
docker compose up --build -d
infrastructure-stop:
docker compose stop
make — это мощный инструмент, помогающий автоматизировать в потоке разработки практически всё. И хотя выше я привёл весьма упрощённые примеры, всегда есть возможность добавить любые сложные задачи, которые вам нужны.
Docker [40] — это инструмент, который позволяет упаковывать приложение с его зависимостями в контейнер, включая всё необходимое для выполнения: зависимости, системные инструменты, код и среду выполнения. Работая локально, я использую Docker Compose [41] для соединения всех образов Docker в единой сети. По аналогии с Docker, работающим с зависимостями, Docker Compose позволяет инкапсулировать весь стек приложений и отделить его от остальной локальной среды разработки.
Чтобы лучше понять этот механизм, взглянем на простой файл docker-compose.yml:
version: '3.8'
services:
project-api:
build:
context: ./project-api
dockerfile: Dockerfile
ports:
- "8000:8000"
volumes:
- ./project-api:/app
environment:
- ENV_VAR=value
networks:
- project-network
project-ui:
build:
context: ./project-ui
dockerfile: Dockerfile
ports:
- "3000:3000"
networks:
- project-network
networks:
project-network:
driver: bridge
Здесь мы определяем два сервиса: project-api и project-ui. Каждый из них имеет собственный контекст сборки (Dockerfile), порты, тома и переменные среды.
Вот образец Dockerfile для сервиса project-api:
FROM python:3.11-slim
# Установка системных зависимостей.
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
WORKDIR /app
COPY uv.lock pyproject.toml README.md ./
RUN uv sync --frozen --no-cache
# Включение кода приложения.
COPY src/app app/
COPY tools tools/
# Определение команды для запуска приложения.
CMD ["/app/.venv/bin/fastapi", "run", "project/infrastructure/api.py", "--port", "8000", "--host", "0.0.0.0"]
Как видите, Dockerfile начинается с базового образа Python, устанавливает зависимости, копирует файлы проекта и определяет команду для запуска приложения FastAPI.
Таким образом вы можете запускать весь стек приложений одной командой:
docker compose up --build -d
1. Кто меня знает, тот помнит, что раньше я работал преимущественно на Java/JavaScript/R. ↩ [42]
2. К примеру, сегодня Jupyter [43] используется практически всеми ведущими облачными провайдерами в качестве штатного инструмента для интерактивной обработки данных и научных вычислений. ↩ [44]
3. Под «готовыми к продакшену» я подразумеваю, что могу развернуть приложение в облаке как есть без необходимости вносить множество изменений в инфраструктуру. ↩ [45]
4. Да, я знаю, что порой необходима структура, включающая несколько репозиториев — например, при работе разных команд над разными частями проекта или для разделения зависимостей между несколькими проектами. ↩ [46]
5. Я считаю, что лучше избегать преждевременного разделения. Если кодовая база содержит, скажем, 1/2 миллиона строк кода, то настройка связующего слоя (вроде вызовов API) создаст для здравомыслящих разработчиков только лишние хлопоты. ↩ [47]
6. Файл pyproject.toml аналогичен package.json в Node.js или pom.xml в Java. ↩ [48]
7. Кстати, я думаю, что каждый проект на GitHub должен иметь свой сайт (это очень легко реализовать через GitHub Pages [49]), так что оправдания не принимаются. ↩ [50]
8. Использование Docker для непрерывной интеграции позволяет обеспечить паритет с продакшен-средами, хотя может вносить издержки на старте. Компромиссы, куда без них. ↩ [51]
Автор: Bright_Translate
Источник [52]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/17685
URLs in this post:
[1] Python: https://www.python.org/
[2] вот этот: https://github.com/cesarsotovalero/cesarsotovalero.github.io/blob/1fb2efe0577719a72fdf7d5bdf2a8d4d51ee58c5/scripts/fetch_all_youtube_videos.py
[3] моего канала на YouTube: https://www.youtube.com/channel/UCR4rI98w6-MqYoCS6jR9LGg
[4] файла JSON: https://github.com/cesarsotovalero/cesarsotovalero.github.io/blob/1fb2efe0577719a72fdf7d5bdf2a8d4d51ee58c5/_data/youtube-videos.json
[5] этой статичной странице: https://www.cesarsotovalero.net/youtube
[6] VSCode: https://code.visualstudio.com/
[7] 1: #id1
[8] 2: #id2
[9] вкусом: http://www.braintools.ru/article/6291
[10] 3: #id3
[11] более 680 комментариев: https://news.ycombinator.com/item?id=44579717
[12] 4: #id4
[13] запоминанием: http://www.braintools.ru/article/722
[14] потребности: http://www.braintools.ru/article/9534
[15] 5: #id5
[16] CCDS: https://cookiecutter-data-science.drivendata.org/
[17] логику: http://www.braintools.ru/article/7640
[18] uv: https://github.com/astral-sh/uv
[19] внимание: http://www.braintools.ru/article/7595
[20] 6: #id6
[21] Ruff: https://github.com/astral-sh/ruff
[22] PEP 8: https://pep8.org/
[23] ty: https://github.com/astral-sh/ty
[24] typing: https://docs.python.org/3/library/typing.html
[25] ошибки: http://www.braintools.ru/article/4192
[26] pytest: https://docs.pytest.org/en/stable/
[27] Pydantic: https://pydantic-docs.helpmanual.io/
[28] Pydantic Settings: https://docs.pydantic.dev/latest/concepts/pydantic_settings/
[29] MkDocs: https://www.mkdocs.org/
[30] 7: #id7
[31] FastAPI: https://fastapi.tiangolo.com/
[32] Dataclasses: https://docs.python.org/3/library/dataclasses.html
[33] GitHub Actions: https://github.com/features/actions
[34] 8: #id8
[35] боль: http://www.braintools.ru/article/9901
[36] Gitleaks: https://github.com/gitleaks/gitleaks
[37] pre-commit: https://pre-commit.com/
[38] ruff-pre-commit: https://github.com/astral-sh/ruff-pre-commit
[39] Make: https://www.gnu.org/software/make/
[40] Docker: https://www.docker.com/
[41] Docker Compose: https://docs.docker.com/compose/
[42] ↩: #id11
[43] Jupyter: https://jupyter.org/
[44] ↩: #id12
[45] ↩: #id13
[46] ↩: #id14
[47] ↩: #id15
[48] ↩: #id16
[49] GitHub Pages: https://pages.github.com/
[50] ↩: #id17
[51] ↩: #id18
[52] Источник: https://habr.com/ru/companies/ruvds/articles/930234/?utm_source=habrahabr&utm_medium=rss&utm_campaign=930234
Нажмите здесь для печати.