Introducción

Objetivo : Acompañar al lector en la configuración de un pipeline CI/CD minimalista pero funcional para una aplicación Python, con un despliegue automático en PyPI.

La automatización de los procesos de desarrollo se ha vuelto indispensable en los proyectos modernos. Un pipeline CI/CD bien diseñado permite no solo detectar las regresiones temprano en el ciclo de desarrollo, sino también automatizar por completo el proceso de despliegue.

En este artículo, vamos a explorar cómo configurar una canalización completa con GitHub Actions para una aplicación Python, desde la integración continua (CI) hasta el despliegue continuo (CD) en PyPI.

Arquitectura del Pipeline

Nuestro pipeline se compone de dos flujos de trabajo distintos :

  1. Pipeline de CI: Ejecutado en cada push y pull request

  2. pipeline CD: desencadenado únicamente durante los releases de GitHub

ci cd overview

Configuración del Pipeline de Integración Continua (CI)

Estructura del flujo de trabajo CI

El flujo de trabajo CI está diseñado para validar cada contribución al código. Aquí está su configuración completa:

name: CI/CD Pipeline

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install ruff pytest pytest-mock

    - name: Run Linting (Ruff)
      run: ruff check .

    - name: Run Tests (Pytest)
      run: pytest

Análisis de los pasos CI

desencadenadores (on)

----
----
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
----

El pipeline se dispara en : - Cada push en la rama`main` - Cada pull request hacia`main`

Este enfoque garantiza que el código principal permanezca estable y que toda contribución sea validada antes de la integración.

==== 2. Entorno de Ejecución

----

runs-on: ubuntu-latest

Ubuntu Latest ofrece un buen compromiso entre rendimiento, costo y compatibilidad para la mayoría de los proyectos de Python.

==== 3. Extracción del código

```yaml
----
----
- name: Checkout code
  uses: actions/checkout@v4
----

la acción`checkout@v4`obtiene el código fuente del repositorio. La versión v4 aporta mejoras de rendimiento y de seguridad.

==== 4. Configuración de Python

----

- name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x'

El uso de`'3.x'`permite usar automáticamente la última versión estable de Python 3, simplificando el mantenimiento.

==== 5. Instalación de las dependencias

```yaml
----
----
- name: Install dependencies
  run: |
    python -m pip install --upgrade pip
    pip install -r requirements.txt
    pip install ruff pytest pytest-mock
----

Este paso: Actualiza pip a la última versión Instala las dependencias del proyecto - Añade las herramientas de desarrollo (linting y pruebas)

==== 6. Linting con Ruff

----

- name: Run Linting (Ruff) run: ruff check .

**Ruff**es un linter de Python ultra-rápido escrito en Rust. Combina las funcionalidades de varias herramientas (Flake8, Black, isort) en una única herramienta de alto rendimiento.

==== Ejecución de las pruebas

```yaml
----
----
- name: Run Tests (Pytest)
  run: pytest
----

Pytest ejecuta todo el conjunto de pruebas, garantizando que las modificaciones no introduzcan regresiones.

== Configuración del pipeline de despliegue (CD)

=== Estructura del Workflow CD

El flujo de trabajo CD se activa únicamente en las versiones de GitHub y automatiza la publicación en PyPI :

[source,yaml]
----
name: Publish to PyPI

on:
  release:
    types:
      - published

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install setuptools wheel twine

    - name: Build and publish
      env:
        TWINE_USERNAME: __token__
        TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
      run: |
        python setup.py sdist bdist_wheel
        twine upload dist/*
----

=== Análisis de las etapas CD

==== 1. Disparador Release

----

on: release: types: - published

El pipeline CD solo se activa cuando se publica una release de GitHub. Este enfoque garantiza un control preciso de los despliegues.

==== 2. Instalación de las Herramientas de compilación

```yaml
----
----
pip install setuptools wheel twine
----

- **setuptools**: Herramientas de empaquetado de Python - **rueda**: Formato moderno de distribución de Python - **hilo**: Herramienta segura para subir a PyPI

==== 3. Configuración de la Autenticación

----

env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}

La autenticación utiliza un token de API PyPI almacenado como secreto de GitHub, más seguro que las credenciales clásicas.

==== 4. Compilación y publicación

```yaml
----
----
run: |
  python setup.py sdist bdist_wheel
  twine upload dist/*
----

- `sdist`: Cree una distribución de código fuente - `bdist_wheel`: Crea una wheel (distribución binaria) - `twine upload`Publica las distribuciones en PyPI

== Configuración del paquete de Python

=== Estructura del setup.py

Para que el pipeline funcione, su proyecto debe incluir un archivo`setup.py` :

[source,python]
----
from setuptools import setup, find_packages

with open("README.adoc", "r", encoding="utf-8") as fh:
    long_description = fh.read()

setup(
    name="playlist-downloader",
    version="1.0.0",
    author="Votre Nom",
    author_email="[email protected]",
    description="CLI tool for managing YouTube playlists",
    long_description=long_description,
    long_description_content_type="text/plain",
    url="https://github.com/cheroliv/playlist-downloader",
    packages=find_packages(),
    classifiers=[
        "Development Status :: 4 - Beta",
        "Intended Audience :: Developers",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
        "Programming Language :: Python :: 3",
        "Programming Language :: Python :: 3.8",
        "Programming Language :: Python :: 3.9",
        "Programming Language :: Python :: 3.10",
        "Programming Language :: Python :: 3.11",
    ],
    python_requires=">=3.8",
    install_requires=[
        "typer>=0.9.0",
        "yt-dlp>=2023.1.6",
        "google-api-python-client>=2.70.0",
        "google-auth-oauthlib>=0.7.1",
        "pymonad>=2.4.0",
        "pyyaml>=6.0",
    ],
    entry_points={
        "console_scripts": [
            "playlist-downloader=cli:app",
        ],
    },
)
----

=== puntos clave de `setup.py

1. **Metadatos**nombre, version, autor, descripción
2. **Dependencias**Lista de los paquetes requeridos
3. **Puntos de entrada**: Comandos CLI expuestos
4. **Clasificadores**: Metadatos para PyPI

== Seguridad con los GitHub Secrets

=== Configuración del Token PyPI

1. **Crear un token API en PyPI** :

- Inicia sesión en PyPI - Vaya a Configuración de la cuenta > Tokens de API Cree un nuevo token con los permisos apropiados

1. **Agregar el secreto en GitHub**:

- Configuración del repositorio > Secretos y variables > Acciones - Creen un nuevo secreto llamado`PYPI_API_TOKEN` - Pega tu token PyPI

[plantuml, secrets-flow, svg]
----
@startuml
!theme plain

actor Developer as dev
participant "Repositorio GitHub" as repo
participant "GitHub Actions" as actions
participant PyPI

dev -> repo : Configure PYPI_API_TOKEN secret
repo -> actions : Trigger CD pipeline on release
actions -> actions : Access secret securely
actions -> PyPI : Authenticate with token
PyPI -> PyPI : Validate and publish package

note over actions, PyPI
  Token never exposed in logs
  Automatic rotation possible
end note

@enduml
----

== Flujo de Despliegue Completo

=== Secuencia de Despliegue

[plantuml, deployment-sequence, svg]
----
@startuml
!theme plain

actor Developer as dev
participant "Git local" as git
participant "GitHub" as github
participant "GitHub Actions" as actions
participant PyPI
participant "usuarios finales" as users

dev -> git : git tag v1.0.0
dev -> git : git push origin v1.0.0
git -> github : Push tag
dev -> github : Create release from tag
github -> actions : Trigger CD pipeline
actions -> actions : Checkout code
actions -> actions : Setup Python environment
actions -> actions : Install build tools
actions -> actions : Build distributions (sdist + wheel)
actions -> PyPI : Upload to PyPI with token
PyPI -> PyPI : Validate and publish
users -> PyPI : pip install playlist-downloader

note over dev, github
  Release creation can be automated
  or done manually through GitHub UI
end note

@enduml
----

=== Estados del Pipeline

[plantuml, pipeline-states, svg]
----
@startuml
!theme plain

[*] --> Idle

Idle --> CI_Running : Push/PR created
CI_Running --> CI_Success : All checks pass
CI_Running --> CI_Failed : Linting/Tests fail
CI_Success --> Idle : Merge completed
CI_Failed --> Idle : Fix and retry

Idle --> CD_Running : Release published
CD_Running --> CD_Success : Package published
CD_Running --> CD_Failed : Build/Upload error
CD_Success --> Idle : Package available on PyPI
CD_Failed --> Idle : Fix and retry release

note on link #red : Blocks merge
note on link #green : Allows deployment

@enduml
----

== Buenas prácticas y optimizaciones

=== 1. Gestión de versiones

Utilice etiquetas Git semánticas :

----

git tag -a v1.2.3 -m "Release version 1.2.3" git push origin v1.2.3

=== 2. Pruebas de Matriz

Para probar en varias versiones de Python :

```yaml
----
----
strategy:
  matrix:
    python-version: [3.8, 3.9, "3.10", "3.11"]
----

=== 3. Caché de Dependencias

Acelere los builds con el caché :

----

- name: Cache pip dependencies uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}

=== 4. Entornos de despliegue

Utilice los entornos de GitHub para despliegues controlados :

```yaml
----
----
jobs:
  deploy:
    environment: production
    runs-on: ubuntu-latest
----

== Caso de Uso y Arquitectura

=== Diagrama de Casos de Uso

[plantuml, use-cases, svg]
----
@startuml
!theme plain

left to right direction

actor "Desarrollador" as dev
actor "GitHub Actions" as ga
actor "Usuario final" as user

package "Sistema CI/CD" {
  usecase "Ejecutar Linting" as lint
  usecase "Ejecutar pruebas" as test
  usecase "Paquete de construcción" as build
  usecase "Publicar en PyPI" as publish
  usecase "Crear versión" as release
}

dev --> lint : Pushes code
dev --> test : Pushes code
dev --> release : Creates release
ga --> build : On release trigger
ga --> publish : After successful build
user --> publish : Downloads package

lint .> test : triggers
test .> build : on success
build .> publish : on success

@enduml
----

=== Arquitectura de la Implementación

[plantuml, deployment-architecture, svg]
----
@startuml
!theme plain

cloud "GitHub" {
  [Source Repository]
  [GitHub Actions]
  [Secrets Store]
}

cloud "PyPI" {
  [Package Registry]
  [Distribution Files]
}

node "pipeline de CI/CD" {
  [Linting]
  [Testing]
  [Building]
  [Publishing]
}

[Source Repository] --> [GitHub Actions] : Triggers
[GitHub Actions] --> [Linting]
[Linting] --> [Testing]
[Testing] --> [Building]
[Building] --> [Publishing]
[Publishing] --> [Package Registry] : Uploads
[Secrets Store] --> [Publishing] : Provides token

note as N1
  Secure token-based
  authentication
end note

[Secrets Store] .. N1

@enduml
----

== Monitoreo y depuración

=== Logs y monitoreo

GitHub Actions proporciona registros detallados para cada paso. Para depurar:

1. **Examine los registros**de cada paso
2. **Active el debug**con`ACTIONS_STEP_DEBUG`
3. **Utiliza artefactos**para guardar los archivos de compilación

----

- name: Upload build artifacts uses: actions/upload-artifact@v3 if: failure() with: name: build-logs path: build/

=== Notificaciones

Añade notificaciones de Slack o correo electrónico :

```yaml
----
----
- name: Notify on failure
  if: failure()
  uses: 8398a7/action-slack@v3
  with:
    status: ${{ job.status }}
    webhook_url: ${{ secrets.SLACK_WEBHOOK }}
----

== Conclusión

La implementación de un pipeline CI/CD robusto con GitHub Actions transforma radicalmente la experiencia de desarrollo. Al automatizar el linting, las pruebas y el despliegue, usted:

- **Reduce los errores**en producción - **Acelere los ciclos**de desarrollo - **Mejore la confianza**en sus releases - **Facilita la colaboración**en equipo

Este pipeline puede adaptarse a diferentes tipos de proyectos Python ajustando las herramientas de linting, los frameworks de prueba o los destinos de despliegue.

La inversión inicial en la configuración de estos flujos de trabajo se rentabiliza rápidamente gracias al ahorro de tiempo y la reducción de los errores manuales durante los despliegues.

== Recursos Complementarios

- https://docs.github.com/en/actions[Documentación GitHub Actions] - https://packaging.python.org/[Guía de empaquetado de Python] - https://docs.pytest.org/[Documentación Pytest] - https://docs.astral.sh/ruff/[Documentación Ruff] - https://twine.readthedocs.io/[Documentación Twine]

✅ Pipeline funcional alcanzado! Ahora tienes un pipeline CI/CD simple que te permite automatizar tus pruebas y publicar tu paquete Python en PyPI directamente desde GitHub Actions.

Sin embargo, este pipeline sigue siendo intencionalmente minimalista. Aún no cubre algunos aspectos indispensables en un contexto profesional:

Pruebas multi-versiones de Python,

Análisis de seguridad automático,

Despliegue progresivo a través de Test PyPI,

Monitoreo y métricas del pipeline,

Automatización del versionado y la integración de las mejores prácticas modernas (pyproject.toml).

En la próxima parte, pasaremos al siguiente nivel. Aprenderás a transformar este pipeline básico en una verdadera cadena de despliegue industrial, robusta y segura, lista para proyectos de Python de producción.
----

Articles connexes