5 pontos por GN⁺ 2025-03-31 | 1 comentários | Compartilhar no WhatsApp
  • Mesmo pequenos scripts de teste de API exigem ajustar o ambiente de execução por causa das dependências, mas, usando um shebang de execução do uv, é possível deixá-los em um formato executável diretamente, sem etapas de instalação
  • O exemplo jam_users.py entra em um REPL após excluir e criar usuários com httpx, IPython e loguru para testar o endpoint /users de uma API em Go
  • No método tradicional, é preciso instalar pacotes globalmente no Python do sistema ou preparar manualmente um ambiente virtual, o que torna o compartilhamento e a reexecução de scripts trabalhosos
  • Ao declarar dependencies = ["ipython", "httpx", "loguru"] no cabeçalho # /// script e executar uv run jam_users.py, o uv cuida do ambiente isolado e da instalação das dependências
  • Ao adicionar o shebang #!/usr/bin/env -S uv run --script e permissão de execução, em sistemas Unix é possível executar como ./jam_users.py tendo apenas o uv instalado

Declarando dependências dentro do script

  • O exemplo jam_users.py prepara dados de usuários de teste para a API local http://localhost:4000/v1/users
    • Envia requisições à API com httpx
    • Entra no REPL do IPython para inspecionar as respostas e continuar os testes
    • Registra logs de exclusão e criação com loguru
  • O fluxo do script básico é inicializar a lista de usuários e depois repopulá-la com dados de teste
    • Busca a lista de usuários existente com GET /v1/users
    • Chama DELETE /v1/users/{id} para cada usuário
    • Envia a lista de usuários preparada em JSON para POST /v1/users
    • Em seguida, abre um REPL com IPython.embed()
  • Para executar com python jam_users.py, httpx, IPython e loguru já precisam estar instalados no ambiente de execução
  • Também é possível instalar globalmente no Python do sistema ou criar um ambiente virtual separado, mas ambos exigem preparação antes da execução
    • A instalação global pode bagunçar o Python do sistema com pacotes
    • A abordagem com ambiente virtual exige gerenciar manualmente criação, ativação, instalação e execução
    • Em ambos os casos, é preciso ter um Python do sistema compatível com os pacotes necessários

Criando um script executável diretamente com uv

  • Ao inserir a tag # /// script do uv no topo do script, é possível declarar as dependências dentro do próprio arquivo
# /// script

# dependencies = ["ipython", "httpx", "loguru"]

# ///
  • Com esse cabeçalho, é possível executar com uv run jam_users.py
    • O uv cria um ambiente virtual isolado para o script
    • Baixa e instala as dependências necessárias
    • Executa o script no contexto desse ambiente virtual
  • Um shebang comum de Python, como #!/usr/bin/env python, não aproveita o cabeçalho de script do uv, pois o Python ignora o comentário # /// script
  • Ao colocar a chamada ao uv diretamente no shebang, é possível tratar o script como um arquivo executável
#!/usr/bin/env -S uv run --script

# /// script

# dependencies = ["ipython", "httpx", "loguru"]

# ///
  • A flag -S do env faz com que a string seguinte seja dividida em argumentos separados e passada ao env
  • Depois de conceder permissão de execução com chmod +x jam_users.py, é possível executar diretamente assim
./jam_users.py
  • Com essa abordagem, se o uv estiver instalado em um sistema Unix, o script pode ser executado sem instalação separada de dependências nem gerenciamento de ambiente virtual
  • Ao entregar scripts Python complexos a outros usuários, isso reduz o peso de ter que explicar longos procedimentos de preparação do sistema antes da execução

1 comentários

 
GN⁺ 2025-03-31
Opiniões no Hacker News
  • Não é algo específico do UV, mas, de modo geral, me incomoda a forma de controlar a execução do código por meio de comentários
    Acho ok usar comentários para diretivas de linters ou notas de desenvolvedores, mas, se forem dados relacionados a configuração ou execução, algo como UV_ENV = { "dependencies": { "requests": "2.32.3", "pandas": "2.2.3" } } me parece muito melhor
    Essa abordagem é sintaxe Python válida, usa estruturas de dados padrão em vez de parsing arbitrário de comentários, o que facilita geração e validação, e, acima de tudo, preserva o princípio de que mesmo removendo todos os comentários, o código deve executar da mesma forma

    • Concordo, mas gostaria de ir um passo além
      A abordagem sugerida também é uma constante mágica que não faz nada em runtime e só é analisada por análise estática; em vez de ser lida pelo uv, outra ferramenta poderia vê-la como código não usado e removê-la
      Melhor seria fazer uma chamada direta ao uv dizendo o que fazer, como import uv seguido de uv.exec(dependencies=["clown"], python=">=3.10")
      A primeira execução aconteceria em qualquer runtime Python capaz de encontrar esse pacote uv hipotético; o pacote uv prepararia o ambiente virtual e o runtime Python e então faria re-exec(3) com algum sinalizador, como uma variável de ambiente
      No segundo runtime, uv.exec detectaria o sinalizador e não faria nada
    • Isso não foi inventado pelo uv; ele usa o padrão PEP 723, como outras ferramentas
      https://peps.python.org/pep-0723/
    • É um ponto válido, mas acho que fazer parsing e avaliação de código imperativo é muito mais difícil e menos flexível do que manter isso como dados declarativos, alinhados ao princípio do menor privilégio
      Além disso, a própria linha shebang também funciona, na prática, como um comentário
      Ela está tão profundamente arraigada há 45 anos que as pessoas simplesmente não se dão muito conta de que aquilo é um comentário do shell
    • A condição de que “mesmo removendo os comentários, o código deve executar da mesma forma” continua válida
      Se as mesmas dependências estiverem instaladas, o código executa da mesma forma
      Em vez de mudar o significado do código em si, isso muda o ambiente em que o código é executado; nesse sentido, não é diferente do comentário #!/bin/bash no topo de um script de shell
    • Concordo totalmente e espero que algo nesse formato acabe sendo padronizado
      Dito isso, é bem provável que o uv não queira executar o código para descobrir as dependências, então teria de ser um subconjunto muito limitado da sintaxe Python
      O próprio fato de algo assim ser necessário expõe uma fraqueza da linguagem
      A instrução import em si deveria ser capaz de transmitir todas as informações sobre dependências
  • Nos últimos meses, esse assunto apareceu com muita frequência no HN, e exemplos recentes são estes
    https://news.ycombinator.com/item?id=43500124
    https://news.ycombinator.com/item?id=42463975
    Eu gosto do uv, mas tenho dificuldade em concordar com o termo self-contained por dois motivos
    Primeiro, para executar o script, o uv já precisa estar instalado
    Dá para fazer um script de shell verificar se o uv está instalado e, se não estiver, instalá-lo via curlpipe, mas isso aumenta bastante o boilerplate, e o próprio método curlpipe não é bom
    Segundo, criar automaticamente um ambiente virtual em algum lugar no diretório home não é realmente self-contained
    Mesmo que você execute uma única vez e apague o script, esse ambiente virtual fica para trás ocupando espaço, e não encontrei na documentação do uv nenhuma garantia de que esses ambientes virtuais temporários sejam limpos automaticamente

    • A reclamação de que o uv é necessário e, se ele não existir, é preciso instalá-lo manualmente ou usar curl | sh é válida
      Mas, à medida que gerenciadores de pacotes começarem a incluir o uv em seus repositórios, isso deve se tornar cada vez menos problemático
      Por exemplo, o uv já é fornecido no Alpine Linux e no Homebrew: https://repology.org/project/uv/versions
      Além disso, metadados inline de scripts são um padrão do Python
      Se o sistema não tiver uv e ele não estiver empacotado, mas houver uma versão do Python compatível com o script, dá para executá-lo com pipx: https://pipx.pypa.io/stable/examples/#pipx-run-examples
      O pipx é empacotado de forma muito mais ampla: https://repology.org/project/pipx/versions
    • Para tentar resolver esse problema, forcei uma combinação de shebang docker+uv, e ela funciona até certo ponto
      Em uma máquina de desenvolvimento qualquer, Docker pode ser mais comum do que uv, e o texto original também dizia que era um projeto de empresa, então parecia plausível
      No estado atual, porém, não há cache nenhum, então downloads acontecem a cada execução, o que é meio estranho; talvez dê para corrigir isso com volumes
      É algo assim: https://hugojosefson.github.io/docker-shebang/#python
    • Normalmente, para executar um programa em um computador, é preciso instalar alguma coisa antes, então instalar o uv em si não parece tão ruim
      Ainda assim, se, na hora da execução, ele baixa da internet algo de identidade desconhecida, fica difícil chamar isso de self-contained; algo realmente totalmente self-contained está mais próximo de um AppImage
    • Concordo 100%
      Com algo como py2exe, dá para criar um “script Python” self-contained
      Isso causa muitos problemas para o desenvolvedor, mas minimiza os problemas para o usuário
    • Só um pequeno complemento: graças à deduplicação de pacotes do uv, enquanto não houver dependências únicas, o virtualenv não ocupa espaço
  • Com Nix também se usa a mesma abordagem, e a linha shebang fica assim
    #! nix-shell -i python3 -p "python312.withPackages (pkgs: [ pkgs.boto3 pkgs.click ])"
    Assim, a única coisa necessária no sistema é o Nix; nem é preciso ter Python instalado

    • É verdade, mas ainda há muitos pacotes PyPI que não foram empacotados no nixpkgs, então não é uma abordagem tão universal quanto o uv
    • Como no texto original, o uv instala o Python na hora quando necessário
    • A mesma técnica pode ser aplicada a qualquer linguagem
      O exemplo mais óbvio é um bash com todas as dependências especificadas, e eu já cheguei a criar scripts Rust rápidos em arquivo único com shebang do Nix
      https://nixos.wiki/wiki/Nix-shell_shebang
    • A exigência de ter o Nix instalado é um requisito muito mais forte do que exigir que o uv esteja disponível
    • Tenho curiosidade sobre como fazer a mesma coisa com o comando baseado em flakes nix shell, em vez de nix-shell
  • Como outros comentários disseram, a alegação de “self-contained” depende de uv estar instalado
    Se você quer um script Python realmente self-contained, vale olhar o compilador Nuitka
    Uso em produção em um serviço gRPC sem problemas, e basta executar nuitka --onefile run.py
    Por ser um compilador, o binário resultante também pode ser mais rápido do que o programa Python original empacotado com PyInstaller
    Na página do autor no GitHub há a frase: “a missão da minha vida é tentar, até morrer de velhice, criar o melhor Python Compiler possível”
    https://nuitka.net/
    https://github.com/kayhayen

  • Gosto muito desse padrão, mas infelizmente não consegui fazê-lo funcionar direito com LSP
    Uso pyright no Helix, e nem executando o editor com uv run hx script.py funciona
    Dá para fazer algo como uv run --with whatever-it-is-i-need hx script.py, mas a duplicação vai aumentando

    • Estou usando um script feioso que fiz, chamado uve
      $ cat ~/.local/bin/uve
      #!/bin/bash
      temp=$(mktemp)
      uv export --script $1 --no-hashes > $temp
      uv run --with-requirements $temp vim $1
      unlink $temp
      Espero que os editores passem a oferecer suporte a uv python find --script em breve
  • Parece bastante útil
    Fico me perguntando se o uv é uma opção mais segura para distribuição de longo prazo de projetos baseados em Python
    Lembro do rug pull de cerca de 5 anos atrás, quando usamos Anaconda para gerenciamento de dependências e depois as regras mudaram: clientes organizacionais com mais de 200 funcionários não podiam mais usar Anaconda gratuitamente e precisavam pagar por uma licença comercial

    • O uv é licenciado sob MIT ou Apache-2.0
      Eles podem parar o desenvolvimento ou mover o trabalho futuro para um fork com outra licença, mas não podem alterar retroativamente a licença anterior; portanto, o que existe hoje é garantidamente open source
      Se isso realmente preocupa você, basta criar um fork e mantê-lo sincronizado
      Na prática, isso vale quase da mesma forma para outros projetos OSS, então eu não me preocuparia muito
      Até onde sei, o conda nunca foi open source e distribuía binários
      https://github.com/astral-sh/uv?tab=readme-ov-file#license
    • Vejo o rug pull da Anaconda como algo ligado ao repositório
      Os pacotes do conda-forge continuam gratuitos
      Como o uv simplesmente usa o PyPI, se houver problema dá para trocar o uv por pip, Poetry etc., e continuar obtendo os pacotes do mesmo lugar
    • Entendo que, se um projeto tiver um Contributor License Agreement (CLA) e fizer com que os contribuidores transfiram os direitos autorais de suas contribuições ao dono do projeto, é possível relicenciar
      Esse dono acabará sendo adquirido pela pior pessoa rica que se possa imaginar
      Passei os olhos no guia de contribuição e nas issues do uv, mas não vi CLA; já o PyTorch tinha um CLA logo no topo do guia de contribuição
      Ainda assim, deveria ter havido um fork comunitário da última versão FOSS do Anaconda
      No Redis isso aconteceu, e o Redis também usa CLA: https://github.com/redis/redis/blob/unstable/CONTRIBUTING.md#redis-software-grant-and-contributor-license-agreement
      Nunca se deve assinar um CLA
      Eu diria que é melhor contribuir apenas com projetos copyleft
      Somos bem pagos demais para trabalhar de graça
  • Para pequenos utilitários, parece uma boa alternativa de empacotamento em vez de conteinerização
    Agora preciso convencer todos os meus colegas a instalar o uv

    • O uv é surpreendentemente rápido, então isso deve ajudar
    • Para nós, o obstáculo é que nosso scanner de vulnerabilidades SCA ainda não funciona com uv
  • Parece semelhante ao bundler/inline do Ruby
    É bom ver algo parecido surgindo no Python, e na prática é muito conveniente
    https://bundler.io/guides/bundler_in_a_single_file_ruby_script.html

  • Fico curioso para saber se alguém conseguiu fazer isso funcionar no Windows
    Eu queria usar esse truque em uma ferramenta para um mod de jogo em que estou trabalhando, mas não consegui fazer o truque do shebang funcionar

  • Passei a gostar do uv por causa desse caso de uso, mas acho que o fato de uma PEP oficial e muito útil não ser suportada pelas ferramentas oficiais do Python vai contra o Zen of Python
    Do meu ponto de vista, foi a primeira vez que Python deixou de vir com “batteries included”
    Agora também tenho dois gerenciadores de dependências de Python no sistema
    Sei que há muito a dizer sobre gerenciamento de dependências em Python, mas, nos últimos anos, quando um projeto tinha apenas requirements.txt, consegui me virar com o pip+venv padrão

    • Houve um pouco dessa dinâmica na especificação de build do Python
      Pelo que me lembro, pyproject.toml veio antes da biblioteca tomllib
      Então, por algumas versões, era preciso especificar módulos em uma linguagem que o Python não conseguia ler por padrão
      É uma situação pior do que haver uma forma padrão de incluir metadados que não são usados
      Afinal, é por isso que são metadados; caso contrário, seriam apenas sintaxe de Python