Conteúdos

UV: ambientes e dependências Python com velocidade e praticidade

Gerenciar um projeto Python envolve mais do que escrever os arquivos da aplicação. É preciso escolher a versão do Python, criar o ambiente virtual, instalar dependências, registrar versões e repetir tudo isso em outra máquina ou durante um deploy.

O uv reúne essas tarefas em uma ferramenta única e rápida. Desenvolvido em Rust, ele resolve dependências, aproveita cache e evita que eu precise alternar entre várias ferramentas para preparar o ambiente. A praticidade não está apenas em executar comandos mais velozes: está em deixar o estado do projeto explícito e fácil de reproduzir.

Criando um projeto

uv init --python 3.12 meu-projeto
cd meu-projeto
uv add requests
uv run python -c "import requests; print(requests.__version__)"

O uv init cria a estrutura inicial e o pyproject.toml. O uv add requests registra a dependência e atualiza o arquivo de lock. Com uv run, o comando é executado no ambiente do projeto, sem que eu precise ativar manualmente o .venv em cada terminal.

Para ferramentas usadas apenas durante o desenvolvimento, separo a dependência:

uv add --dev ruff pytest

Essa distinção deixa claro o que a aplicação precisa para funcionar em produção e o que só é necessário para testar, formatar ou desenvolver o código.

O pyproject.toml e o uv.lock

O pyproject.toml descreve o projeto, suas dependências diretas e os grupos de desenvolvimento. Já o uv.lock registra uma resolução completa, incluindo versões específicas e dependências transitivas.

Versiono o pyproject.toml e o uv.lock no repositório. Não versiono o .venv, porque ele contém arquivos gerados para uma máquina específica. Em outra máquina, o ambiente pode ser reconstruído a partir dos arquivos que realmente descrevem o projeto.

Quando adiciono ou removo uma dependência, deixo o uv atualizar o lockfile:

uv add fastapi
uv remove requests
uv lock

O lockfile não elimina a necessidade de atualizar dependências com cuidado. Ele torna o resultado conhecido, mas ainda preciso revisar versões, changelogs e vulnerabilidades antes de aceitar uma atualização importante.

Velocidade que ajuda no dia a dia

O uv é rápido por combinar uma implementação eficiente com cache de downloads e uma resolução de dependências própria. Depois que um pacote está no cache, criar outro ambiente ou preparar novamente um projeto tende a exigir menos trabalho e menos espera.

Essa velocidade faz diferença quando alterno entre projetos, recrio um ambiente quebrado ou inicio uma máquina de CI. A ferramenta não faz a rede ficar mais rápida, e o primeiro download ainda depende do índice e do servidor de pacotes. O ganho aparece principalmente na redução de etapas e no reaproveitamento do que já foi baixado.

Também posso instalar e selecionar versões do Python pelo próprio uv:

uv python install 3.12
uv python pin 3.12

O arquivo de versão ajuda a comunicar qual interpretador o projeto espera. Mesmo assim, continuo validando a combinação com o sistema operacional e com as bibliotecas nativas usadas pela aplicação.

O papel do uv sync

O comando mais importante para repetir o ambiente é o uv sync:

uv sync

Ele lê a configuração do projeto, resolve o estado definido e sincroniza o ambiente virtual. Se uma dependência foi removida do projeto, o ambiente também pode ser ajustado para não carregar componentes que já não fazem parte da configuração.

Para garantir que o deploy não altere o lockfile silenciosamente, uso a opção --locked:

uv sync --locked

Se o pyproject.toml e o uv.lock estiverem inconsistentes, o comando falha e me obriga a corrigir o estado antes de continuar. Prefiro esse erro explícito a descobrir em produção que a máquina instalou versões diferentes das testadas.

Deploy mais rápido e previsível

O uv agiliza o deploy porque reduz o processo a uma preparação reproduzível. Depois de obter o código e o lockfile no servidor, posso sincronizar apenas as dependências de produção:

uv sync --locked --no-dev
uv run --no-dev python -m minha_aplicacao

O --no-dev evita instalar Ruff, pytest e outras ferramentas que não são necessárias para executar o serviço. O --locked garante que o servidor use a resolução registrada no repositório.

Em uma pipeline, também posso usar o cache do uv entre execuções. Isso reduz o tempo gasto baixando novamente os mesmos pacotes, mas não dispensa invalidar o cache quando a versão do Python, o lockfile ou a plataforma mudarem.

O deploy continua precisando de outras decisões: variáveis de ambiente, migrações de banco, arquivos estáticos, serviços externos, usuário de execução e rollback. O uv acelera a preparação do ambiente Python; ele não substitui a estratégia de publicação da aplicação.

Migrando um projeto existente

Em um projeto que já usa requirements.txt, começo identificando a versão do Python, as dependências diretas e os comandos usados pela equipe. Não apago o ambiente antigo antes de comparar a aplicação executando com o novo fluxo.

Depois registro as dependências no pyproject.toml, gero o uv.lock e executo os testes. Também documento os comandos para que outra pessoa consiga criar o ambiente sem depender de detalhes da minha máquina.

A migração é uma boa oportunidade para separar dependências de produção e de desenvolvimento, adicionar o Ruff e incluir uma verificação de vulnerabilidades. A ferramenta nova não deve esconder diferenças de versão ou transformar uma mudança de gerenciamento em uma atualização involuntária de tudo.

Cuidados com a reprodução

Para que o fluxo seja realmente reproduzível, mantenho o lockfile atualizado no mesmo commit da alteração de dependências. Em desenvolvimento posso usar uv lock para resolver mudanças; na CI e no deploy, prefiro uv sync --locked.

Também verifico a versão do Python e o sistema operacional. Alguns pacotes possuem extensões nativas ou dependem de bibliotecas do sistema, e nenhum gerenciador consegue remover essas diferenças completamente.

Minha conclusão

O uv tornou mais simples o ciclo de vida dos meus projetos Python. Ele cria ambientes, adiciona dependências, escolhe versões do interpretador, executa comandos e mantém o lockfile sem exigir uma coleção de ferramentas diferentes.

A velocidade ajuda no desenvolvimento e na CI, mas a praticidade é o ganho mais importante. Com uv sync --locked --no-dev, o deploy consegue preparar apenas o necessário e falhar cedo quando o estado versionado não corresponde à configuração do projeto.

Ainda preciso testar a aplicação, revisar atualizações e configurar corretamente o ambiente de produção. O uv não substitui esses cuidados, mas reduz bastante o trabalho repetitivo e deixa mais claro o que está sendo instalado em cada etapa.