← Volver al Blog
Tutorials & How-Tos

Lista de comprobación para generar y publicar paquetes con uv: cuándo basta el backend de uv y cuándo no

uv puede simplificar la generación y publicación de paquetes de Python, pero no elimina la necesidad de demostrar que tu wheel o distribución de código fuente funciona fuera de tu espacio de trabajo local. Esta lista de comprobación ayuda a los mantenedores a decidir cuándo basta el backend de construcción de uv, cuándo se necesita otro backend y cómo verificar los artefactos antes de publicar.

Escrito por Hamza Diaz
3 de octubre de 202610 min de lectura89 vistas

uv build puede facilitar la publicación de paquetes de Python, pero no garantiza que el wheel o la distribución de código fuente generados sean correctos. Un proyecto puede resolver su archivo de bloqueo, superar las pruebas desde la copia local del repositorio y arrancar dentro de Docker, y aun así fallar cuando alguien instala el artefacto publicado.

Ese fallo suele deberse a una carencia sencilla: un archivo local, una dependencia basada en una ruta, un directorio de datos del paquete o una importación de un script de consola existía en el espacio de trabajo, pero no en la distribución. Esta lista de comprobación está dirigida a mantenedores que quieren usar uv sin confundir un repositorio que funciona con un paquete listo para publicar. Los comandos siguientes son comprobaciones propuestas para tu proyecto. No se ejecutaron en este flujo de trabajo y no se ha inventado ninguna salida de comandos.

La regla: prueba el artefacto, no la copia local del repositorio

uv ofrece a los mantenedores una única herramienta para gestionar proyectos, generar distribuciones y publicar. La guía oficial de paquetes documenta uv build y uv publish, y la guía del backend documenta uv_build como backend de construcción. Estas herramientas son útiles. Aun así, queda una pregunta difícil: ¿puede un usuario externo instalar y usar lo que planeas publicar?

Un usuario recibe metadatos y archivos de un wheel, un sdist o un índice de paquetes. No recibe tu copia editable del repositorio, la configuración de fuentes del espacio de trabajo, una dependencia privada basada en una ruta, la caché de capas de Docker ni una carpeta de datos local, salvo que esos elementos formen parte del artefacto o sean accesibles mediante los metadatos declarados. Por eso el artefacto necesita sus propias pruebas.

Es el mismo enfoque basado en evidencias que se aplica en la observabilidad de la compilación de motores TensorRT. Un mensaje de progreso no equivale a una prueba. En el empaquetado, superar las pruebas locales no equivale a que un usuario pueda instalar el paquete desde un wheel.

flowchart TD A[El proyecto funciona en el checkout] --> B[Elegir backend deliberadamente] B --> C[Construir wheel y sdist] C --> D[Inspeccionar archivos por nombre exacto] D --> E[Reconstruir desde sdist cuando corresponda] E --> F[Instalar wheel fuera del checkout] F --> G[Importar modulo y cargar datos del paquete] G --> H{Listo para publicar?} H -->|No| B H -->|Si| I[Publicar, luego verificar desde el indice objetivo]

Cuándo basta uv_build y cuándo no

El backend de construcción de uv es un buen candidato cuando el paquete está escrito íntegramente en Python y tiene una estructura convencional: módulos habituales, una estructura src clara, metadatos sencillos, datos de paquete simples y ningún mecanismo de publicación específico del backend. Una biblioteca pequeña o una CLI con una API pública acotada es una mejor opción para una primera migración que un paquete que también compila código nativo o ensambla artefactos generados durante la publicación.

Conviene tener presente el límite actual: uv_build solo admite paquetes de Python puro. Si el propio paquete compila módulos de extensión, se necesita otro backend. Ese es el límite documentado. uv puede seguir siendo útil como frontend de otro backend, pero conviene esperar a demostrar la equivalencia de los artefactos antes de sustituir el backend. En los proyectos de Python para GPU o vinculados al código nativo, como las cargas de trabajo comentadas en la prueba de aceptación de la ruta de NVIDIA Warp, esa separación debe quedar clara. Empaquetar un wrapper de Python no es lo mismo que compilar todas las dependencias a las que puede llamar.

Tipo de proyectoElección del backendQué demostrar antes de publicar
Biblioteca de Python puro con estructura srcuv_build es un candidato razonableEl código del wheel se puede importar, el sdist permite reconstruir el wheel y los datos del paquete están presentes
Paquete CLI sencillouv_build puede encajarEl script de consola instalado llama al módulo empaquetado
Paquete con archivos generadosDecidir una vez definido el proceso de generaciónLos archivos generados están en ambos artefactos o se regeneran a partir del sdist
Paquete con módulos de extensiónUsar otro backendSe gestionan los pasos de compilación nativa, las etiquetas de plataforma y las cadenas de herramientas
Proyecto consolidado con personalizaciones de setuptools o HatchlingConservar inicialmente el backend actualEl frontend de uv funciona sin perder el comportamiento de publicación existente

Ejemplo práctico: invoice-normalizer

Usa un pequeño paquete hipotético para que las comprobaciones tengan algo concreto que inspeccionar. El paquete normaliza diccionarios de facturas e incluye un mapa de monedas como datos del paquete. También tiene un script de consola, otro punto en el que pueden aparecer errores de empaquetado.

pyproject.toml:

[build-system]
requires = ["uv_build>=0.12.22,<0.13"]
build-backend = "uv_build"

[project]
name = "invoice-normalizer"
version = "0.1.0"
description = "Normalize simple invoice payloads."
readme = "README.md"
requires-python = ">=3.11"
dependencies = []

[project.scripts]
invoice-normalizer = "invoice_normalizer:main"

README.md no es opcional en este ejemplo, porque pyproject.toml hace referencia a él. Créalo antes de generar las distribuciones:

# invoice-normalizer

Normalize simple invoice payloads for packaging checks.

src/invoice_normalizer/__init__.py:

from importlib.resources import files
import json

def normalize_invoice(payload: dict) -> dict:
    currency = payload.get("currency", "USD").upper()
    data = json.loads(files(__package__).joinpath("data/currencies.json").read_text())
    if currency not in data["supported"]:
        raise ValueError(f"unsupported currency: {currency}")
    return {"invoice_id": str(payload["invoice_id"]), "currency": currency}

def main() -> None:
    print(normalize_invoice({"invoice_id": 123, "currency": "usd"}))

src/invoice_normalizer/data/currencies.json:

{"supported": ["USD", "EUR", "GBP"]}

Este no es un sistema de facturación para producción. Es deliberadamente pequeño. Ofrece una función pública que se puede importar, un script de consola y datos del paquete: precisamente los elementos que suelen funcionar desde la copia local del repositorio y fallar desde el wheel generado.

Generar e inspeccionar los archivos por su nombre exacto

Primero genera ambas distribuciones:

uv build

Después inspecciona los archivos que esperas encontrar usando su nombre exacto. Evita ejemplos imprecisos con comodines en la documentación y en CI, porque pueden quedar artefactos antiguos en dist y dar la impresión de que una comprobación es más fiable de lo que realmente es.

python -m zipfile --list dist/invoice_normalizer-0.1.0-py3-none-any.whl
python -m tarfile --list dist/invoice_normalizer-0.1.0.tar.gz

Busca invoice_normalizer/__init__.py, invoice_normalizer/data/currencies.json, los metadatos del proyecto, los archivos README utilizados, los archivos de licencia declarados y los metadatos de los puntos de entrada de los scripts de consola. El resultado esperado es que el artefacto contenga los archivos que tu API pública necesita tras la instalación. No se trata de una salida registrada en este flujo de trabajo.

ComprobaciónComando o elemento que revisarResultado esperado
Contenido del wheelpython -m zipfile --list ...whlEl módulo y el archivo de datos aparecen en el wheel
Contenido del sdistpython -m tarfile --list ...tar.gzEl código fuente, los archivos utilizados para los metadatos y el archivo de datos aparecen en el sdist
Instalación limpia del wheelInstalar el wheel exacto fuera de la copia local del repositorioLa ruta de importación corresponde al paquete instalado, no al repositorio
Carga de datos del paqueteLlamar a normalize_invoice después de instalarLos datos JSON están disponibles mediante importlib.resources
Prueba básica de la CLIEjecutar el comando invoice-normalizer instaladoEl punto de entrada remite al código empaquetado

Reconstruir a partir del sdist

Si publicas un sdist, demuestra que permite reconstruir el wheel a partir de los archivos que contiene. Esto detecta errores distintos de los que se encuentran al inspeccionar el primer wheel. La ausencia de un README, un archivo generado o un directorio de datos puede pasar inadvertida hasta que un usuario genera el paquete a partir del código fuente.

REPO_DIR="$PWD"
SDIST_PATH="$REPO_DIR/dist/invoice_normalizer-0.1.0.tar.gz"
SDIST_CHECK_DIR="$(mktemp -d)"

cd "$SDIST_CHECK_DIR"
python -m tarfile --extract "$SDIST_PATH"
cd invoice_normalizer-0.1.0
uv build --wheel
python -m zipfile --list dist/invoice_normalizer-0.1.0-py3-none-any.whl

El resultado esperado es obtener un wheel reconstruido a partir del contenido extraído del sdist y comprobar que sigue incluyendo el módulo y los datos del paquete. Considera estos pasos una secuencia de validación propuesta, no un informe de comandos ejecutados aquí.

Instalar el wheel fuera de la copia local del repositorio

Para comprobar una instalación limpia, crea el entorno fuera del repositorio, instala el wheel exacto mediante una ruta absoluta y ejecuta allí las comprobaciones de importación y de datos del paquete. Ejecutar uv run desde la copia local del código fuente no demuestra que el empaquetado sea correcto.

REPO_DIR="$PWD"
WHEEL_PATH="$REPO_DIR/dist/invoice_normalizer-0.1.0-py3-none-any.whl"
WHEEL_CHECK_DIR="$(mktemp -d)"

cd "$WHEEL_CHECK_DIR"
uv venv .venv
. .venv/bin/activate
uv pip install "$WHEEL_PATH"
python -c 'from invoice_normalizer import normalize_invoice; assert normalize_invoice({"invoice_id": 7, "currency": "eur"}) == {"invoice_id": "7", "currency": "EUR"}'
python -c 'from importlib.resources import files; assert files("invoice_normalizer").joinpath("data/currencies.json").is_file()'
invoice-normalizer

La aserción de importación comprueba la función pública. La aserción de recursos comprueba que el archivo JSON esté empaquetado y no solo presente en el árbol de código fuente. El comando de la CLI comprueba que los metadatos del punto de entrada se resuelvan tras la instalación. De nuevo, estos son los resultados esperados que un mantenedor debe registrar en CI o en las notas de publicación; este artículo no afirma que los comandos se hayan ejecutado localmente.

Qué demuestra uv build --no-sources

Conviene usar uv build --no-sources, pero hay que delimitar con precisión qué comprueba. Desactiva tool.uv.sources al resolver las dependencias de construcción indicadas en build-system.requires. Esto puede revelar supuestos sobre la configuración de las fuentes de las que se obtienen los requisitos de construcción.

No resuelve por sí mismo las dependencias en tiempo de ejecución. No audita las importaciones en tiempo de ejecución. No detecta todos los paquetes no declarados que tu código importa tras la instalación. Si un módulo importa requests en tiempo de ejecución, pero no lo declara como dependencia, --no-sources no es la comprobación que permite detectar ese error. Una instalación limpia seguida de pruebas básicas de importación y del flujo de trabajo es una mejor forma de detectarlo.

Usa --no-sources como una comprobación más en la secuencia de publicación, no como el único criterio para autorizarla. Si falla, aclara a qué uso está destinado el paquete. Los paquetes públicos necesitan requisitos accesibles con versiones especificadas. Los paquetes privados necesitan instrucciones de acceso al índice. Los paquetes destinados exclusivamente al espacio de trabajo interno no deben publicarse como si los usuarios externos pudieran instalarlos. La discusión histórica en GitHub sobre la separación del backend de uv aporta contexto práctico útil, pero la incidencia 3957 no debe considerarse una prueba de un error actual en la configuración de fuentes.

{
  "checklist": "Outside-Consumer Build Check",
  "minimumSignals": ["wheel built", "sdist built", "archives inspected by exact name", "wheel installed outside checkout", "import and package data verified"],
  "noSourcesBoundary": "Checks build dependency resolution without tool.uv.sources; it is not a runtime dependency audit."
}

Comprobaciones de Docker y de publicación

Docker resulta útil cuando necesitas una versión fija de uv, una sincronización reproducible de dependencias y un entorno limpio de CI. También puede ocultar errores de empaquetado si la imagen copia directamente el árbol de código fuente o reutiliza capas almacenadas en caché. Valida por separado la imagen de la aplicación y la distribución. Que un contenedor arranque no demuestra que el wheel incluya currencies.json.

Antes de ejecutar uv publish, confirma el nombre del paquete, la versión, el requisito de Python, las dependencias, los archivos README utilizados, los metadatos de licencia y la configuración del índice de destino. Después de publicar, verifica la instalación como usuario desde ese índice usando la versión exacta. Que la subida se complete solo demuestra que el registro aceptó los archivos. No demuestra que los usuarios puedan importar el paquete, ejecutar la CLI o cargar los datos empaquetados.

PUBLISH_CHECK_DIR="$(mktemp -d)"
TARGET_INDEX_URL="https://test.pypi.org/simple"

cd "$PUBLISH_CHECK_DIR"
uv venv .venv
. .venv/bin/activate
uv pip install --index-url "$TARGET_INDEX_URL" "invoice-normalizer==0.1.0"
python -c 'from invoice_normalizer import normalize_invoice; assert normalize_invoice({"invoice_id": 42}) == {"invoice_id": "42", "currency": "USD"}'
python -c 'from importlib.resources import files; assert files("invoice_normalizer").joinpath("data/currencies.json").is_file()'
invoice-normalizer

Para una publicación real, configura TARGET_INDEX_URL con el índice en el que hayas publicado y añade explícitamente la autenticación necesaria o la configuración de índices adicionales. El resultado esperado es una instalación limpia de la versión exacta desde ese índice, seguida de las mismas comprobaciones de importación, datos y CLI utilizadas antes de publicar.

Errores comunes

El primer error es equiparar la resolución correcta del archivo de bloqueo con una publicación correcta. Un archivo de bloqueo aporta evidencias sobre el contexto del proyecto, no sobre la distribución. Otro error común es cambiar el backend, la estructura de dependencias, CI y las credenciales de publicación de una sola vez. Eso dificulta el diagnóstico de los fallos. Cambia una capa cada vez.

Un tercer error es omitir el sdist porque el wheel se instaló localmente. Algunos usuarios siguen generando el paquete a partir del código fuente, y un sdist defectuoso puede ser el primer fallo que encuentren. Un cuarto error es asumir que Docker ya demostró la validez del artefacto. Solo lo habrá hecho si el contenedor instaló el mismo wheel o sdist que recibirá un usuario.

La mejor forma de adoptar uv es casi aburrida, algo positivo en la ingeniería de publicaciones: úsalo primero como frontend, conserva el backend actual si ya incorpora el comportamiento de publicación y adopta uv_build solo cuando la estructura del proyecto sea adecuada. Trata el wheel y el sdist como el producto. Valídalos desde fuera de la copia local del repositorio. Elige el backend cuando las evidencias sobre los artefactos respalden esa decisión.

Puntos clave

  • 1uv simplifica la generación y publicación de paquetes de Python, pero los artefactos aún deben validarse desde la perspectiva de un usuario externo.
  • 2uv_build solo admite actualmente proyectos de Python puro; los módulos de extensión requieren otro backend.
  • 3uv build --no-sources comprueba la resolución de dependencias de construcción sin tool.uv.sources; no verifica que se hayan declarado todas las dependencias en tiempo de ejecución.
  • 4Instalar los artefactos generados en un entorno limpio fuera de la copia local del repositorio ofrece la evidencia local más sólida antes de publicar.
  • 5La reproducibilidad de Docker no demuestra que el wheel o el sdist sean correctos.

Conclusión

uv resulta más útil cuando facilita la comprensión del empaquetado, no cuando oculta supuestos sobre la publicación tras un comando más rápido. Trata el wheel y el sdist como el producto, valídalos desde fuera de la copia local del repositorio y elige uv_build solo cuando el paquete sea de Python puro y los artefactos generados demuestren que está listo para publicar.

Preguntas frecuentes

¿Qué crea uv build?

uv build crea artefactos de distribución de Python, como wheels y distribuciones de código fuente, según el proyecto y el comando utilizado.

¿Cuándo debo usar el backend de construcción de uv?

Usa uv_build para paquetes compatibles de Python puro con estructuras convencionales. Usa otro backend cuando el paquete compile módulos de extensión o necesite un comportamiento personalizado del backend.

¿Para qué sirve uv build --no-sources?

Desactiva tool.uv.sources al resolver las dependencias de construcción indicadas en build-system.requires, lo que ayuda a revelar supuestos sobre las fuentes de construcción. No es una auditoría de dependencias en tiempo de ejecución.

¿Una imagen de Docker que funciona demuestra que mi paquete de Python es correcto?

No. Docker puede copiar archivos fuente o usar capas almacenadas en caché que oculten archivos ausentes del wheel o del sdist.

¿Qué debo verificar después de publicar?

Instala la versión publicada desde el índice de destino en un entorno limpio y comprueba las importaciones, los scripts de consola, los datos del paquete y un flujo mínimo de uso del paquete.

Fuentes

Compartir este artículo

Hamza Diaz

Escrito por

Hamza Diaz

Hamza Diaz es el fundador de Optijara, donde crea agentes de IA prácticos, sistemas de automatización y flujos de trabajo de Copilot para empresas de servicios. Escribe sobre operaciones de IA, estrategia de agentes e implementación real para equipos que quieren sistemas útiles en lugar de promesas vacías.