uv no macOS: gerir versões, projetos e ferramentas Python

Tradução automática Este artigo foi traduzido automaticamente a partir da versão original em inglês.

Mudei o meu fluxo de trabalho Python para uv. Substituiu a alternância entre pip, ambientes virtuais, pip-tools, pipx e gestores de projetos que fazia anteriormente. Agora, um único executável cobre a maior parte desse trabalho.

Os fluxos continuam a ser diferentes. Para quem desenvolve em Python e está a passar de pip, ambientes virtuais, pip-tools ou pipx para uv no macOS, um projeto, um script com metadados inline, uma CLI usada uma única vez e uma CLI instalada pertencem a ambientes e ciclos de vida diferentes. Os comandos abaixo mostram como escolher entre estas opções.

Início rápido

# 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 .

TL;DR. Dentro dos projetos, uso uv add, uv lock, uv sync e uv run. Uso metadados PEP 723 para scripts autónomos, uvx para ferramentas usadas uma única vez e uv tool install para comandos que têm de permanecer em PATH. Em CI, --locked verifica se os metadados do projeto estão de acordo com uv.lock. --frozen confia no lock existente sem verificar se está atualizado.

Instalar uv com um único gestor

O Homebrew é uma forma conveniente de instalar no macOS. Consulte o guia de instalação do uv para conhecer os métodos suportados:

brew install uv
uv --version

Se o Homebrew instalou o uv, deve ser o Homebrew a atualizá-lo:

brew upgrade uv

uv self update destina-se ao método de instalação autónoma do uv e está desativado para instalações feitas através de gestores de pacotes. Não permita que dois instaladores concorram pelo mesmo executável.

Comandos úteis para confirmar a identidade:

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

As instalações de Python geridas, as ferramentas persistentes e as entradas descartáveis da cache usam diretórios separados. A cache pode ser eliminada e reconstruída. Não é uma fonte de verdade.

As quatro fronteiras dos fluxos de trabalho do uvAs quatro fronteiras dos fluxos de trabalho do uv

Projetos: declarações, resolução e ambiente

Um projeto uv tem normalmente três artefactos:

  • pyproject.toml declara os metadados do projeto e os requisitos diretos.
  • uv.lock guarda a resolução multiplataforma do uv.
  • .venv é o ambiente local instalado e deve poder ser eliminado.

Entradas e estado derivado num projeto uvEntradas e estado derivado num projeto uv

Crie uma aplicação e adicione os requisitos de execução e de teste:

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

Os templates atuais de aplicações uv criam main.py, pyproject.toml, README.md e .python-version. Por predefinição, não definem um sistema de build. Use uv init --lib para uma biblioteca empacotada com um layout src e um build backend.

uv run verifica o projeto, atualiza o lock quando necessário, sincroniza as dependências exigidas e executa o comando. Consulte a documentação do uv sobre layout de projetos e sincronização para conhecer este ciclo de vida. A primeira operação no projeto cria .venv e uv.lock conforme necessário. uv init apenas cria os ficheiros do projeto.

Faça commit das entradas, não do ambiente

Faça commit de pyproject.toml, uv.lock, do código-fonte e de um .python-version intencional. Ignore .venv e as caches do uv.

O lock regista uma resolução para os marcadores e plataformas suportados. Não torna idênticos os wheels nativos no macOS, Linux, Intel e Apple Silicon. Teste todas as plataformas de deployment.

Atualização do lock: --locked não é --frozen

Por predefinição, os comandos do projeto podem atualizar uv.lock quando as declarações mudam. Consulte a documentação do uv sobre locking e sincronização para conhecer as flags de atualização e exatidão.

Use --locked para exigir que o lock esteja atualizado relativamente aos metadados do projeto:

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

Se pyproject.toml e uv.lock não coincidirem, estes comandos falham em vez de resolverem um novo lock. Essa falha é normalmente a barreira de CI pretendida.

Use --frozen apenas quando quiser intencionalmente que o uv use o lock existente sem verificar se está atualizado:

uv sync --frozen

Isto pode ser útil numa fase controlada do build em que o lock já foi validado, mas não deteta locks desatualizados.

A exatidão é uma definição separada da atualização. uv sync é exato por predefinição e remove pacotes excedentários. uv run usa uma sincronização inexata por predefinição, a menos que seja solicitado --exact.

Python gerido: um pedido de versão, não um binário universal

O uv pode descarregar e gerir distribuições Python:

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

As builds de CPython geridas pelo uv provêm do projeto python-build-standalone. O uv também pode descobrir interpretadores do sistema, do Homebrew, do pyenv, do Conda e de outras origens. Consulte a documentação do uv sobre versões Python para conhecer as origens suportadas e o comportamento de descoberta.

.python-version é um pedido de versão descoberto pelo uv e por outras ferramentas compatíveis. requires-python em pyproject.toml é o contrato de compatibilidade do projeto. Mantenha ambos de forma intencional:

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

Fixar 3.12 não garante para sempre a mesma build de patch nem o mesmo artefacto em todos os sistemas operativos. Peça um patch exato quando precisar de um específico e faça com que o CI selecione e reporte explicitamente o interpretador.

Use outro gestor de Python quando o projeto precisar de uma distribuição ou configuração de build que o uv não forneça. O uv pode continuar a usar esse interpretador através de --python ou da descoberta normal.

Escolher entre uv run, uvx e ferramentas instaladas

Escolher entre um projeto, uma ferramenta efémera ou uma ferramenta persistenteEscolher entre um projeto, uma ferramenta efémera ou uma ferramenta persistente

Ferramentas associadas ao projeto: uv run

Se pytest, mypy, um gerador de código ou outra ferramenta tiver de importar o projeto ou usar os seus plugins bloqueados, declare-a num grupo de dependências:

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

Executar essa ferramenta através de uvx iria isolá-la do projeto e poderia ocultar o pacote instalado ou os plugins de que necessita.

Ferramentas ad hoc: uvx

uvx é um alias de uv tool run. Cria um ambiente isolado armazenado na cache descartável do uv. A documentação do uv sobre tools descreve esta cache e o ciclo de vida das ferramentas persistentes:

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

Fixe a versão da ferramenta no CI ou na documentação. Uma primeira invocação sem versão seleciona uma release atual e as invocações posteriores podem reutilizar o estado da cache.

Executáveis persistentes: uv tool install

Instale uma ferramenta quando scripts fora do seu controlo precisarem do respetivo comando em PATH, ou quando o manifesto de configuração da máquina dever ser o responsável por essa ferramenta:

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

As ferramentas persistentes continuam a usar ambientes isolados. Não altere esses ambientes manualmente com pip.

Scripts: fazer de um único ficheiro a unidade de release

A PEP 723 define metadados inline para scripts. Um runner compatível pode ler o bloco de comentários e criar um ambiente isolado. Consulte o guia de scripts do uv e a PEP 723 para conhecer o formato dos metadados e o comportamento do runner.

Anatomia de um script PEP 723Anatomia de um script 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)

Execute o script e edite os metadados com uv:

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

O python fetch.py simples ignora os metadados nos comentários. Por isso, o script depende de um runner compatível, apesar de a sintaxe Python continuar válida.

Para um script que tenha de reproduzir mais tarde uma resolução, crie um lock adjacente:

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

Isto escreve fetch.py.lock. Um timestamp exclude-newer pode limitar as datas candidatas das distribuições, mas é mais fraco do que uma resolução exata bloqueada e não garante que um artefacto continue disponível.

Use um projeto quando vários ficheiros partilharem dependências, o código for importável como pacote, os testes precisarem do estado do projeto ou vários scripts tiverem de evoluir em conjunto.

Ficheiros de requirements: compatibilidade, não falha

Um ficheiro requirements.txt pode conter entradas flexíveis, pins exatos, hashes, constraints, índices, URLs ou uma exportação de requirements gerada pelo resolver. A sua reprodutibilidade depende da forma como foi produzido e consumido. O nome do ficheiro, por si só, não diz nada.

Use a interface compatível com pip do uv sem migrar:

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

uv pip sync faz com que o ambiente corresponda ao ficheiro. uv pip install -r é aditivo.

Numa aplicação sob sua responsabilidade, uma migração faseada pode ser útil:

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

Execute o percurso completo de testes e deployment antes de eliminar os ficheiros antigos. Se um sistema a jusante ainda esperar o formato do pip, derive-o do lock validado do uv com o comando de exportação do uv:

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

Não mantenha uv.lock e uma exportação editada manualmente como duas resoluções concorrentes.

Cache, índices e fronteiras da supply chain

A cache do uv acelera as instalações repetidas, mas continua a ser descartável:

uv cache prune
uv cache clean

Prefira prune para a limpeza habitual. clean remove todas as entradas da cache e obriga a novos downloads e builds posteriores.

Um lockfile melhora a repetibilidade. Não torna as dependências fiáveis. Reveja as origens dos pacotes, a configuração dos índices, as revisões Git, os build backends, as licenças e as credenciais. Mantenha a configuração de índices autenticados fora dos ficheiros versionados. A exceção é uma referência não secreta a um mecanismo de credenciais aprovado.

Para pacotes nativos, registe a arquitetura de deployment e teste a disponibilidade dos wheels. Caso contrário, o resolver pode recorrer a uma build a partir do código-fonte que necessite de compiladores e bibliotecas de sistema ausentes no CI ou em produção.

Checklist operacional compacto

Para cada projeto:

  1. Defina requires-python e as dependências diretas em pyproject.toml.
  2. Separe os extras publicados dos grupos de dependências locais.
  3. Faça commit de uv.lock. Ignore .venv e o estado da cache.
  4. Execute as ferramentas associadas ao projeto através do ambiente do projeto.
  5. Use uv lock --check ou --locked no CI.
  6. Teste todos os sistemas operativos e arquiteturas alvo representados pelo lock.
  7. Exporte formatos de compatibilidade apenas para consumidores a jusante identificados.
  8. Reveja a política de origens e credenciais independentemente da resolução.

O uv é mais útil quando estas fronteiras de responsabilidade permanecem visíveis. Um único binário pode geri-las todas sem as transformar num único ambiente. Menos ferramentas, mantendo separados os quatro fluxos de trabalho. É por isso que continuo a usá-lo como ferramenta Python predefinida para projetos no macOS.

Referências