Migrar de poetry para uv e acelerar suas builds em 10x

Aprenda a migrar de poetry para uv de forma segura mantendo dependências exatas e acelere suas builds no Python 3.14 em até 10 vezes.

Migrar de poetry para uv e acelerar suas builds em 10x
Fonte (Acervo pessoal/maiastudios.com.br)

O ecossistema de empacotamento em Python passou por transformações profundas no último ano. Durante muito tempo, o Poetry se consolidou como a ferramenta padrão para gerenciamento de dependências, resolução de ambientes virtuais e publicação de pacotes. No entanto, o surgimento do uv mudou drasticamente as expectativas de velocidade no fluxo de trabalho. Desenvolvido em Rust, ele substitui ferramentas como pip, pip-tools, virtualenv e o próprio Poetry com um ganho de desempenho expressivo. Decidir migrar de poetry para uv não é apenas uma questão de acompanhar tendências, mas uma melhoria direta de produtividade que reduz o tempo de execução de CI/CD de minutos para poucos segundos no Python 3.14.7.

Neste artigo, vou demonstrar o processo prático para realizar essa transição de forma estruturada, mantendo a reprodutibilidade exata das dependências do seu projeto e evitando incidentes em ambientes de produção.

Por que a transição do Poetry para ferramentas modernas se tornou essencial?

O Poetry utiliza um resolvedor de dependências próprio escrito em Python puro. Embora seja preciso na garantia de consistência, o algoritmo de resolução (SAT solver) frequentemente enfrenta gargalos severos ao lidar com árvores de dependências profundas ou com múltiplos pacotes pesados. Em projetos corporativos, uma simples adição de biblioteca pode exigir dezenas de segundos — ou até minutos — apenas para recalcular a árvore e atualizar o arquivo de trava.

O uv aborda esse problema reescrevendo todo o motor de resolução e download do zero. Ele implementa um resolvedor de dependências concorrente extremamente rápido, capaz de efetuar o download e a compilação de wheels em uma fração do tempo consumido por ferramentas tradicionais. Além da velocidade, o uv adota por padrão os padrões oficiais da Python Packaging Authority (PyPA), especificamente o PEP 621 para a definição de metadados de projetos dentro do arquivo pyproject.toml.

O Poetry, por ter surgido antes da consolidação do PEP 621, utiliza uma seção customizada ([tool.poetry]) que isola a configuração do projeto do padrão moderno. A migração alinha seu código às especificações universais da comunidade, eliminando o acoplamento com uma ferramenta específica.

Como migrar de poetry para uv no seu projeto sem quebrar dependências?

Ilustração vetorial em fundo escuro comparando dois fluxos de resolução de dependências, um longo e sinuoso e outro rápido e direto com setas ciano e violeta.
Fonte (Acervo pessoal/maiastudios.com.br)

O maior receio ao trocar de gerenciador de pacotes é alterar inadvertidamente a versão de uma biblioteca transitiva que está rodando em produção. O processo de conversão deve ser feito de forma a preservar o estado atual da aplicação, migrando as declarações primárias e reeditando a trava com garantias equivalentes.

Passo 1: Converter a seção de dependências para o padrão PEP 621

No Poetry, as dependências ficam sob [tool.poetry.dependencies] e [tool.poetry.group.dev.dependencies]. No padrão PEP 621, utilizado pelo uv, as dependências de produção residem em project.dependencies, e os grupos de desenvolvimento ficam em dependency-groups (conforme o PEP 735) ou em project.optional-dependencies.

Você pode fazer a transição manualmente ou utilizar o comando de automação do próprio uv para ler o projeto atual. Para fazer a migração direta da estrutura no seu pyproject.toml, modifique a organização das chaves.

Estrutura legada no Poetry:

[tool.poetry]
name = "meu-sistema"
version = "0.1.0"
description = "API de processamento de dados"
authors = ["Dev Team <dev@empresa.com>"]

[tool.poetry.dependencies]
python = "^3.14"
fastapi = "^0.115.0"
uvicorn = {extras = ["standard"], version = "^0.30.0"}

[tool.poetry.group.dev.dependencies]
pytest = "^8.0.0"
ruff = "^0.6.0"

Estrutura convertida para o padrão PEP 621 aceito pelo uv:

[project]
name = "meu-sistema"
version = "0.1.0"
description = "API de processamento de dados"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
    "fastapi>=0.115.0",
    "uvicorn[standard]>=0.30.0",
]

[dependency-groups]
dev = [
    "pytest>=8.0.0",
    "ruff>=0.6.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

Note que substituímos o backend de compilação poetry-core pelo hatchling (ou flit_core), que são backends modernos e leves suportados pelo ecossistema padrão.

Passo 2: Exportar o lockfile antigo para garantir integridade

Para garantir que nenhuma subdependência mude de versão durante a primeira sincronização com o uv, a melhor estratégia é exportar a árvore exata do Poetry para um arquivo temporário do pip antes de gerar o novo uv.lock.

Execute o comando de exportação no Poetry:

poetry export -f requirements.txt --output requirements-temp.txt --with dev

Em seguida, utilize o uv para compilar um novo arquivo de trava compilado rigorosamente a partir dessas versões exatas, garantindo que a resolução inicial seja idêntica ao que existia no poetry.lock:

uv pip compile requirements-temp.txt -o requirements-resolved.txt

Agora você pode gerar o arquivo uv.lock nativo executando a inicialização do projeto com o uv:

uv lock

Após validar a criação do uv.lock, o arquivo poetry.lock e o arquivo temporário requirements-temp.txt podem ser removidos com segurança do repositório.

Passo 3: Criar o ambiente virtual e sincronizar

Com a nova configuração pronta, remova a pasta do ambiente virtual antigo gerenciado pelo Poetry (geralmente localizada em ~/.cache/pypoetry/virtualenvs ou na raiz em .venv) e deixe o uv criar o novo ambiente isolado:

# Remove o ambiente antigo se estiver na raiz
rm -rf .venv

# Cria o novo ambiente virtual vinculado ao Python 3.14.7
uv venv

# Sincroniza o ambiente com as dependências do uv.lock
uv sync

O comando uv sync faz a instalação atômica e extremamente rápida de todos os pacotes, deixando o .venv pronto para uso imediato.

Como ajustar o pipeline de CI/CD para utilizar o novo gerenciador?

Um dos maiores retornos ao adotar o uv ocorre nas etapas de automação e integração contínua (CI/CD). O tempo gasto preparando o ambiente Python nos runners de nuvem cai drasticamente devido ao sistema de cache global do uv e à ausência de overhead de compilação.

Em um pipeline típico do GitHub Actions, o passo de instalação que anteriormente exigia a configuração do Python, instalação do Poetry via pip e execução do poetry install pode ser simplificado usando a action oficial astral-sh/setup-uv.

Abaixo está um exemplo comparativo de um fluxo de trabalho configurado para testar e validar o código com uv:

name: Pipeline de Integração Contínua

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Instalar uv
        uses: astral-sh/setup-uv@v3
        with:
          version: "latest"
          enable-cache: true

      - name: Configurar Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.14"

      - name: Sincronizar Dependências
        run: uv sync --all-groups

      - name: Executar Testes
        run: uv run pytest

A flag enable-cache: true instrui a action a armazenar em cache as camadas de pacotes do uv. Como o download de pacotes wheels ocorre de forma paralela e otimizada em Rust, o tempo de instalação das dependências no runner cai comumente de 45 segundos para menos de 3 segundos.

No ambiente de conteinerização com Docker, a otimização também é evidente. Veja como estruturar um Dockerfile focado em produção usando multi-stage builds:

FROM python:3.14-slim AS builder

# Copia o executável do uv diretamente da imagem oficial
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv

WORKDIR /app

# Copia apenas os arquivos de definição para otimizar o cache de camadas
COPY pyproject.toml uv.lock ./

# Sincroniza as dependências sem instalar o projeto atual e sem pacotes de dev
RUN uv sync --frozen --no-dev --no-install-project

FROM python:3.14-slim

WORKDIR /app

# Copia o ambiente virtual compilado do estágio anterior
COPY --from=builder /app/.venv /app/.venv
COPY .

ENV PATH="/app/.venv/bin:$PATH"

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

A flag --frozen garante que o uv não tentará atualizar o uv.lock durante o build do container, falhando imediatamente caso o arquivo de trava esteja dessincronizado com o pyproject.toml.

Quais são os principais problemas ao trocar de gerenciador e como resolvê-los?

Fotografia em plano detalhado de um teclado mecânico iluminado em bancada escura com monitor ultrawide desfocado ao fundo emitindo luz ciano.
Fonte (Acervo pessoal/maiastudios.com.br)

Embora o processo de migração seja bastante direto, existem particularidades de configuração e comportamentos de borda que podem gerar erros caso não sejam antecipados.

Tratando repositórios privados e índices customizados

Se o seu projeto depende de um servidor PyPI privado (como Nexus, Artifactory ou AWS CodeArtifact), a configuração no Poetry era feita através dos comandos poetry config repositories. No uv, a definição de índices privados é declarada diretamente no pyproject.toml usando a especificação [[tool.uv.index]] ou via variáveis de ambiente.

Exemplo de declaração no pyproject.toml:

[[tool.uv.index]]
name = "repositorio-interno"
url = "https://pypi.empresa.com/simple/"
explicit = true

Ao marcar explicit = true, você evita que o uv busque pacotes públicos no PyPI oficial para dependências registradas nesse repositório, prevenindo ataques do tipo dependency confusion.

Gerenciamento de scripts de linha de comando

No Poetry, era comum definir pontos de entrada na seção [tool.poetry.scripts]. Com o PEP 621, esses comandos devem ser reescritos na seção [project.scripts].

# Formato antigo do Poetry
# [tool.poetry.scripts]
# meu-cli = "meu_pacote.cli:main"

# Formato padrão PEP 621 para o uv
[project.scripts]
meu-cli = "meu_pacote.cli:main"

Ao executar uv pip install -e . ou uv sync, o executável meu-cli será instalado dentro do diretório bin do ambiente virtual .venv e poderá ser invocado com uv run meu-cli.

Diferenças na resolução de pacotes editáveis

Projetos que utilizam monorepos ou dependências locais vinculadas via -e (editable) exigem atenção. No Poetry, dependências de caminho local eram declaradas com { path = "../outro-pacote", develop = true }. No uv, a sintaxe segue o padrão de fontes do uv:

[tool.uv.sources]
outro-pacote = { path = "../outro-pacote", editable = true }

Essa separação explícita entre a declaração da versão mínima em project.dependencies e a origem do código em tool.uv.sources mantém o arquivo compatível com especificações abertas sem prender o repositório a uma ferramenta proprietária.

Tabela comparativa e veredito de recursos

Para visualizar de forma clara o que muda no dia a dia do desenvolvimento após substituir o Poetry pelo uv, analise a tabela com os principais aspectos operacionais de cada ferramenta:

Critério Poetry uv (Astral)
Linguagem do Motor Python puro Rust
Padrão de Metadados Seção proprietária [tool.poetry] Padrão oficial PEP 621 ([project])
Arquivo de Trava poetry.lock uv.lock
Tempo Médio de Lock De 10s a 120s em projetos médios Menos de 1s em quase todos os cenários
Gerenciamento de Python Requer pyenv ou Python pré-instalado Instala e gerencia versões do Python nativamente (uv python install)
Execução de Ferramentas Isoladas Exige instalação prévia via plugins Executa ferramentas sob demanda igual ao npx (uvx ruff check)

O veredito técnico é claro: o Poetry desempenhou um papel histórico fundamental ao trazer o conceito de arquivos de trava e gerenciamento moderno de projetos para o Python. No entanto, a arquitetura do uv oferece uma superioridade técnica difícil de ignorar. A combinação de performance extrema em Rust com a adesão rigorosa aos padrões da PyPA torna o uv a escolha ideal para novos projetos e uma atualização de alta prioridade para bases de código existentes.

Conclusão

Decidir migrar de poetry para uv é um dos investimentos de infraestrutura de código com maior retorno imediato para times Python. Ao adotar os padrões do PEP 621 e utilizar a velocidade do motor em Rust, você elimina atrasos frequentes em pipelines de integração contínua, simplifica o gerenciamento de versões da linguagem e reduz a complexidade da sua ferramenta de empacotamento. O fluxo de conversão é seguro, totalmente reversível e garante a paridade exata de dependências necessária para manter a estabilidade da sua aplicação em produção.

Gostou? Compartilhe

Mais em Python & Código