Depedencias automáticas en scripts Python con autopep723

¿Alguna vez has deseado poder ejecutar un script de Python sin preocuparte por instalar sus dependencias primero?

Todos hemos estado ahí. Necesitas hackear un pequeño fragmento de código, tu LLM amigo te da un script de Python útil o encontraste uno en Stack Overflow (Dios te tenga en la gloria) o Gist. Pero si lo intentas ejecutar directamente:

ModuleNotFoundError: No module named 'your_experiment_dependency'

Entonces comienza la burocracia de verificar qué paquetes necesitas, con el agravante de que muchas veces el nombre con el que se instala no corresponde con el nombre que se importa), lidiar con conflictos de versiones y sobrecargar tu entorno.

Si todo el mundo coincide en que Python es un lenguaje pragmático y elegante que resulta ideal para scripts, ¿por qué esta parte tiene que ser complicada?

Considera este script de análisis de datos

import httpx
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans

response = httpx.get("https://api.github.com/users/octocat")
data = pd.DataFrame([response.json()])
# ... análisis

La forma tradicional implica configurar un entorno virtual, activarlo e instalar manualmente cada dependencia:

python -m venv venv
source venv/bin/activate  # o venv\Scripts\activate en Windows
pip install httpx pandas matplotlib seaborn scikit-learn
python script.py

Por supuesto, la situación mejoró mucho desde la aparición del glorioso uv con el que podés ejecutar tu script en un virtualenv efímero en una sola línea

uv run --with httpx --with pandas --with matplotlib --with seaborn --with scikit-learn script.py

Pero claramente esto no escala y cada vez que quieras ejecutar tu script tendrás que recordar todos los paquetes que necesitas. Es decir, el script no es autocontenido.

Un paso mejor es incluir un comentario de metadata en la cabecera del script en el formato de PEP 723 que uv run soporta, de modo que el propio script declare sus dependencias y sea "autocontenido".

# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "httpx",
#     "pandas",
#     "matplotlib",
#     "seaborn",
#     "scikit-learn",
# ]
# ///
import httpx
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans

Pero hay una solución más elegante: ejecuta tus scripts usando autopep723

uvx autopep723 script.py

Como uv run puede ejecutar scripts remotos (tecnicamente, bajarlos antes de ejecutarlos), también lo puede hacer autopep723, así que probemos con un ejemplo real

uvx autopep723 https://gist.githubusercontent.com/mgaitan/7052bbe00c2cc88f8771b576c36665ae/raw/cbaa289ef7712b5f4c5a55316cce4269f5645c20/autopep723_rocks.py

autopep8

Un tip extra? Usa autopep723 directamente como el shebang de tu script

#!/usr/bin/env -S uvx autopep723
import httpx
...

Después de marcar el script como ejecutable (es decir, chmod +x script.py) simplemente podrás ejecutar ./script.py. ¡Comparte este script con cualquiera y simplemente funcionará también en su máquina siempre que tenga uv!

Cómo funciona

Entre bastidores, autopep723 analiza tu script usando el módulo ast para detectar importaciones de terceros, maneja casos complicados conocidos donde los nombres de las importaciones difieren de los nombres de los paquetes (por ejemplo, import PILpillow), luego ejecuta tu script usando uv run pasando los --with necesarios automáticamente. Todo sucede en un entorno limpio y efímero gracias a las capacidades de aislamiento de uvx.

En mi humilde opinión, este enfoque resuelve varios puntos débiles que plagan la distribución de scripts de Python. Los scripts simplemente funcionan de inmediato con cero fricción de configuración, mientras que uvx garantiza que no haya contaminación del entorno, ya que las dependencias se instalan de forma aislada. Cada script obtiene su propio entorno limpio, lo que evita conflictos de versiones, lo que hace que los scripts sean verdaderamente portátiles sin instrucciones de instalación. La velocidad de uv hace que esto sea práctico para el uso diario en lugar de solo una demostración inteligente.

Por cierto, también podés usar autopep723 para agregar metadatos PEP 723 a tus scripts existentes:

autopep723 add script.py  # Agrega PEP 723 al archivo

y luego simplemente usa uv run script.py

El futuro

Para ser honesto, me gustaría que este paquete no existiera, sino que fuera una característica incorporada de uv.

Tuiteé sobre esta idea pidiendo un flag --with-auto que haga su mejor intento para satisfacer las dependencias faltantes:

Aquí está mi propuesta abierta. Hasta entonces, autopep723 cierra la brecha, haciendo que los scripts de Python sean tan fáciles de ejecutar como los scripts de shell.

Comentarios

Comments powered by Disqus