- Incluso los scripts pequeños para probar APIs requieren ajustar el entorno de ejecución por sus dependencias, pero usando el shebang de ejecución de uv se pueden convertir en una forma ejecutable de inmediato, sin pasos de instalación
- El ejemplo
jam_users.py entra a un REPL después de eliminar y crear usuarios con httpx, IPython y loguru para probar el endpoint /users de una API en Go
- Con el enfoque tradicional hay que instalar paquetes globalmente en el Python del sistema o preparar manualmente un entorno virtual, lo que vuelve engorroso compartir y volver a ejecutar el script
- Si se declaran
dependencies = ["ipython", "httpx", "loguru"] en el encabezado # /// script y se ejecuta uv run jam_users.py, uv se encarga del entorno aislado y de instalar las dependencias
- Al agregar el shebang
#!/usr/bin/env -S uv run --script y permisos de ejecución, en sistemas Unix se puede ejecutar como ./jam_users.py con solo uv instalado
Declarar dependencias dentro del script
- El ejemplo
jam_users.py prepara datos de usuarios de prueba contra la API local http://localhost:4000/v1/users
- Envía solicitudes a la API con httpx
- Entra al REPL de IPython para revisar las respuestas y continuar las pruebas
- Registra logs de eliminación y creación con
loguru
- El script básico sigue el flujo de inicializar la lista de usuarios y luego volver a cargar los datos de prueba
- Obtiene la lista de usuarios existente con
GET /v1/users
- Llama a
DELETE /v1/users/{id} para cada usuario
- Envía la lista de usuarios preparada como JSON a
POST /v1/users
- Después abre el REPL con
IPython.embed()
- Para ejecutarlo con
python jam_users.py, httpx, IPython y loguru ya deben estar instalados en el entorno de ejecución
- También se puede instalar globalmente en el Python del sistema o crear un entorno virtual aparte, pero ambos métodos requieren preparación antes de ejecutar
- La instalación global puede ensuciar el Python del sistema con paquetes
- El enfoque con entorno virtual exige gestionar manualmente la creación, activación, instalación y ejecución
- En ambos casos hace falta un Python del sistema compatible con los paquetes necesarios
Crear un script ejecutable directamente con uv
- Si se coloca la etiqueta
# /// script de uv al inicio del script, se pueden declarar dependencias dentro del archivo
# /// script
# dependencies = ["ipython", "httpx", "loguru"]
# ///
- Con este encabezado, se puede ejecutar con
uv run jam_users.py
- uv crea un entorno virtual aislado para el script
- Descarga e instala las dependencias necesarias
- Ejecuta el script dentro del contexto de ese entorno virtual
- Un shebang común de Python, como
#!/usr/bin/env python, no puede aprovechar el encabezado de script de uv porque Python ignora el comentario # /// script
- Si se coloca la llamada a uv directamente en el shebang, el script puede tratarse como un archivo ejecutable
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["ipython", "httpx", "loguru"]
# ///
- La bandera
-S de env hace que la cadena siguiente se divida en argumentos separados y se pase a env
- Después de otorgar permisos de ejecución con
chmod +x jam_users.py, se puede ejecutar directamente así
./jam_users.py
- Con este enfoque, si uv está instalado en el sistema Unix, se puede ejecutar el script sin instalar dependencias por separado ni administrar entornos virtuales
- Al entregar scripts de Python complejos a otros usuarios, se reduce la carga de explicar largos pasos de preparación del sistema antes de ejecutarlos
1 comentarios
Comentarios de Hacker News
No me molesta UV en sí, sino en general la forma de controlar la ejecución del código mediante comentarios.
Está bien usar comentarios para directivas del linter o notas de desarrolladores, pero si se trata de configuración o datos relacionados con la ejecución, me parece mucho mejor algo como
UV_ENV = { "dependencies": { "requests": "2.32.3", "pandas": "2.2.3" } }.Ese enfoque es sintaxis válida de Python, usa estructuras de datos estándar en lugar de parsear comentarios arbitrarios, por lo que es fácil de generar y validar, y, sobre todo, respeta el principio de que el código debería ejecutarse igual aunque se eliminen todos los comentarios.
El enfoque propuesto también es una constante mágica que no hace nada en runtime, así que solo se parsea mediante análisis estático, y otra herramienta podría verla como código sin usar y eliminarla en vez de que uv la lea.
Mejor sería que, después de
import uv, se llamara directamente a uv para decirle qué hacer, por ejemplouv.exec(dependencies=["clown"], python=">=3.10").La primera ejecución se haría con cualquier runtime de Python que pueda encontrar este paquete
uvhipotético, y luego el paqueteuvprepararía el entorno virtual y el runtime de Python, añadiría una bandera como una variable de entorno y haríare-exec(3).En el segundo runtime,
uv.execdetectaría la bandera y no haría nada.https://peps.python.org/pep-0723/
Además, la línea shebang en sí también funciona en la práctica como un comentario.
Lleva 45 años tan profundamente arraigada que la gente simplemente no suele ser consciente de que es un comentario del shell.
Si se instalaron las mismas dependencias, el código se ejecuta igual.
Más que cambiar el significado del código en sí, cambia el entorno en el que se ejecuta, y en ese sentido no es distinto del comentario
#!/bin/bashal inicio de un script de shell.Dicho eso, es muy probable que uv no quiera ejecutar código para averiguar las dependencias, así que tendría que ser un subconjunto muy limitado de la sintaxis de Python.
El solo hecho de que algo así sea necesario ya expone una debilidad del lenguaje.
La sentencia
importen sí debería poder transmitir toda la información sobre dependencias.En los últimos meses este tema apareció con mucha frecuencia en HN; algunos ejemplos recientes son:
https://news.ycombinator.com/item?id=43500124
https://news.ycombinator.com/item?id=42463975
Me gusta uv, pero me cuesta aceptar la expresión self-contained por dos motivos.
Primero, para ejecutar el script uv ya tiene que estar instalado.
Se podría hacer que un script de shell verifique si uv está instalado y, si no, lo instale con curlpipe, pero eso aumenta bastante el boilerplate y el enfoque de curlpipe tampoco es bueno.
Segundo, crear automáticamente un entorno virtual en algún lugar del directorio home no es realmente self-contained.
Aunque lo ejecutes una sola vez y luego borres el script, ese entorno virtual queda ahí ocupando espacio, y no encontré en la documentación de uv ninguna garantía de que limpie automáticamente esos entornos virtuales temporales.
curl | sh, es válida.Sin embargo, a medida que los gestores de paquetes empiecen a incluir uv en sus repositorios, será un problema cada vez menor.
Por ejemplo, uv ya está disponible en Alpine Linux y Homebrew: https://repology.org/project/uv/versions
Además, los metadatos de script en línea son un estándar de Python.
Si el sistema no tiene uv y tampoco está empaquetado, pero sí tiene una versión de Python compatible con el script, se puede ejecutar con pipx: https://pipx.pypa.io/stable/examples/#pipx-run-examples
pipx está empaquetado de forma mucho más amplia: https://repology.org/project/pipx/versions
En una máquina cualquiera de desarrollo, Docker puede ser más común que uv, y como el artículo original habla de un proyecto de empresa, sonaba razonable.
El problema es que por ahora no cachea nada, así que descarga todo en cada ejecución, lo cual se siente raro; probablemente se pueda arreglar con volúmenes.
Sería algo así: https://hugojosefson.github.io/docker-shebang/#python
Aun así, si al ejecutarlo descarga cosas desconocidas de internet, es difícil llamarlo self-contained; algo realmente self-contained se parece más a AppImage.
Con algo como py2exe se puede crear un “script de Python” self-contained.
A los desarrolladores les causa muchos problemas, pero para los usuarios los problemas se minimizan.
Con Nix también se usa el mismo enfoque, y la línea shebang se ve así:
#! nix-shell -i python3 -p "python312.withPackages (pkgs: [ pkgs.boto3 pkgs.click ])"Con esto, lo único que se necesita en el sistema es Nix; ni siquiera hace falta tener Python instalado.
El ejemplo más obvio es bash con todas las dependencias especificadas, y también he llegado a crear scripts Rust rápidos de un solo archivo con shebang de Nix.
https://nixos.wiki/wiki/Nix-shell_shebang
nix shell, el comando basado en flakes, en lugar denix-shell.Como dicen otros comentarios, la afirmación de que es “self-contained” depende de que
uvesté instalado.Si de verdad quieres un script de Python self-contained, vale la pena echarle un vistazo al compilador Nuitka.
Lo usamos sin problemas en producción para servicios gRPC, y basta con ejecutar
nuitka --onefile run.py.Como es un compilador, el binario resultante también puede ser más rápido que el programa Python original empaquetado con PyInstaller.
En la página de GitHub del autor aparece la frase: “La misión de mi vida es seguir intentando hasta morirme de viejo crear el mejor Python Compiler posible”.
https://nuitka.net/
https://github.com/kayhayen
Me gusta mucho este patrón, pero lamentablemente no pude hacerlo funcionar bien con LSP.
Uso pyright en Helix, y ni siquiera funciona ejecutando el editor con
uv run hx script.py.Se puede hacer algo como
uv run --with whatever-it-is-i-need hx script.py, pero empieza a haber cada vez más duplicación.uve:$ cat ~/.local/bin/uve#!/bin/bashtemp=$(mktemp)uv export --script $1 --no-hashes > $tempuv run --with-requirements $temp vim $1unlink $tempOjalá pronto el editor soporte
uv python find --script.Se ve bastante útil.
Me pregunto si uv es una opción más segura para distribuir proyectos basados en Python a largo plazo.
Me recuerda al rug pull de hace unos 5 años, cuando usamos Anaconda para administrar dependencias y luego cambiaron las reglas: los clientes corporativos con organizaciones de más de 200 empleados ya no podían usar Anaconda gratis y tenían que pagar una licencia comercial.
Podrían dejar de desarrollarlo o mover el trabajo futuro a un fork con otra licencia, pero no pueden cambiar retroactivamente la licencia anterior, así que lo que existe hoy queda garantizado como open source.
Si realmente te preocupa, puedes hacer un fork y mantenerlo sincronizado.
En la práctica, esto aplica casi igual a cualquier otro proyecto OSS, así que no me preocuparía demasiado.
Hasta donde sé, conda nunca fue open source y distribuía binarios.
https://github.com/astral-sh/uv?tab=readme-ov-file#license
Los paquetes de conda-forge todavía se pueden usar gratis.
uv simplemente usa PyPI, así que si surge un problema puedes cambiar de uv a pip, Poetry u otra herramienta, y seguir trayendo los paquetes del mismo lugar.
Ese dueño terminará siendo adquirido por la peor persona rica que puedas imaginar.
Revisé por encima la guía de contribución y los issues de uv, pero no vi ningún CLA; en PyTorch, en cambio, el CLA está al inicio de la guía de contribución.
Aun así, debería haber existido un fork comunitario de la última versión FOSS de Anaconda.
Con Redis sí ocurrió, y Redis también usa CLA: https://github.com/redis/redis/blob/unstable/CONTRIBUTING.md#redis-software-grant-and-contributor-license-agreement
Nunca hay que firmar un CLA.
Mejor contribuyamos solo a proyectos copyleft.
Nos pagan demasiado como para trabajar gratis.
Para utilidades pequeñas, parece una buena alternativa de empaquetado frente a la contenedorización.
Ahora tengo que convencer a todos mis colegas de que instalen uv.
Se parece a bundler/inline del lado de Ruby.
Me alegra que Python tenga algo parecido, y en la práctica es muy cómodo.
https://bundler.io/guides/bundler_in_a_single_file_ruby_script.html
Me pregunto si alguien logró hacer que esto funcione en Windows.
Quería usar este truco para una herramienta de un mod de juego en la que estoy trabajando, pero no pude hacer funcionar el truco del shebang.
$> uv init --script .py$> uv add --script .py ...$> uv add --script .py --dev ...$> uv run .pyEspero que sirva.
Fuente: https://docs.astral.sh/uv/guides/scripts/
.py.py launcher admite líneas shebang.
También se trató en una entrada de blog sobre el mismo tema publicada hace unos días y, según esa entrada, hay que quitar
-S: https://thisdavej.com/share-python-scripts-like-a-pro-uv-and-pep-723-for-easy-deployment/https://news.ycombinator.com/item?id=43500124
No lo probé directamente; en cambio, cambié la asociación de archivos para que todos los
.pyse abran por defecto conuv run.https://docs.python.org/3/using/windows.html#python-launcher-for-windows
https://peps.python.org/pep-0397/
https://pyinstaller.org
https://onor.io/2025/01/more-scripting-with-racket.html
Este caso de uso hizo que me gustara uv, pero siento que el hecho de que un PEP oficial y muy útil no esté soportado por las herramientas oficiales de Python va en contra del Zen of Python.
Desde mi punto de vista, fue la primera vez que Python dejó de venir con “batteries included”.
Ahora también tengo dos gestores de dependencias de Python en el sistema.
Sé que hay mucho que decir sobre la gestión de dependencias en Python, pero en los últimos años, si un proyecto tenía solo
requirements.txt, me las arreglaba con elpip+venvbásico.Recuerdo que
pyproject.tomlsalió antes que la bibliotecatomllib.Así que durante algunas versiones había que especificar módulos en un lenguaje que Python no podía leer de forma predeterminada.
Es una situación peor que tener una forma predeterminada de incluir metadatos que no se usan.
Para empezar, por eso son metadatos; si no, simplemente habría sido sintaxis de Python.