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 pytestEssa 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 lockO 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.12O 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 syncEle 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 --lockedSe 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_aplicacaoO --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.