uv на macOS: управление версиями Python, проектами и инструментами

Автоматический перевод Эта статья была автоматически переведена с оригинальной английской версии.

Я перевёл свой Python-воркфлоу на uv. Он заменил переключение между pip, виртуальными окружениями, pip-tools, pipx и менеджерами проектов, которым я раньше занимался вручную. Теперь один исполняемый файл покрывает большую часть этих задач.

При этом сами воркфлоу остаются разными. Для Python-разработчиков, переходящих на uv в macOS с pip, виртуальных окружений, pip-tools или pipx, проект, скрипт с inline-метаданными, разовый CLI-инструмент и установленный CLI-инструмент относятся к разным окружениям и жизненным циклам. Ниже показано, как выбирать между ними.

Быстрый старт

# Install uv with Homebrew
brew install uv

# Start a project
uv init example-app
cd example-app
uv add httpx
uv run python main.py

# Run an isolated CLI without installing it permanently
uvx ruff check .

Коротко. В проектах я использую uv add, uv lock, uv sync и uv run. Для самодостаточных скриптов использую метаданные PEP 723, для разовых инструментов — uvx, а для команд, которые должны оставаться доступными в PATH, — uv tool install. В CI --locked проверяет, что метаданные проекта согласуются с uv.lock. --frozen доверяет существующему lock-файлу и не проверяет его актуальность.

Установка uv с одним владельцем

Homebrew — удобный способ установить uv в macOS. Поддерживаемые варианты перечислены в руководстве по установке uv:

brew install uv
uv --version

Если uv установил Homebrew, обновлять его тоже должен Homebrew:

brew upgrade uv

uv self update предназначен для автономного способа установки uv и отключён для установок через менеджеры пакетов. Не допускайте, чтобы два установщика конкурировали за один и тот же исполняемый файл.

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

command -v uv
uv python dir
uv tool dir
uv cache dir

У управляемых установок Python, постоянных инструментов и одноразовых записей в кэше разные каталоги. Кэш можно удалить и пересоздать. Он не является источником истины.

Четыре границы воркфлоу uvЧетыре границы воркфлоу uv

Проекты: объявления, резолвинг и окружение

У проекта uv обычно есть три артефакта:

  • pyproject.toml содержит метаданные проекта и прямые зависимости.
  • uv.lock хранит кроссплатформенный результат резолвинга uv.
  • .venv — локальное установленное окружение, которое должно быть одноразовым.

Входные данные и производное состояние проекта uvВходные данные и производное состояние проекта uv

Создайте приложение и добавьте зависимости для рантайма и тестов:

uv init forecast-app
cd forecast-app

uv add httpx
uv add --group test pytest
uv run python main.py
uv run --group test pytest

Актуальные шаблоны приложений uv создают main.py, pyproject.toml, README.md и .python-version. По умолчанию они не задают build-систему. Для упаковываемой библиотеки с layout src и build-бэкендом используйте uv init --lib.

uv run проверяет проект, при необходимости обновляет lock-файл, синхронизирует обязательные зависимости и запускает команду. Подробнее об этом жизненном цикле см. в документации uv по структуре проекта и синхронизации. Первая операция над проектом при необходимости создаёт .venv и uv.lock. Сама команда uv init создаёт только файлы проекта.

Коммитьте входные данные, а не окружение

Коммитьте pyproject.toml, uv.lock, исходный код и осознанно выбранный .python-version. Добавьте в ignore .venv и кэши uv.

Lock-файл фиксирует резолвинг с учётом поддерживаемых маркеров и платформ. Он не делает native wheels идентичными в macOS, Linux, на Intel и Apple Silicon. Тестируйте каждую платформу деплоя.

Актуальность lock-файла: --locked — это не --frozen

По умолчанию команды проекта могут обновлять uv.lock при изменении объявлений. О флагах актуальности и точности см. документацию uv по блокировке и синхронизации.

Используйте --locked, чтобы потребовать актуальный lock-файл, согласованный с метаданными проекта:

uv lock --check
uv sync --locked --group test
uv run --locked --group test pytest

Если pyproject.toml и uv.lock расходятся, эти команды завершаются с ошибкой, а не создают новый lock-файл резолвингом. Обычно именно такую ошибку и должен ловить CI-гейт.

Используйте --frozen только тогда, когда намеренно хотите, чтобы uv применил существующий lock-файл без проверки его актуальности:

uv sync --frozen

Это может быть полезно на контролируемом этапе сборки, если lock-файл уже прошёл валидацию, но такая команда не обнаруживает устаревшие lock-файлы.

Точность — отдельная настройка, не связанная с актуальностью. uv sync по умолчанию работает точно и удаляет лишние пакеты. uv run по умолчанию выполняет неточную синхронизацию, если явно не передан --exact.

Управляемый Python: запрос версии, а не универсальный бинарник

uv умеет скачивать и управлять дистрибутивами Python:

uv python install 3.12 3.13
uv python list --only-installed
uv python pin 3.12

Управляемые сборки CPython в uv поставляются проектом python-build-standalone. Кроме того, uv может обнаруживать системные интерпретаторы, версии из Homebrew, pyenv, Conda и другие. Поддерживаемые источники и поведение обнаружения описаны в документации uv по версиям Python.

.python-version — это запрос версии, который обнаруживают uv и другие совместимые инструменты. requires-python в pyproject.toml — контракт совместимости проекта. Делайте оба параметра осознанными:

[project]
requires-python = ">=3.12,<3.14"

Фиксация 3.12 не гарантирует одну и ту же patch-сборку навсегда или один и тот же артефакт во всех операционных системах. Когда нужна конкретная patch-версия, запрашивайте её явно, а в CI явно выбирайте и выводите информацию об интерпретаторе.

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

Выбор между uv run, uvx и установленными инструментами

Выбор между окружением проекта, одноразовым и постоянным инструментомВыбор между окружением проекта, одноразовым и постоянным инструментом

Инструменты, связанные с проектом: uv run

Если pytest, mypy, генератор кода или другой инструмент должен импортировать проект либо использовать его заблокированные плагины, объявите его в группе зависимостей:

uv add --group lint ruff
uv run --group lint ruff check .

Запуск такого инструмента через uvx изолирует его от проекта и может скрыть установленный пакет или нужные ему плагины.

Разовые инструменты: uvx

uvx — это псевдоним для uv tool run. Он создаёт изолированное окружение, хранящееся в одноразовом кэше uv. В документации uv по инструментам описаны этот кэш и жизненный цикл постоянных инструментов:

uvx ruff@0.12.0 check .
uvx --from jupyterlab jupyter lab
uvx --with mkdocs-material mkdocs --help

Фиксируйте версию инструмента в CI или документации. Первый вызов без версии выбирает актуальный релиз, а последующие вызовы могут повторно использовать состояние кэша.

Постоянные исполняемые файлы: uv tool install

Устанавливайте инструмент, если скриптам вне вашего контроля нужна его команда в PATH или если манифест конфигурации машины должен владеть этой установкой:

uv tool install ruff
uv tool list
uv tool upgrade ruff
uv tool uninstall ruff

Постоянные инструменты по-прежнему используют изолированные окружения. Не изменяйте эти окружения вручную через pip.

Скрипты: один файл как единица поставки

PEP 723 определяет inline-метаданные скрипта. Совместимый раннер может прочитать блок комментариев и создать изолированное окружение. Формат метаданных и поведение раннера описаны в руководстве uv по скриптам и PEP 723.

Анатомия скрипта PEP 723Анатомия скрипта PEP 723

# /// script
# requires-python = ">=3.12"
# dependencies = [
#   "httpx>=0.27,<1",
# ]
# ///
import httpx

response = httpx.get("https://example.com", timeout=10)
response.raise_for_status()
print(response.status_code)

Редактируйте метаданные и запускайте скрипт с помощью uv:

uv run fetch.py
uv add --script fetch.py rich
uv remove --script fetch.py rich

Обычный python fetch.py игнорирует метаданные в комментариях. Поэтому скрипт зависит от совместимого раннера, хотя синтаксис Python остаётся корректным.

Если скрипт должен позднее воспроизводить тот же результат резолвинга, создайте рядом с ним lock-файл:

uv lock --script fetch.py
uv run --script fetch.py

Будет создан fetch.py.lock. Метка времени exclude-newer может ограничить даты дистрибутивов-кандидатов, но это слабее точного заблокированного результата резолвинга и не гарантирует, что артефакт останется доступным.

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

Файлы requirements: совместимость, а не гарантия

Файл requirements.txt может содержать неточные входные данные, точные pin-версии, хэши, ограничения, индексы, URL или экспорт requirements, созданный резолвером. Воспроизводимость зависит от способа его создания и использования. Одно имя файла ничего не говорит.

Используйте совместимый с pip интерфейс uv без миграции:

uv venv
uv pip sync requirements.txt
uv run python app.py

uv pip sync приводит окружение в соответствие с файлом. uv pip install -r добавляет зависимости, не удаляя уже установленные.

Для приложения, которым вы владеете, может быть полезна поэтапная миграция:

uv init --bare
uv add -r requirements.in
uv add --group test -r requirements-test.in
uv lock
uv sync --locked

Перед удалением старых файлов выполните полный путь тестирования и деплоя. Если downstream-системе по-прежнему нужен формат pip, получите его из провалидированного lock-файла uv с помощью команды экспорта uv:

uv export --format requirements.txt \
  --output-file requirements.txt

Не поддерживайте uv.lock и экспорт, отредактированный вручную, как два конкурирующих результата резолвинга.

Кэш, индексы и границы supply chain

Кэш uv ускоряет повторные установки, но остаётся одноразовым:

uv cache prune
uv cache clean

Для обычной очистки предпочтительно использовать prune. clean удаляет все записи кэша и заставляет последующие команды заново скачивать и собирать пакеты.

Lock-файл повышает воспроизводимость. Но он не делает зависимости надёжными. Проверяйте источники пакетов, конфигурацию индексов, Git-ревизии, build-бэкенды, лицензии и учётные данные. Не храните конфигурацию аутентифицированных индексов в коммитах. Исключение — несекретная ссылка на одобренный механизм получения учётных данных.

Для native-пакетов фиксируйте архитектуру деплоя и проверяйте доступность wheel-файлов. Иначе резолвер может перейти к сборке из исходников, для которой нужны компиляторы и системные библиотеки, отсутствующие в CI или продакшене.

Краткий операционный чек-лист

Для каждого проекта:

  1. Задайте requires-python и прямые зависимости в pyproject.toml.
  2. Отделяйте публикуемые extras от локальных групп зависимостей.
  3. Коммитьте uv.lock. Добавьте в ignore .venv и состояние кэша.
  4. Запускайте связанные с проектом инструменты через окружение проекта.
  5. Используйте в CI uv lock --check или --locked.
  6. Тестируйте каждую целевую ОС и архитектуру, представленную в lock-файле.
  7. Экспортируйте совместимые форматы только для явно указанных downstream-потребителей.
  8. Проверяйте политику источников и учётных данных независимо от резолвинга.

uv наиболее полезен, когда эти границы владения остаются видимыми. Один бинарник может управлять ими всеми, не превращая их в одно окружение. Инструментов становится меньше, а четыре воркфлоу остаются раздельными. Поэтому uv по-прежнему остаётся моим основным инструментом для Python-проектов в macOS.

Ссылки