lunes, 26 de enero de 2026

Aprendiendo Python de 0 a experto - Empaquetando Aplicaciones Python

Packaging Python Applications
Python // Capítulo 16

Packaging Python Applications

Empaqueta, distribuye y comparte tu código con el mundo

Crear código Python es solo el primer paso. Para que otros puedan usar tu trabajo, necesitas empaquetarlo y distribuirlo de manera profesional. Este capítulo explora el ecosistema moderno de empaquetado en Python, desde la estructura de un proyecto hasta su publicación en PyPI.

Aprenderás a usar pyproject.toml, los diferentes build backends, cómo crear wheels y source distributions, y cómo publicar tu paquete para que millones de desarrolladores puedan instalarlo con un simple pip install.

MÓDULO_01

¿Por qué Empaquetar?

El empaquetado de aplicaciones Python transforma tu código de un conjunto de archivos locales en un artefacto distribuible que otros pueden instalar y usar fácilmente. Es el puente entre desarrollar y compartir.

Beneficios del Empaquetado

DISTRIBUCIÓN
Comparte tu código con millones de desarrolladores a través de PyPI
DEPENDENCIAS
Gestiona automáticamente las bibliotecas que tu proyecto necesita
VERSIONADO
Control preciso de versiones para compatibilidad y actualizaciones
Flujo de Distribución
Código Fuente
Build
PyPI
pip install
PyPI (Python Package Index) es el repositorio oficial de paquetes Python. Con más de 500,000 proyectos, es donde los desarrolladores publican y descargan bibliotecas.
MÓDULO_02

Estructura del Proyecto

Un proyecto Python bien estructurado sigue convenciones que facilitan el empaquetado. La estructura src layout es la recomendada actualmente por la comunidad.

Layout Recomendado

mi_proyecto/ ├── pyproject.toml ├── README.md ├── LICENSE ├── src/ │ └── mi_paquete/ │ ├── __init__.py │ ├── core.py │ └── utils.py └── tests/ ├── __init__.py └── test_core.py

Archivos Esenciales

Archivo Propósito
pyproject.toml Configuración del proyecto y metadatos
README.md Documentación y descripción del proyecto
LICENSE Términos de uso y distribución
__init__.py Marca el directorio como paquete Python
El src layout previene importaciones accidentales del código fuente durante el desarrollo, asegurando que siempre uses la versión instalada del paquete.
MÓDULO_03

pyproject.toml

El archivo pyproject.toml es el estándar moderno para configurar proyectos Python. Reemplaza a los antiguos setup.py y setup.cfg con un formato más limpio y declarativo basado en TOML.

Estructura Básica

pyproject.toml
# Sistema de build
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

# Metadatos del proyecto
[project]
name = "mi-paquete"
version = "1.0.0"
description = "Un paquete Python increíble"
readme = "README.md"
license = "MIT"
requires-python = ">=3.9"

authors = [
    { name = "Tu Nombre", email = "tu@email.com" }
]

keywords = ["python", "packaging", "ejemplo"]

classifiers = [
    "Development Status :: 4 - Beta",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
]

# Dependencias
dependencies = [
    "requests>=2.28.0",
    "click>=8.0.0",
]

# Dependencias opcionales
[project.optional-dependencies]
dev = [
    "pytest>=7.0.0",
    "black>=23.0.0",
    "mypy>=1.0.0",
]
docs = [
    "sphinx>=6.0.0",
    "sphinx-rtd-theme",
]

# URLs del proyecto
[project.urls]
Homepage = "https://github.com/usuario/mi-paquete"
Documentation = "https://mi-paquete.readthedocs.io"
Repository = "https://github.com/usuario/mi-paquete"
Issues = "https://github.com/usuario/mi-paquete/issues"

Campos Obligatorios vs Opcionales

OBLIGATORIOS
name, version
RECOMENDADOS
description, readme, license, requires-python
OPCIONALES
authors, keywords, classifiers, urls
Los nombres de paquetes en PyPI deben ser únicos. Usa prefijos como tu nombre de usuario para evitar conflictos: mi_usuario_paquete.
MÓDULO_04

Build Backends

El build backend es la herramienta que convierte tu código fuente en paquetes distribuibles. Python soporta múltiples backends, cada uno con sus propias características y filosofía.

Comparativa de Backends

Backend Características Caso de Uso
Hatchling Rápido, moderno, extensible Proyectos nuevos, paquetes puros Python
Setuptools Maduro, ampliamente soportado Proyectos existentes, extensiones C
Flit Minimalista, simple Paquetes simples sin compilación
PDM Gestor de dependencias integrado Proyectos con gestión avanzada
Poetry Todo en uno, popular Proyectos que quieren ecosistema completo

Configuración por Backend

hatchling (recomendado)
[build-system]
requires = ["hatchling >= 1.26"]
build-backend = "hatchling.build"
setuptools
[build-system]
requires = ["setuptools >= 77.0", "wheel"]
build-backend = "setuptools.build_meta"
flit
[build-system]
requires = ["flit_core >= 3.12.0, <4"]
build-backend = "flit_core.buildapi"
Si no especificas un build backend, pip asume setuptools por defecto. Sin embargo, es mejor ser explícito para evitar sorpresas.
MÓDULO_05

Entry Points y Scripts

Los entry points permiten que tu paquete exponga funcionalidades que otros pueden descubrir y usar, incluyendo comandos de terminal que se crean automáticamente al instalar el paquete.

Console Scripts

Define comandos CLI que estarán disponibles después de la instalación:

pyproject.toml
[project.scripts]
mi-comando = "mi_paquete.cli:main"
otro-comando = "mi_paquete.tools:run"
mi_paquete/cli.py
import click

@click.command()
@click.option("--name", default="World", help="Nombre a saludar")
def main(name: str) -> None:
    """CLI principal del paquete."""
    click.echo(f"¡Hola, {name}!")

if __name__ == "__main__":
    main()
Después de pip install mi-paquete
$ mi-comando --name Python
¡Hola, Python!

GUI Scripts

Para aplicaciones gráficas (sin ventana de consola en Windows):

pyproject.toml
[project.gui-scripts]
mi-app-gui = "mi_paquete.gui:launch"

Plugins con Entry Points

Los entry points también permiten crear sistemas de plugins:

pyproject.toml
[project.entry-points."mi_app.plugins"]
json_exporter = "mi_paquete.exporters:JSONExporter"
csv_exporter = "mi_paquete.exporters:CSVExporter"
descubrir_plugins.py
from importlib.metadata import entry_points

# Descubrir todos los plugins registrados
plugins = entry_points(group="mi_app.plugins")

for plugin in plugins:
    print(f"Plugin: {plugin.name}")
    exporter_class = plugin.load()  # Carga la clase
    exporter = exporter_class()     # Instancia
Los entry points son la forma estándar en Python para implementar arquitecturas extensibles mediante plugins. Frameworks como pytest, Flask y Django los usan extensivamente.
MÓDULO_06

Formatos de Distribución

Python soporta dos formatos principales de distribución: source distributions (sdist) y wheels. Cada uno tiene propósitos diferentes y es importante entender cuándo usar cada uno.

Tipos de Distribución
Distribution Package
Source Dist (.tar.gz)
Wheel (.whl)

Source Distribution (sdist)

FORMATO
.tar.gz comprimido con código fuente
USO
Requiere build al instalar
VENTAJA
Universal, funciona en cualquier plataforma

Wheel

FORMATO
.whl (ZIP renombrado) pre-compilado
USO
Instalación directa sin build
VENTAJA
Rápido, no requiere compilador

Anatomía de un Wheel

Nombre del archivo wheel
# Formato: {nombre}-{version}-{python}-{abi}-{plataforma}.whl

mi_paquete-1.0.0-py3-none-any.whl
# │         │     │   │    │
# │         │     │   │    └── any: cualquier plataforma
# │         │     │   └── none: sin ABI específico
# │         │     └── py3: Python 3.x
# │         └── versión
# └── nombre del paquete

numpy-1.24.0-cp311-cp311-manylinux_x86_64.whl
# Wheel con extensiones C para CPython 3.11 en Linux x86_64
py3-none-any indica un wheel "universal" de Python puro que funciona en cualquier plataforma. Los paquetes con extensiones C necesitan wheels específicos por plataforma.
MÓDULO_07

Construir el Paquete

El proceso de build convierte tu código fuente en artefactos distribuibles. La herramienta estándar es build, que funciona con cualquier backend configurado en pyproject.toml.

Instalación de Herramientas

terminal
# Instalar build y twine
$ pip install build twine

# Verificar instalación
$ python -m build --version
$ twine --version

Proceso de Build

terminal
# Desde el directorio raíz del proyecto
$ cd mi_proyecto/

# Construir sdist y wheel
$ python -m build

# Solo sdist
$ python -m build --sdist

# Solo wheel
$ python -m build --wheel
python -m build
* Creating venv isolated environment...
* Installing packages in isolated environment...
* Building sdist...
* Building wheel from sdist...
Successfully built mi_paquete-1.0.0.tar.gz and mi_paquete-1.0.0-py3-none-any.whl

Estructura del dist/

dist/ ├── mi_paquete-1.0.0.tar.gz # Source distribution └── mi_paquete-1.0.0-py3-none-any.whl # Wheel

Verificar el Contenido

terminal
# Ver contenido del wheel
$ unzip -l dist/mi_paquete-1.0.0-py3-none-any.whl

# Ver contenido del sdist
$ tar -tvf dist/mi_paquete-1.0.0.tar.gz

# Verificar que el paquete es válido para PyPI
$ twine check dist/*
twine check dist/*
Checking dist/mi_paquete-1.0.0-py3-none-any.whl: PASSED
Checking dist/mi_paquete-1.0.0.tar.gz: PASSED
Siempre ejecuta twine check antes de subir a PyPI. Detecta problemas como descripciones mal formateadas que causarían errores al publicar.
MÓDULO_08

Instalación en Modo Editable

Durante el desarrollo, necesitas probar cambios sin reinstalar constantemente. El modo editable (o desarrollo) vincula tu código fuente directamente, reflejando cambios inmediatamente.

Instalación Editable

terminal
# Instalación editable básica
$ pip install -e .

# Con dependencias de desarrollo
$ pip install -e ".[dev]"

# Con múltiples grupos de extras
$ pip install -e ".[dev,docs,test]"
Flujo de Desarrollo
Editar código
Cambios inmediatos
Probar

Verificar la Instalación

terminal
# Ver información del paquete instalado
$ pip show mi-paquete

# Listar archivos instalados
$ pip show -f mi-paquete

# Verificar que funciona
$ python -c "import mi_paquete; print(mi_paquete.__version__)"
pip show mi-paquete
Name: mi-paquete
Version: 1.0.0
Location: /home/user/mi_proyecto/src
Editable project location: /home/user/mi_proyecto
Los cambios en el código Python se reflejan inmediatamente. Sin embargo, cambios en pyproject.toml (como nuevos entry points) requieren reinstalar: pip install -e .
MÓDULO_09

Publicar en PyPI

PyPI (Python Package Index) es el repositorio oficial donde se publican paquetes Python. Una vez publicado, cualquiera puede instalar tu paquete con pip install.

Proceso de Publicación

Crear cuenta en PyPI
Registra una cuenta en pypi.org y habilita 2FA
Generar API Token
En Account Settings → API tokens, crea un token con scope para tu proyecto
Configurar credenciales
Guarda el token de forma segura para autenticación
Subir con Twine
Usa twine para subir los artefactos de forma segura

Configurar Credenciales

~/.pypirc
[pypi]
username = __token__
password = pypi-AgEIcHlwaS5vcmc...  # Tu API token

[testpypi]
username = __token__
password = pypi-AgENdGVzdC5weXBp...  # Token de TestPyPI

Probar en TestPyPI

terminal
# Primero, probar en TestPyPI (repositorio de prueba)
$ twine upload --repository testpypi dist/*

# Verificar instalación desde TestPyPI
$ pip install --index-url https://test.pypi.org/simple/ mi-paquete

Publicar en PyPI Oficial

terminal
# Subir a PyPI oficial
$ twine upload dist/*

# Verificar que está disponible
$ pip install mi-paquete
twine upload dist/*
Uploading distributions to https://upload.pypi.org/legacy/
Uploading mi_paquete-1.0.0-py3-none-any.whl
100% ━━━━━━━━━━━━━━━━ 15.2/15.2 kB
Uploading mi_paquete-1.0.0.tar.gz
100% ━━━━━━━━━━━━━━━━ 12.8/12.8 kB

View at: https://pypi.org/project/mi-paquete/1.0.0/
Una vez publicada una versión en PyPI, no puedes sobrescribirla. Si encuentras un error, debes publicar una nueva versión (ej: 1.0.1). Usa TestPyPI primero.
MÓDULO_10

Versionado Semántico

El Semantic Versioning (SemVer) es un estándar para nombrar versiones que comunica la naturaleza de los cambios a los usuarios de tu paquete.

Formato SemVer
MAJOR
.
MINOR
.
PATCH

Significado de cada Número

Componente Cuándo Incrementar Ejemplo
MAJOR Cambios incompatibles (breaking changes) 1.0.0 → 2.0.0
MINOR Nueva funcionalidad compatible 1.0.0 → 1.1.0
PATCH Corrección de bugs 1.0.0 → 1.0.1

Versiones Pre-release

Ejemplos de versiones
"0.1.0"      # Desarrollo inicial
"1.0.0a1"    # Alpha 1
"1.0.0b1"    # Beta 1
"1.0.0rc1"   # Release Candidate 1
"1.0.0"      # Versión estable
"1.0.1"      # Patch con bugfix
"1.1.0"      # Nueva funcionalidad
"2.0.0"      # Breaking change

Versión Dinámica desde Git

pyproject.toml con hatchling
[project]
name = "mi-paquete"
dynamic = ["version"]  # Versión calculada dinámicamente

[tool.hatch.version]
source = "vcs"  # Obtener de Git tags

[tool.hatch.build.hooks.vcs]
version-file = "src/mi_paquete/_version.py"
Con versión desde VCS, simplemente crea un tag Git (git tag v1.0.0) y el build system extraerá la versión automáticamente. Elimina la sincronización manual.
MÓDULO_11

Incluyendo Archivos Adicionales

Además del código Python, tu paquete puede necesitar incluir archivos de datos, configuración, plantillas u otros recursos. La forma de incluirlos depende del build backend.

Package Data con Hatchling

pyproject.toml
[tool.hatch.build.targets.wheel]
packages = ["src/mi_paquete"]

[tool.hatch.build.targets.sdist]
include = [
    "/src",
    "/tests",
    "README.md",
    "LICENSE",
]

Package Data con Setuptools

pyproject.toml
[tool.setuptools.package-data]
mi_paquete = [
    "data/*.json",
    "templates/*.html",
    "static/**/*",
]

Acceder a los Recursos

mi_paquete/utils.py
from importlib.resources import files, as_file
import json

def load_config() -> dict:
    """Cargar archivo de configuración incluido en el paquete."""
    # Forma moderna (Python 3.9+)
    config_file = files("mi_paquete.data").joinpath("config.json")
    
    with as_file(config_file) as path:
        with open(path) as f:
            return json.load(f)

def read_template(name: str) -> str:
    """Leer plantilla HTML del paquete."""
    template = files("mi_paquete.templates").joinpath(name)
    return template.read_text()
importlib.resources es la forma recomendada de acceder a recursos del paquete. Funciona correctamente incluso si el paquete está instalado en un archivo ZIP.

Resumen del Capítulo

01 // pyproject.toml

El estándar moderno para configurar proyectos Python con metadatos, dependencias y build system.

02 // Build Backends

Hatchling, Setuptools, Flit y otros transforman tu código en paquetes distribuibles.

03 // Entry Points

Expone comandos CLI y plugins que se instalan automáticamente con tu paquete.

04 // Wheels y Sdist

Formatos de distribución: wheels pre-compilados y source distributions universales.

05 // Publicación PyPI

Usa Twine para subir de forma segura a TestPyPI (pruebas) y PyPI (producción).

06 // Versionado Semántico

MAJOR.MINOR.PATCH comunica la naturaleza de los cambios a tus usuarios.

Basado en "Learn Python Programming" // 4th Edition // Capítulo 16