2 pontos por GN⁺ 5 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • 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

 
GN⁺ 5 시간 전
Comentários do Hacker News
  • O intervalo de versão padrão de uv add pode ser definido como uma configuração permanente, então não é preciso passá-lo toda vez
    Referê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

    • Entendo que remover o limite superior de versão é importante ao distribuir bibliotecas, mas o texto foi escrito do ponto de vista de quem está criando um site, não uma biblioteca
      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
    • A comunidade Haskell também passou anos lidando com esse problema, e por um tempo a abordagem mais bem-sucedida foi o Stackage
      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
    • Só agora descobri a configuração 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
    • Queria saber se existe uma forma de definir a flag --native-tls permanentemente
      Por 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/32bit
    • Talvez seja uma pergunta meio crua, mas queria saber se existe uma forma de fazer o uv respeitar exclude-newer do pyproject.toml
      Quando eu executo uv run, exclude-newer é removido do pyproject.toml
      Eu até poderia rodar uv run —-frozen ou uv run --exclude-newer toda vez, mas isso não parece o fluxo certo, então queria saber se existe alguma forma idiomática que eu esteja deixando passar
  • O uv foi projetado intencionalmente para não ter limite superior, porque o Python precisa de um único resultado de resolução
    O 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

    • Pessoalmente, prefiro que o uv me 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
    • O problema real não é a ausência de limites superiores no pyproject.toml
      O problema real é que uv lock —-upgrade faz um upgrade em massa de tudo que não tem limite superior
      Se existisse uma forma de atualizar pacotes sem subir a versão major, esse comando seria muito mais seguro
    • O uv melhorou muito a situação, mas parece que ainda há bastante coisa que, no fundo, ferramenta nenhuma consegue resolver sozinha
      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
    • Isso faz sentido para autores de bibliotecas, mas quando você está fazendo um site e depende de vários pacotes, quer limites superiores para ter segurança nas atualizações
      A flag —-bound ajuda, mas é mais uma coisa para digitar e lembrar
      Se o uv consegue saber que aquele projeto não é uma biblioteca, talvez pudesse colocar limites superiores por padrão nesse caso
    • Na verdade, tanto com uv quanto com npm, a forma certa de usar é trocar tudo para = e atualizar só manualmente, sem confiar que updates não major não vão quebrar nada
  • Tenho 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.toml e, a cada duas semanas, executo uv lock --upgrade via GitHub Actions
    Como 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

    • Escrever testes também é algo em que agentes de IA costumam ser bons
      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 nova
    Só que as três coisas foram mais difíceis do que eu esperava. Na prática, uv run reinstalava tudo toda vez, e o ambiente virtual com a instalação levava 6 segundos
    uvx e uv tool também não ajudaram muito, porque surgiu outro problema: os usuários deixavam de receber upgrades
    No 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 --help deveria fazer exatamente isso com pouquíssimo overhead, então fiquei um pouco confuso
      Ele não reinstala tudo toda vez; os ambientes de --with ficam 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 ms
      Se você abrir um exemplo reproduzível com mais detalhes, dá para investigar
    • Para esse caso de uso, uv tool install e uv tool upgrade parecem mais adequados
      Dito isso, esse tipo de incômodo menor provavelmente é relativamente fácil de resolver, então seria bom abrir uma issue
    • Você também pode definir as dependências necessárias em um bloco de documentação no topo do arquivo e depois só rodar uv run main
      Aí ele instala, faz cache e executa automaticamente as dependências necessárias: https://docs.astral.sh/uv/guides/scripts/
    • Acho que o usuário poderia simplesmente rodar uv tool upgrade
    • Talvez valha a pena olhar o https://copier.readthedocs.io/en/stable/
      Nã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 desatualizadas
    Pessoalmente, 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

    • Do ponto de vista do autor, era menos uma recomendação e mais o único jeito que ele conhecia
      É verdade que "uv pip list --outdated" dá uma saída bem melhor, obrigado
      Mas 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 funciona
      Mas 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

    • Essa formulação, junto com frases como “quem foi que projetou esta interface de linha de comando”, parece feita para chamar atenção e gerar clique
      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 learning
    Se 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

    • Antigamente eu contornava isso com Anaconda, mas ele vinha com um monte de coisa extra e o ambiente de desenvolvimento ficava totalmente diferente do ambiente de produção, então também não era muito bom
      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ção
    Como 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 --upgrade ser 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 em package.json. Pode ser confuso, mas colocá-lo sob uv lock é mais preciso. Caso contrário, apareceriam usuários confusos porque uv upgrade nã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ências
    https://news.ycombinator.com/item?id=48230048

    • Concordo sobre pacotes desatualizados e limites superiores, mas se os usuários reclamam que a interface é difícil, então claramente existe algum problema
      Faz sentido que o comando uv lock cuide 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 com uv lock --upgrade-package, mas isso é um pouco verboso. Também funciona para dependências diretas, mas não mexe no pyproject.toml, e esse arquivo é muito mais visível para o desenvolvedor
      Se as versões de pacote em uv.lock avançam além do que está em pyproject.toml, então o pyproject.toml fica menos confiável como guia para entender a superfície de dependências. Seria bom ter um comando amigável como uv upgrade
      A maior armadilha de UX que vi no uv até agora é uv pip. Muitos projetos usam uv corretamente para desenvolvimento com pyproject.toml e uv.lock, mas no Dockerfile de deploy ou em ferramentas de CI acabam usando uv pip install -r pyproject.toml, contornando o uv.lock
      O fato de agentes de código recomendarem padrões ruins com uv pip porque foram treinados com muito pip nos dados também é um problema, mas o uv também deveria fornecer mecanismos de proteção ao usuário
      Acho o uv uma ferramenta excelente e que deveria ser mais adotada: https://aleyan.com/blog/2026-why-arent-we-uv-yet
    • Se isso soou “clickbait”, peço desculpas, mas na verdade é só franqueza e objetividade holandesas
      poetry update també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 humanos
    • Para quem veio de pip para uv, quando precisa dessa informação acaba voltando para uv pip list --outdated
    • uv upgrade está no roadmap
      Ainda 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
    • Um dos usos de algo no estilo 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 outdated no projeto
    A 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 contexto

  • Recentemente apareceu um script env no meu PATH, atrapalhando o uso do comando UNIX comum env
    Descobri 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>;) | sh
    Ele instala em $HOME/.cargo/bin/ e cria um script de shell em $HOME/.cargo/env que adiciona $HOME/.cargo/bin/ ao PATH se ele ainda não estiver presente
    É difícil entender que tipo de programador escreve código que sobrescreve comandos UNIX básicos dessa forma