Тулчейн 2025 года: uv для окружений и локов, ruff для линта и форматирования, один pyproject.toml
uv сворачивает pip, venv, pipx и pip-tools в один Rust-бинарь: быстрый резолвер, универсальный lock-файл, uv run и uvx. ruff заменяет flake8, isort и black одним проходом. Вся конфигурация — в pyproject.toml, а миграция с легаси идёт инкрементально, без переписывания.
CI-пайплайн занимал четырнадцать минут, и шесть из них — pip install. Ещё девяносто секунд уходило на линт-стадию, где flake8, isort и black бежали по очереди — и дважды в месяц дрались: black переносил блок импортов, isort «чинил» его обратно, и сборка падала на диффе, который не писал ни один человек. Онбординг был хуже. В README настройка окружения занимала одиннадцать шагов, два из которых были неверны со времён перехода на Python 3.11; новички теряли первый день на pip, сорок минут резолвивший граф и падавший на конфликте транзитивных пинов, который requirements.txt не умел даже выразить. Миграция заняла у одного инженера два дня: uv sync восстановил залоченное окружение за восемь секунд на тёплом кеше, uv run pytest сделал «у меня работает» синонимом lock-файла, а ruff check && ruff format заменил три ссорящихся инструмента одним проходом, который заканчивался раньше, чем старый стек успевал импортировать свои плагины. Пайплайн теперь укладывается в пять минут; venv с тех пор не дебажил никто.
uv: один бинарь там, где раньше было пять инструментов
uv (Astral, написан на Rust) сворачивает pip, venv, pip-tools, pipx и бо́льшую часть pyenv в один бинарь. Основной цикл: uv add httpx записывает зависимость в pyproject.toml, резолвит весь граф и пинит его в uv.lock; uv sync приводит проектный .venv в точное соответствие lock-файлу — удаляя лишнее, а не только доставляя; uv run pytest гарантирует, что команда выполнится в этом синхронизированном окружении, создав его на лету, если коллега только что склонировал репозиторий. uvx ruff запускает инструмент в изолированном кешируемом окружении — pipx без второго инструмента. uv python install 3.12 управляет даже интерпретаторами. Тяжёлую работу делают два свойства. Первое — скорость: резолвер в 10–100 раз быстрее pip на тёплом кеше (опубликованные бенчмарки Astral; холодные установки ближе к 8–10×), и «перерезолвить весь граф с нуля» превращается из перерыва на кофе в мгновение — именно это делает практичным постоянный резолв из pyproject. Второе — lock-файл: uv.lock универсален — резолвится один раз для всех платформ и версий Python, с хешами, — так что линуксовая CI-машина и макбук ставят байт-в-байт одинаковые графы. Честные оговорки: uv.lock — собственный формат uv (стандарт, pylock.toml из PEP 751, только формируется), а инструмент молод и финансируется венчуром, — но поскольку все входные данные живут в стандартном pyproject.toml, путь отступления назад к pip реален.
# /// script
# requires-python = ">=3.12"
# dependencies = ["httpx"]
# ///
# PEP 723 inline metadata: `uv run fetch.py` строит одноразовое
# окружение по требованию — никаких requirements.txt для одного скрипта.
import httpx
print(httpx.get("https://example.com").status_code)Коллега клонирует репозиторий и сразу запускает uv run pytest без какой-либо настройки. Что произойдёт?
ruff: линт и формат за один проход
Когда линт живёт только в CI и занимает 90 секунд, инженеры перестают на него реагировать. Когда он отрабатывает за доли секунды на каждом сохранении, он становится таким же мгновенным фидбеком, как синтаксическая ошибка, — и это меняет, какие баги ловятся до ревью. Вот главный аргумент в пользу ruff.
ruff — та же консолидация, применённая к качеству кода: один Rust-бинарь, который переимплементирует flake8 (плюс большинство его популярных плагинов), isort, pyupgrade и изрядный кусок pylint как ruff check, и black-совместимый форматтер как ruff format (Astral измеряет совместимость >99,9% идентичности с black на собственном корпусе тестов black). Правила включаются семействами в pyproject.toml: E/F (pycodestyle/pyflakes) как база, I — сортировка импортов, B — вероятные баги из flake8-bugbear, UP — устаревший синтаксис; --fix автоматически применяет безопасные исправления. Скорость — заглавная цифра, и это не маркетинг: ruff линтит кодовые базы масштаба CPython сильно меньше чем за секунду там, где стек flake8 тратил десятки секунд, — достаточно быстро, чтобы гонять весь набор правил на каждое сохранение, и это меняет поведение: линт перестаёт быть CI-занудой и становится фидбеком прямо при наборе. Консолидация заодно прекращает драки трёх инструментов из Хука: один инструмент с одним конфигом не может спорить сам с собой о переносе импортов. Компромиссы честно: у части плагинных правил flake8 и некоторых проверок pylint эквивалента в ruff нет, и ruff сознательно не проверяет типы — он дополняет mypy/pyright, а не заменяет их.
pyproject.toml как единый конфиг и путь миграции
Всё перечисленное читает конфигурацию из одного файла:
[project]
name = "billing"
requires-python = ">=3.12"
dependencies = ["httpx>=0.27", "pydantic>=2.7"]
[tool.ruff]
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP"]
[tool.uv]
dev-dependencies = ["pytest>=8", "mypy>=1.10"]Один файл заменяет requirements.txt, requirements-dev.txt, setup.cfg, .flake8, .isort.cfg и старую black-секцию. Миграция с легаси инкрементальна, и порядок важен: (1) сначала ruff — это чисто конфиг, нулевой рантайм-риск; начните с E,F, удалите конфиги flake8/isort/black, расширяйте семейства правил по одному PR, чтобы дифф оставался ревьюируемым. (2) Используйте uv как быстрый pip без смены процесса: uv pip install -r requirements.txt — прямая замена, доказывающая скорость на вашем же репозитории. (3) Перенесите зависимости в pyproject.toml через uv add, закоммитьте uv.lock, переключите CI на uv sync --frozen, чтобы сборка падала при протухшем lock-файле, а не молча перерезолвивала. (4) Замените глобальные установки инструментов на uvx. Антипаттерн — один big-bang PR, меняющий пакетирование, линт и форматирование одновременно: когда что-то ломается, невозможно сказать, какой слой виноват.
Чем коммит uv.lock отличается от коммита снапшота pip freeze > requirements.txt?
- 01Расскажите, что делают uv add, uv sync, uv run и uvx, и чем uv.lock отличается от снапшота pip freeze.
- 02Каков безопасный порядок миграции с pip+flake8+isort+black и почему big-bang-замена — антипаттерн?
Python-тулчейн 2025 года — история консолидации. uv заменяет pip, venv, pip-tools, pipx и значительную часть pyenv одним Rust-бинарём: uv add резолвит и пинит в универсальный хеш-верифицированный uv.lock; uv sync приводит окружение в точное соответствие; uv run гарантирует выполнение команд внутри этого окружения, собирая его по требованию из свежего клона; uvx гоняет инструменты в изолированных кешах; uv python install управляет интерпретаторами. Резолв на тёплом кеше в 10–100 раз быстрее pip — именно поэтому lockfile-first-процессы ощущаются бесплатными, а lock-файл резолвится один раз для всех платформ — конец «на CI-машине резолвится иначе». ruff применяет ту же консолидацию к качеству: ruff check переимплементирует flake8 с его экосистемой плагинов, isort и pyupgrade; ruff format совместим с black на >99,9% на корпусе самого black; один проход на масштабе CPython укладывается сильно меньше чем в секунду — линт переезжает из CI-зануды в фидбек на сохранение, а один конфиг прекращает войны black против isort. Всё читает pyproject.toml — зависимости, выбор правил, длину строки — вместо полудюжины дотфайлов. Мигрируйте в порядке риска: ruff (только конфиг), uv как drop-in pip, затем pyproject.toml плюс закоммиченный lock с uv sync --frozen в CI, затем uvx; и никогда одним big-bang PR — первую поломку будет не атрибутировать. Оговорки остаются оговорками: uv.lock специфичен для инструмента, пока зреет PEP 751, ruff не проверяет типы, а у пары правил flake8/pylint эквивалента пока нет. Теперь, когда увидите CI-пайплайн, который тратит шесть минут на pip install или линт-стадию, ссорящуюся сама с собой из-за переносов импортов, вы знаете точный четырёхшаговый план миграции — и точную причину начать с ruff, а не с lock-файла.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.