- uv tem como pontos fortes a alta velocidade, o gerenciamento de versões do Python e a integração em um único binário, mas a UX de gerenciamento de pacotes na fase de manutenção ainda é bastante áspera
- Pacotes desatualizados podem ser verificados com
uv pip list --outdated, mas como isso fica sob um namespace compatível com pip, e não como um comando de topo, sua descoberta é ruim
uv add pydantic adiciona por padrão uma restrição sem limite superior, como pydantic>=2.13.4, permitindo até aumentos de versão major
- A atualização completa é feita com
uv lock --upgrade, e para vários pacotes específicos é preciso repetir --upgrade-package, o que deixa o comando verboso
- A configuração
add-bounds = "major" pode fornecer restrições padrão mais seguras, mas é um recurso em preview, e aplicações precisam de uma UX de atualização mais intuitiva
Pontos fortes do uv e incômodos na fase de manutenção
- O uv da Astral se destaca pela alta velocidade, pelo gerenciamento de versões do Python e por substituir várias ferramentas com um único binário
- O fluxo para iniciar um novo projeto Python e adicionar a primeira dependência é fácil, mas quando o projeto entra na fase de manutenção, a UX para verificar pacotes desatualizados e fazer upgrades regulares parece mais áspera do que em pnpm ou Poetry
- Os principais incômodos estão na descoberta do comando para verificar pacotes desatualizados, na ausência de limite superior nas restrições de versão padrão e na verbosidade dos comandos de upgrade
Verificação de pacotes desatualizados
- Em projetos JavaScript,
pnpm outdated permite ver de forma concisa os pacotes desatualizados, a versão atual, a versão mais recente e a versão permitida pelas restrições
- No uv, não existe um comando de topo
uv outdated, e no início a alternativa usada era o seguinte comando
$ uv tree --outdated --depth 1
uv tree --outdated --depth 1 não filtra apenas os itens desatualizados: ele imprime a árvore inteira de dependências de topo e depois adiciona uma pequena anotação ao lado dos itens que podem ser atualizados
- Mesmo que haja 50 dependências e apenas 2 pacotes desatualizados, ainda é preciso percorrer uma lista de 50 linhas
poetry show --outdated também tem um nome de comando menos intuitivo, mas sua saída na prática mostra apenas os pacotes desatualizados
O risco das restrições de versão padrão
-
O padrão de pnpm e Poetry
pnpm add grava no package.json um requisito com caret, como ^1.23.4
^1.23.4 permite versões 1.x.x, mas não atualiza para 2.0.0
- O Poetry também usa por padrão um formato como
>=1.23.4,<2.0.0; a notação é menos fácil de ler, mas o efeito é o mesmo
- Nessas duas ferramentas, partindo do pressuposto de que o pacote segue SemVer, executar
pnpm update ou poetry update reduz a chance de quebrar o build por causa de mudanças importantes de API
-
O padrão do uv
uv add pydantic adiciona ao pyproject.toml uma restrição sem limite superior, assim:
dependencies = [
"pydantic>=2.13.4",
]
- Com essa restrição, as versões 2, 3 ou 100 do pydantic são todas permitidas
- Ao executar uma atualização em massa, você pode receber não só correções de bugs, mas também mudanças incompatíveis publicadas por todos os mantenedores ao longo do grafo de dependências
- Especialmente na manutenção de aplicações, isso pode virar um risco de estabilidade
A UX dos comandos de upgrade
- Em pnpm e Poetry, a atualização completa é simples assim:
$ pnpm update
$ poetry update
- No uv, usa-se o seguinte comando para o upgrade completo:
$ uv lock --upgrade
uv lock --upgrade não é uv update nem uv upgrade; ele funciona como uma opção do comando lock, o que o torna menos intuitivo como comando de gerenciamento de pacotes voltado a humanos
- Quando combinado com restrições sem limite superior,
uv lock --upgrade passa a significar elevar todos os pacotes do lockfile para as versões absolutamente mais recentes
- Essa atualização pode incluir até dependências profundamente aninhadas que você nem conhece diretamente
- Para atualizar apenas pacotes específicos, no pnpm basta listar os nomes dos pacotes assim:
$ pnpm update pydantic httpx uvicorn
- No uv, é preciso repetir a flag
--upgrade-package para cada pacote
$ uv lock --upgrade-package pydantic --upgrade-package httpx --upgrade-package uvicorn
- Ao subir vários pacotes de uma vez, a repetição de flags vira um grande incômodo
A flag --bounds e a configuração
- O uv adicionou recentemente a opção
--bounds para uv add
$ uv add pydantic --bounds major
- Esse comando gera a restrição mais segura
pydantic>=2.13.4,<3.0.0
--bounds major é atualmente um recurso em preview e uma funcionalidade opt-in que precisa ser informada manualmente em cada comando
- Depois descobriu-se que é possível definir um padrão uma vez no
pyproject.toml
[tool.uv]
add-bounds = "major"
- Com essa configuração, você passa a obter um padrão mais razoável nos próximos
uv add, sem precisar digitar --bounds major toda vez
- Para aplicações, seria melhor que esse comportamento fosse o padrão, mas na prática a usabilidade não é tão ruim quanto parecia na descrição inicial
A diferença entre aplicações e bibliotecas
- O conselho padrão no empacotamento Python é que bibliotecas publicadas no PyPI não fixem limite superior, e esse conselho faz sentido
- Se toda biblioteca fixar limites superiores, a árvore de dependências dos consumidores abaixo pode deixar de ser resolvível
- Já as aplicações são o nó terminal do grafo de dependências, e outros usuários não resolvem suas dependências com base nessas restrições
- Em aplicações, não há custo em usar limite superior, e isso oferece proteção contra aumentos inesperados de versão major
- O foco aqui é a manutenção de aplicações como sites, serviços e ferramentas internas; para distribuição de bibliotecas, o padrão sem limite superior pode ser razoável
O que foi corrigido e o que ainda sobra
- Com
uv pip list --outdated, é possível ver apenas os pacotes desatualizados
$ uv pip list --outdated
- Isso enfraquece a crítica sobre a saída barulhenta de
uv tree --outdated --depth 1
- O problema que permanece é que essa funcionalidade não está em um comando de topo, e sim sob o namespace compatível com
pip, o que prejudica a descoberta
- Com
add-bounds = "major", é possível definir limites padrão, então não é correto dizer que só existe a escolha entre editar manualmente o limite superior toda vez ou aceitar o risco
- Ainda assim, o recurso continua em preview e, no gerenciamento de pacotes para aplicações, ainda faltam restrições padrão mais seguras e comandos de atualização mais intuitivos
Melhorias desejadas
- É necessário um comando dedicado
uv outdated que mostre claramente apenas os pacotes desatualizados
- É necessário um comando
update mais ergonômico, que permita atualizar vários pacotes sem repetir flags
- As restrições de versão padrão deveriam refletir melhor a expectativa de estabilidade do versionamento semântico (SemVer)
- No estado atual, permanece o peso de revisar com desconfiança cada linha modificada no lockfile
1 comentários
Comentários do Hacker News
O intervalo de versão padrão de
uv addpode ser definido como uma configuração permanente, então não é preciso passá-lo toda vezReferência: https://docs.astral.sh/uv/reference/settings/#add-bounds
O motivo de não colocar um limite superior por padrão é que isso cria muitos conflitos desnecessários no ecossistema, e na época em que eu usava Poetry eu até reuni material relacionado aqui: https://github.com/zanieb/poetry-relax#references
Ao usar dependências em um projeto web, eu quero um limite superior para evitar mudanças incompatíveis, assumindo que a dependência segue SemVer
Eles desencorajavam limites superiores defensivos e, em vez disso, compilavam continuamente uma grande parte ativa do ecossistema a cada release para encontrar problemas reais de compatibilidade, enviavam notificações automáticas aos responsáveis e forneciam um cronograma claro para permanecer na próxima release "LTS"
Hoje parece que só o resolvedor do Cabal já é bem estável, mas uma ampla bateria de builds noturnos, com falhas e bloqueios visíveis, provavelmente ajudou a manter o ecossistema solucionável
add-bounds; fixar dependências com precisão é importante, mas isso é útil principalmente em projetos que desenvolvedores menos experientes podem deixar passar, especialmente em produtos finais, não bibliotecas--native-tlspermanentementePor causa da configuração de Zscaler no trabalho, o UV sempre falha sem essa flag
Também queria saber se há planos para suportar algo como especificar uma versão compatível do Python para certa arquitetura. Um pacote mantido pela empresa precisa usar Python 32-bit, então eu sempre tenho que passar
--python /path/to/32bitexclude-newerdopyproject.tomlQuando eu executo
uv run,exclude-neweré removido dopyproject.tomlEu até poderia rodar
uv run —-frozenouuv run --exclude-newertoda vez, mas isso não parece o fluxo certo, então queria saber se existe alguma forma idiomática que eu esteja deixando passarO
uvfoi projetado intencionalmente para não ter limite superior, porque o Python precisa de um único resultado de resoluçãoO npm pode instalar resultados diferentes de resolução em partes diferentes da árvore, mas no Python isso não é uma opção. No Rye tivemos que tomar a mesma decisão, e não há uma solução melhor para isso
Colocar limites superiores pode, na prática, gerar árvores que deixam de ser solucionáveis. Em parte do ecossistema de pacotes Python, no passado, chegaram até a distribuir overrides para pacotes antigos que haviam sido publicados assumindo limites superiores incorretos
Hoje você ainda não tem como saber se seu pacote será compatível ou não com pacotes que nem foram lançados ainda
uvme dê um erro ao atualizar dizendo que os pacotes não são compatíveis, e então eu decido se quero fazer override ou nãoÉ melhor do que encontrar erros de incompatibilidade de versão em tempo de execução que são difíceis de rastrear
pyproject.tomlO problema real é que
uv lock —-upgradefaz um upgrade em massa de tudo que não tem limite superiorSe existisse uma forma de atualizar pacotes sem subir a versão major, esse comando seria muito mais seguro
Comparado ao período anterior ao uv, melhorou absurdamente, mas parece difícil ficar realmente bom sem algum grau de mudanças incompatíveis em todo o ecossistema. Pensando na transição do Python 2 para o 3, imagino que também não haja muita disposição para isso tão cedo
A flag
—-boundajuda, mas é mais uma coisa para digitar e lembrarSe o uv consegue saber que aquele projeto não é uma biblioteca, talvez pudesse colocar limites superiores por padrão nesse caso
=e atualizar só manualmente, sem confiar que updates não major não vão quebrar nadaTenho um app em produção com 257 dependências Python, e mais da metade delas são dependências diretas
Não coloco limites superiores no
pyproject.tomle, a cada duas semanas, executouv lock --upgradevia GitHub ActionsComo a cobertura de testes é boa, se algo quebrar os testes falham, e também temos um fluxo de revisão com ajuda de IA. Quando o PR de upgrade é criado, um workflow de IA usa um script Python para listar atualizações de versão major e minor, encontrar os changelogs, criar links e resumos, e analisar o risco de cada pacote com base em como ele é usado no codebase
Em geral, quase não dói: não preciso atualizar pacote por pacote, nem revisar pacotes antigos, nem lidar com dependências abandonadas. É muito raro precisar de correção do autor da dependência, em um caso que não dê para resolver no código, algo como uma vez por ano. Nos últimos 3 meses houve 18 aumentos de versão major, e só um deles exigiu mudança no código
Eu queria fazer isso também no frontend, mas não tenho testes suficientes para rodar isso com segurança. Testes de backend são mais fáceis de escrever e mais importantes, então acho que todo codebase deveria ter isso. Se você tem testes, pode simplesmente automatizar o upgrade de tudo
Pelo menos são excelentes em converter instruções em linguagem natural em testes corretos
Faz tempo que eu não escrevo testes manualmente, e antes era algo de que eu sempre reclamava, mas agora não mais
O UV fez muita coisa pelo Python, mas hoje eu apanhei bastante com ele
Eu estava tentando gerenciar centralmente scripts espalhados por vários repositórios, cuja implementação foi divergindo com o tempo
A ideia era usar
uv run --with $package main --help, com o comportamento automático de 1) instalar e executar se não existir, 2) não instalar se já estiver atualizado e 3) atualizar se não estiver na versão mais novaSó que as três coisas foram mais difíceis do que eu esperava. Na prática,
uv runreinstalava tudo toda vez, e o ambiente virtual com a instalação levava 6 segundosuvxeuv tooltambém não ajudaram muito, porque surgiu outro problema: os usuários deixavam de receber upgradesNo fim, eu fiz o script consultar o CodeArtifact com GET paginado para ver se havia uma versão mais nova que não fosse de desenvolvimento; se houvesse, ele atualizava e rodava de novo. Funciona, e 200 ms de atraso é melhor que 6 segundos, mas não era a experiência que eu queria
uv run --with $package main --helpdeveria fazer exatamente isso com pouquíssimo overhead, então fiquei um pouco confusoEle não reinstala tudo toda vez; os ambientes de
--withficam mantidos em cache. Mesmo que o ambiente não estivesse em cache, as dependências ficam, e instalar a partir do cache costuma ser muito rápido. Certamente deveria ficar abaixo de 200 msSe você abrir um exemplo reproduzível com mais detalhes, dá para investigar
uv tool installeuv tool upgradeparecem mais adequadosDito isso, esse tipo de incômodo menor provavelmente é relativamente fácil de resolver, então seria bom abrir uma issue
uv run mainAí ele instala, faz cache e executa automaticamente as dependências necessárias: https://docs.astral.sh/uv/guides/scripts/
uv tool upgradeNão sei se é exatamente o mesmo caso de uso, mas funcionou muito bem para manter sincronizado um ecossistema polyrepo de microsserviços
Achei bem surpreendente a recomendação de
"uv tree --outdated --depth 1"para ver a lista de dependências desatualizadasPessoalmente, desde que isso foi introduzido eu venho usando
"uv pip list --outdated"Ainda assim, concordo que um comando tão importante mereceria um subcomando de topo separado
É verdade que
"uv pip list --outdated"dá uma saída bem melhor, obrigadoMas o fato de existirem dois jeitos de ver pacotes desatualizados, com saídas tão diferentes, também reforça a sensação de que a UX é bagunçada
"uv tree -od1"provavelmente também funcionaMas uma crítica parecida feita a gerenciadores de pacotes como o pacman era justamente que, para comandos usados com frequência, deveria haver comandos mais legíveis para humanos, como no apt
Pelo título, chamar de “bagunça” parece exagerado para exemplos que no fim são só alguns argumentos a mais
Um título melhor talvez fosse algo como melhorias de qualidade de vida que eu gostaria de ver no UV
O feedback em si é útil e eu concordo com a maior parte dele, mas esse tipo de formulação diminui o valor do feedback e provoca reações defensivas
A interface de linha de comando do uv também me parece meio incômoda, mas eu entendo por que ela foi escrita assim
O
uvé ótimo, mas hoje o maior problema do empacotamento Python ainda é lidar direito com pacotes científicos e de machine learningSe você quiser instalar PyTorch, primeiro precisa saber qual versão quer, se é CUDA etc. Se for CUDA, ainda existem 6 variantes por versão de CUDA, e os wheels são grandes demais para colocar no PyPI, então você precisa baixá-los manualmente
O Conda resolve isso só parcialmente. O Spack é extremamente configurável e permite montar as dependências C/C++/Fortran e o toolchain de compiladores necessários para extrair o máximo desempenho, mas ele não se integra bem com uv e afins. Por isso é difícil levar até produção um projeto experimental de machine learning criado por pesquisadores
No fim você acaba voltando para a situação mencionada antes
Há bastante feedback útil, mas misturado com formulação estilo clickbait
Sobre
pnpm outdated, isso não apareceu muito até agora, mas parece um pedido razoável. Talvez venha da diferença cultural entre Python e JavaScript. Em Python, quase nunca me importei se uma dependência estava desatualizada, desde que não estivesse vulnerável ou quebrada; já no ecossistema JavaScript, parece bem comum aproveitar oportunidades para atualizar. Não estou dizendo que isso é ruim, só mostra como a intuição sobre o que expor numa interface de linha de comando pode variar muito entre grandes comunidades de programaçãoComo o Armin disse, o comportamento de limites superiores do uv é intencional e funcionalmente necessário pela forma como a resolução do Python funciona. É um compromisso que o Python escolheu em comparação com outras linguagens, mas eu o considero um bom compromisso, no sentido de que existe apenas uma versão de cada dependência na árvore de dependências e todas as exigências interligadas são resolvidas em torno disso
O motivo de
uv lock --upgradeser assim é que ele está atualizando o arquivo de lock, não as exigências do usuário. Jápnpm update, ao que parece, atualiza as exigências do usuário empackage.json. Pode ser confuso, mas colocá-lo sobuv locké mais preciso. Caso contrário, apareceriam usuários confusos porqueuv upgradenão faria o tipo de upgrade que cada um imagina. Ainda assim, existe espaço para expor isso de forma mais limpa, e claramente já houve demanda de usuários por um subcomando do uv que atualize diretamente as exigênciashttps://news.ycombinator.com/item?id=48230048
Faz sentido que o comando
uv lockcuide apenas do arquivo de lock, mas os usuários têm uma necessidade real de atualizar dependências diretas e transitivas. Dependências transitivas podem ser atualizadas comuv lock --upgrade-package, mas isso é um pouco verboso. Também funciona para dependências diretas, mas não mexe nopyproject.toml, e esse arquivo é muito mais visível para o desenvolvedorSe as versões de pacote em
uv.lockavançam além do que está empyproject.toml, então opyproject.tomlfica menos confiável como guia para entender a superfície de dependências. Seria bom ter um comando amigável comouv upgradeA maior armadilha de UX que vi no uv até agora é
uv pip. Muitos projetos usam uv corretamente para desenvolvimento compyproject.tomleuv.lock, mas no Dockerfile de deploy ou em ferramentas de CI acabam usandouv pip install -r pyproject.toml, contornando ouv.lockO fato de agentes de código recomendarem padrões ruins com
uv pipporque foram treinados com muitopipnos dados também é um problema, mas o uv também deveria fornecer mecanismos de proteção ao usuárioAcho o uv uma ferramenta excelente e que deveria ser mais adotada: https://aleyan.com/blog/2026-why-arent-we-uv-yet
poetry updatetambém atualiza o arquivo de lock. Acho a forma como a CLI do uv é organizada bem incômoda para trabalhar. Ela parece projetada para exatidão e para máquinas, não para ser amigável para humanosuv pip list --outdateduv upgradeestá no roadmapAinda não foi feito porque é difícil transformá-lo em uma experiência realmente boa, há muito mais nuances do que as pessoas imaginam, a equipe é pequena e há muitas prioridades
pnpm outdatedé ver o que seria atualizado ao executar"uv sync --update"ou"uv lock --update"Mas talvez fosse ainda melhor se esses comandos tivessem um prompt de confirmação
O Pixi usa uv no backend, e eu gostei da UI porque é fácil adicionar aliases de tarefa para listar pacotes desatualizados de forma agradável
Em especial, o Pixi-diff-to-markdown facilita revisar atualizações automáticas de pacotes em CI
Se você quiser ver, entre os pacotes desatualizados, quais seriam atualizados, pode criar no projeto um alias de tarefa como este
pixi task add outdated "pixi update --dry-run --json | pixi exec pixi-diff-to-markdown"E então rodar
pixi run outdatedno projetoA saída é uma tabela Markdown legível com os pacotes que seriam atualizados, a versão atual e a nova versão que seria instalada pelo comando
pixi update. Claro, isso pode variar conforme gosto e contextoRecentemente apareceu um script
envno meuPATH, atrapalhando o uso do comando UNIX comumenvDescobri que isso é criado pelo instalador do uv ao executar o comando abaixo
curl -LsSf [https://astral.sh/uv/install.sh](<https://astral.sh/uv/install.sh>) | shEle instala em
$HOME/.cargo/bin/e cria um script de shell em$HOME/.cargo/envque adiciona$HOME/.cargo/bin/aoPATHse ele ainda não estiver presenteÉ difícil entender que tipo de programador escreve código que sobrescreve comandos UNIX básicos dessa forma