Construya paquetes de Python: Guía del desarrollo hasta PyPI

Maestra el empaquetado de Python desde el código hasta la implementación en PyPI

Índice

Empaquetado de Python ha evolucionado significativamente, con herramientas modernas y estándares que hacen más fácil que nunca distribuir tu código.

Esta guía te lleva paso a paso por el proceso de construcción de paquetes profesionales de Python y su publicación en PyPI. Si eres nuevo en Python o necesitas una referencia rápida, consulta nuestro Hoja de trucos de Python para ponerte al día con los fundamentos de Python.

Paquetes de Python

¿Por qué empaquetar tu código de Python?

Empaquetar tu proyecto de Python ofrece múltiples beneficios:

  • Reutilización: Comparte código entre múltiples proyectos sin copiar y pegar
  • Distribución: Permite que otros instalen tu código con un simple pip install
  • Gestión de dependencias: Especifica claramente y gestiona las dependencias
  • Versionado: Rastrea liberaciones y mantiene la compatibilidad hacia atrás
  • Estándares profesionales: Sigue las mejores prácticas de la comunidad de Python
  • Documentación: Estructura que fomenta una documentación y pruebas adecuadas

Estructura moderna de paquetes de Python

Un paquete bien organizado sigue esta estructura:

my-package/
├── src/
│   └── mypackage/
│       ├── __init__.py
│       ├── core.py
│       └── utils.py
├── tests/
│   ├── __init__.py
│   └── test_core.py
├── docs/
│   └── index.md
├── .github/
│   └── workflows/
│       └── publish.yml
├── pyproject.toml
├── README.md
├── LICENSE
└── .gitignore

La src-layout es ahora preferida sobre la estructura plana porque:

  • Evita importaciones accidentales de código no instalado durante el desarrollo
  • Hace más confiables las pruebas al obligar la instalación
  • Separa claramente el código fuente de otros archivos del proyecto

Al organizar la estructura interna de tu paquete, considera aplicar principios de arquitectura limpia para hacer tu código más mantenible y testable. Nuestra guía sobre Patrones de diseño de Python para arquitectura limpia cubre los principios SOLID, la inyección de dependencias y patrones de arquitectura en capas que funcionan excelente con paquetes de Python.

El archivo pyproject.toml: configuración moderna

pyproject.toml (PEP 518, 621) es el estándar moderno para la configuración de proyectos de Python, reemplazando al antiguo setup.py:

[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "mypackage"
version = "0.1.0"
description = "Un paquete fantástico de Python"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
    {name = "Tu nombre", email = "tú@example.com"}
]
keywords = ["ejemplo", "tutorial", "paquete"]
classifiers = [
    "Estado de desarrollo :: 4 - Beta",
    "Público objetivo :: Desarrolladores",
    "Licencia :: Aprobada por OSI :: Licencia MIT",
    "Lenguaje de programación :: Python :: 3",
    "Lenguaje de programación :: Python :: 3.8",
    "Lenguaje de programación :: Python :: 3.9",
    "Lenguaje de programación :: Python :: 3.10",
    "Lenguaje de programación :: Python :: 3.11",
    "Lenguaje de programación :: Python :: 3.12",
]
dependencies = [
    "requests>=2.28.0",
    "click>=8.1.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.4.0",
    "black>=23.0.0",
    "flake8>=6.0.0",
    "mypy>=1.5.0",
]
docs = [
    "sphinx>=7.0.0",
    "sphinx-rtd-theme>=1.3.0",
]

[project.urls]
Homepage = "https://github.com/yourusername/mypackage"
Documentation = "https://mypackage.readthedocs.io"
Repository = "https://github.com/yourusername/mypackage"
Issues = "https://github.com/yourusername/mypackage/issues"

[project.scripts]
mypackage-cli = "mypackage.cli:main"

[tool.setuptools.packages.find]
where = ["src"]

Secciones clave de configuración

  1. build-system: Especifica el backend de construcción (setuptools, hatchling, poetry-core, flit-core)
  2. project: Metadatos y dependencias principales
  3. project.optional-dependencies: Dependencias adicionales para características (dev, docs, pruebas)
  4. project.scripts: Puntos de entrada para la línea de comandos
  5. tool.*: Configuración específica de herramientas (black, pytest, mypy, etc.)

Elegir un backend de construcción

Setuptools (opción estándar)

La opción más utilizada y compatible:

[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.build_meta"

Ventajas: Compatibilidad universal, características extensas, ecosistema grande Desventajas: Configuración más verbosa, más lento que alternativas modernas

Hatchling (moderno y rápido)

Backend moderno y liviano:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

Ventajas: Construcciones rápidas, configuración simple, buenos valores predeterminados Desventajas: Menos plugins que setuptools

Poetry (todo en uno)

Gestión completa de dependencias y entornos:

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "mypackage"
version = "0.1.0"
description = "Un paquete fantástico de Python"
authors = ["Tu nombre <tú@example.com>"]

[tool.poetry.dependencies]
python = "^3.8"
requests = "^2.28.0"

Ventajas: Resolución de dependencias, archivos de bloqueo, flujo de trabajo integrado Desventajas: Flujo de trabajo diferente, superficie de herramientas más grande

Flit (minimalista)

Herramienta simple para paquetes simples:

[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"

Ventajas: Muy simple, configuración mínima Desventajas: Pocas características para paquetes complejos

Construyendo tu paquete

Instalar herramientas de construcción

# Instalar la herramienta moderna de construcción
pip install build

# O para Poetry
pip install poetry

# O para Hatchling
pip install hatch

Construir archivos de distribución

Usando el paquete build (funciona con cualquier backend):

# Limpiar construcciones previas
rm -rf dist/ build/ *.egg-info

# Construir distribución de código fuente y rueda
python -m build

# Esto crea:
# dist/mypackage-0.1.0.tar.gz    (distribución de código fuente)
# dist/mypackage-0.1.0-py3-none-any.whl  (rueda)

Usando Poetry:

poetry build

Usando Hatch:

hatch build

Entendiendo los formatos de distribución

Distribución de código fuente (sdist) - .tar.gz

  • Contiene código fuente y instrucciones de construcción
  • Los usuarios de pip lo construyen durante la instalación
  • Incluye pruebas, documentación y otros archivos de desarrollo

Rueda - .whl

  • Distribución binaria preconstruida
  • Instalación rápida (sin paso de construcción)
  • Plataforma específica o puramente en Python
  • Formato recomendado para la distribución

Publicar en PyPI

Método 1: Subida manual con Twine (tradicional)

# Instalar twine
pip install twine

# Verificar el paquete antes de subir
twine check dist/*

# Subir primero a TestPyPI (recomendado)
twine upload --repository testpypi dist/*

# Probar instalación desde TestPyPI
pip install --index-url https://test.pypi.org/simple/ mypackage

# Subir a PyPI de producción
twine upload dist/*

Configurar credenciales en ~/.pypirc:

[distutils]
index-servers =
    pypi
    testpypi

[pypi]
username = __token__
password = pypi-AgEIcHlwaS5vcmc...

[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = pypi-AgENdGVzdC5weXBpLm9yZw...

Método 2: Publicación confiable (recomendado para CI/CD)

La publicación confiable utiliza OpenID Connect (OIDC) para autenticarse desde plataformas de CI/CD sin almacenar tokens.

Configuración en PyPI:

  1. Ir a la configuración del proyecto en PyPI
  2. Navegar a la sección “Publicación”
  3. Añadir un nuevo “publicador pendiente”
  4. Configurar:
    • Propietario: Tu nombre de usuario/organización de GitHub
    • Repositorio: Nombre del repositorio
    • Workflow: publish.yml
    • Entorno: release (opcional pero recomendado)

Flujo de trabajo de GitHub Actions (.github/workflows/publish.yml):

name: Publicar en PyPI

on:
  release:
    types: [published]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Configurar Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: Instalar dependencias de construcción
        run: |
          python -m pip install --upgrade pip
          pip install build          
      
      - name: Construir paquete
        run: python -m build
      
      - name: Almacenar paquetes de distribución
        uses: actions/upload-artifact@v3
        with:
          name: python-package-distributions
          path: dist/

  publish:
    needs: build
    runs-on: ubuntu-latest
    environment: release
    permissions:
      id-token: write
    
    steps:
      - name: Descargar distribuciones
        uses: actions/download-artifact@v3
        with:
          name: python-package-distributions
          path: dist/
      
      - name: Publicar en PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

Ventajas:

  • No hay tokens de API que gestionar o proteger
  • Autenticación automática mediante OIDC
  • Mayor seguridad mediante reglas de protección de entornos
  • Registro de auditoría de todas las publicaciones

Buenas prácticas

1. Gestión de versiones

Usa versionado semántico (SemVer): MAJOR.MINOR.PATCH

# Instalar herramienta de incremento de versiones
pip install bump2version

# Incrementar versión
bump2version patch  # 0.1.0 -> 0.1.1
bump2version minor  # 0.1.1 -> 0.2.0
bump2version major  # 0.2.0 -> 1.0.0

Configurar en .bumpversion.cfg:

[bumpversion]
current_version = 0.1.0
commit = True
tag = True

[bumpversion:file:pyproject.toml]
search = version = "{current_version}"
replace = version = "{new_version}"

[bumpversion:file:src/mypackage/__init__.py]
search = __version__ = "{current_version}"
replace = __version__ = "{new_version}"

2. Incluir archivos esenciales

README.md: Descripción clara del proyecto, instalación, ejemplos de uso LICENSE: Elige una licencia adecuada (MIT, Apache 2.0, GPL, etc.) CHANGELOG.md: Documenta los cambios entre versiones .gitignore: Excluye artefactos de construcción, cachés, entornos virtuales

3. Pruebas completas

# Instalar dependencias de prueba
pip install pytest pytest-cov

# Ejecutar pruebas con cobertura
pytest --cov=mypackage tests/

# Generar informe de cobertura
pytest --cov=mypackage --cov-report=html tests/

Configurar en pyproject.toml:

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = "--strict-markers --cov=mypackage"

4. Herramientas de calidad del código

# Formateo
black src/ tests/

# Linting
flake8 src/ tests/

# Comprobación de tipos
mypy src/

# Ordenamiento de importaciones
isort src/ tests/

Integrar en hooks pre-commit (.pre-commit-config.yaml):

repos:
  - repo: https://github.com/psf/black
    rev: 23.9.1
    hooks:
      - id: black
  
  - repo: https://github.com/pycqa/flake8
    rev: 6.1.0
    hooks:
      - id: flake8
  
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.5.1
    hooks:
      - id: mypy

5. Documentación

Usa Sphinx para la documentación:

# Instalar Sphinx
pip install sphinx sphinx-rtd-theme

# Inicializar docs
cd docs
sphinx-quickstart

# Construir documentación
make html

O usa MkDocs para documentación basada en Markdown más simple:

pip install mkdocs mkdocs-material
mkdocs new .
mkdocs serve

6. Integración continua

Probar tus paquetes de Python en diferentes plataformas y entornos es crucial para la confiabilidad. Para obtener información sobre el rendimiento de Python en diferentes escenarios de despliegue, consulta nuestra comparación de Rendimiento de AWS Lambda: JavaScript vs Python vs Golang, que explora cómo se comporta Python en entornos sin servidor.

Flujo de trabajo completo de CI/CD (.github/workflows/ci.yml):

name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Configurar Python
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
      
      - name: Instalar dependencias
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[dev]"          
      
      - name: Ejecutar pruebas
        run: pytest --cov=mypackage --cov-report=xml
      
      - name: Subir cobertura
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage.xml
  
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Configurar Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: Instalar dependencias
        run: |
          pip install black flake8 mypy          
      
      - name: Verificación de formato con Black
        run: black --check src/ tests/
      
      - name: Flake8
        run: flake8 src/ tests/
      
      - name: MyPy
        run: mypy src/

Problemas comunes y soluciones

Problema 1: Errores de importación después de la instalación

Problema: El paquete está instalado pero fallan las importaciones

Solución: Asegúrate de tener una estructura de paquete adecuada con archivos __init__.py y configuración correcta en pyproject.toml:

[tool.setuptools.packages.find]
where = ["src"]
include = ["mypackage*"]

Problema 2: Dependencias faltantes

Problema: El paquete se instala pero falla en tiempo de ejecución debido a dependencias faltantes

Solución: Declara todas las dependencias de tiempo de ejecución en pyproject.toml:

[project]
dependencies = [
    "requests>=2.28.0",
    "click>=8.1.0",
]

Problema 3: Conflictos de versiones

Problema: El paquete funciona en desarrollo pero falla en producción

Solución: Usa entornos virtuales y especifica versiones mínimas:

# Crear entorno aislado
python -m venv .venv
source .venv/bin/activate  # En Windows: .venv\Scripts\activate

# Instalar en modo editable para desarrollo
pip install -e ".[dev]"

Problema 4: Tamaño grande del paquete

Problema: El paquete tarda demasiado en descargarse/instalarse

Solución: Excluye archivos innecesarios usando MANIFEST.in:

include README.md
include LICENSE
include pyproject.toml
recursive-include src *.py
recursive-exclude * __pycache__
recursive-exclude * *.py[co]
recursive-exclude tests *
recursive-exclude docs *

Problema 5: Problemas específicos de plataforma

Problema: El paquete funciona en un sistema operativo pero falla en otro

Solución: Prueba en múltiples plataformas usando matrices de construcción de CI/CD (ver ejemplo de CI anterior)

Lista de verificación para publicar

Antes de publicar tu paquete en PyPI, verifica:

  • Todos los tests pasan en diferentes versiones de Python
  • El código está formateado y linteado
  • README.md está completo con ejemplos
  • Archivo LICENSE está incluido
  • El número de versión está actualizado
  • CHANGELOG.md está actualizado
  • Las dependencias están especificadas correctamente
  • El paquete se construye sin errores (python -m build)
  • El paquete se prueba en TestPyPI
  • La documentación está actualizada
  • El repositorio de Git está etiquetado con la versión
  • Se crea una publicación de GitHub (para publicación confiable)

Temas avanzados

Puntos de entrada y herramientas de línea de comandos

Crear interfaces de línea de comandos:

[project.scripts]
mypackage = "mypackage.cli:main"

Implementación en src/mypackage/cli.py:

import click

@click.command()
@click.option('--nombre', default='Mundo', help='Nombre a saludar')
def main(nombre):
    """Ejemplo de herramienta CLI simple"""
    click.echo(f'Hola, {nombre}!')

if __name__ == '__main__':
    main()

Para un ejemplo real de creación de paquetes de Python que interactúan con APIs externas, consulta nuestra guía sobre Integrar Ollama con Python, que demuestra la construcción de clientes de Python con integraciones tanto de API REST como de bibliotecas oficiales.

Sistemas de plugins

Habilitar descubrimiento de plugins:

[project.entry-points."mypackage.plugins"]
plugin_a = "mypackage_plugin_a:PluginA"

Extensiones C

Para código crítico de rendimiento:

[tool.setuptools.ext-modules]
name = "mypackage._speedups"
sources = ["src/mypackage/_speedups.c"]

Paquetes de espacio de nombres

Para paquetes distribuidos bajo un espacio de nombres común:

[tool.setuptools.packages.find]
where = ["src"]
include = ["company.*"]

[tool.setuptools.package-data]
"company.mypackage" = ["py.typed"]

Herramientas y recursos útiles

Herramientas de desarrollo de paquetes

  • cookiecutter: Plantillas de proyectos para paquetes de Python
  • tox: Automatización de pruebas en múltiples versiones de Python
  • nox: Automatización de pruebas flexible (alternativa a tox)
  • pre-commit: Marco de hooks de Git para la calidad del código
  • commitizen: Estandarizar mensajes de commit y versionado

Plataformas de documentación

  • Read the Docs: Hosting de documentación gratuito
  • GitHub Pages: Anfitrión de documentación de MkDocs o Sphinx
  • pdoc: Generador de documentación de API simple

Registros de paquetes

  • PyPI: El índice oficial de paquetes de Python (pypi.org)
  • TestPyPI: Entorno de prueba (test.pypi.org)
  • Anaconda.org: Distribución de paquetes de Conda
  • GitHub Packages: Hosting de paquetes privados

Monitoreo y análisis

  • PyPI Stats: Estadísticas de descargas para paquetes
  • Libraries.io: Monitoreo de dependencias y alertas
  • Snyk: Escaneo de vulnerabilidades de seguridad
  • Dependabot: Actualizaciones automatizadas de dependencias

Conclusión

El empaquetado moderno de Python ha evolucionado para ser amigable con los desarrolladores con estándares como pyproject.toml, poderosos backends de construcción y publicación segura mediante Publicación confiable. Siguiendo esta guía, puedes crear paquetes profesionales y mantenibles de Python que sirvan eficazmente a tus usuarios.

Puntos clave:

  1. Usa pyproject.toml para todos los nuevos proyectos
  2. Elige el backend de construcción adecuado para tus necesidades
  3. Implementa pruebas automatizadas y CI/CD
  4. Usa Publicación confiable para despliegues seguros sin tokens
  5. Sigue versionado semántico y mantén los registros de cambios
  6. Proporciona documentación y ejemplos completos

El ecosistema de empaquetado de Python continúa mejorando con mejoras en herramientas, estándares y características de seguridad. Mantén actualizado con las Propuestas de Mejora de Python (PEPs) y las mejores prácticas de la comunidad para mantener tus paquetes modernos y mantenibles.

Enlaces útiles

Otros artículos útiles en este sitio