Introduzione

Obiettivo : Accompagnare il lettore nella configurazione di un pipeline CI/CD minimale ma funzionante per un’applicazione Python, con un deployment automatico su PyPI.

L’automazione dei processi di sviluppo è diventata indispensabile nei progetti moderni. Un pipeline CI/CD ben progettato non solo permette di individuare le regressioni presto nel ciclo di sviluppo, ma anche di automatizzare completamente il processo di distribuzione.

In questo articolo, esploreremo come configurare una pipeline completa con GitHub Actions per un’applicazione Python, dall’integrazione continua (CI) al deployment continuo (CD) su PyPI.

Architettura della pipeline

Il nostro pipeline è composto da due workflows distinti:

  1. Pipeline CI: Eseguito su ogni push e pull request

  2. pipeline CD: Attivato solo durante le release di GitHub

ci cd overview

Configurazione della Pipeline di Integrazione Continua (CI)

Struttura del workflow CI

Il workflow CI è progettato per validare ogni contributo al codice. Ecco la sua configurazione 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

Analisi delle fasi CI

1. Trigger (on)

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

Il pipeline si attiva su : - Ogni push sul ramo`main` - Ogni pull request verso`main`

Questo approccio garantisce che il codice principale rimanga stabile e che ogni contributo venga convalidato prima dell'integrazione.

==== 2. Ambiente di esecuzione

----

runs-on: ubuntu-latest

Ubuntu Latest offre un buon compromesso tra prestazioni, costo e compatibilità per la maggior parte dei progetti Python.

==== 3. Checkout del Codice

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

L'azione`checkout@v4`recupera il codice sorgente del repository. La versione v4 apporta miglioramenti delle prestazioni e della sicurezza.

==== 4. Configurazione Python

----

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

L'utilizzo di`'3.x'`consente di utilizzare automaticamente l'ultima versione stabile di Python 3, semplificando la manutenzione.

==== 5. Installazione delle Dipendenze

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

Questa fase: - Aggiorna pip all'ultima versione - Installa le dipendenze del progetto - Aggiungi gli strumenti di sviluppo (linting e test)

==== 6. Linting con Ruff

----

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

**Ruff**è un linter Python ultra-veloce scritto in Rust. Combina le funzionalità di diversi strumenti (Flake8, Black, isort) in un unico strumento performante.

==== 7. Esecuzione dei test

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

Pytest esegue l'intera suite di test, garantendo che le modifiche non introducano regressioni.

== Configurazione del pipeline di distribuzione (CD)

=== Struttura del flusso di lavoro CD

Il workflow CD si attiva solo durante le release di GitHub e automatizza la pubblicazione su 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/*
----

=== Analisi delle fasi CD

==== 1. Scatto Rilascio

----

on: release: types: - published

Il pipeline CD si attiva solo alla pubblicazione di una release GitHub. Questo approccio assicura un controllo preciso dei deployment.

==== 2. Installazione degli Strumenti di Build

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

- **setuptools**: Strumenti di packaging Python - **wheel**: Formato di distribuzione Python moderno - **spago**: Strumento sicuro per caricare su PyPI

==== 3. Configurazione dell'autenticazione

----

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

L'autenticazione utilizza un token API PyPI memorizzato come segreto GitHub, più sicuro delle credenziali tradizionali.

==== 4. Build e Pubblicazione

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

- `sdist`: Crea una distribuzione sorgente - `bdist_wheel`: Crea una wheel (distribuzione binaria) - `twine upload`Pubblica le distribuzioni su PyPI

== Configurazione del pacchetto Python

=== Struttura di setup.py

Per far funzionare il pipeline, il tuo progetto deve includere un file`setup.py`</think>
:

[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",
        ],
    },
)
----

=== Punti chiave del setup.py

1. **Metadati**Nome, versione, autore, descrizione
2. **Dipendenze**Elenco dei pacchetti richiesti
3. **Punti di ingresso**: Comandi CLI esposti
4. **classificatori**: Metadati per PyPI

== Sicurezza con i GitHub Secrets

=== Configurazione del Token PyPI

1. **Creare un token API su PyPI** :

- Accedi a PyPI - Vai in Account Settings > API tokens - Crea un nuovo token con le autorizzazioni appropriate

1. **Aggiungere il segreto in GitHub** :

- Impostazioni del repository > Segreti e variabili > Azioni - Crea un nuovo segreto chiamato`PYPI_API_TOKEN` - Incolla il tuo token PyPI

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

actor Developer as dev
participant "Repository 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
----

== Workflow di Distribuzione Completa

=== Sequenza di distribuzione

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

actor Developer as dev
participant "Git locale" as git
participant "GitHub" as github
participant "GitHub Actions" as actions
participant PyPI
participant "Utenti finali" 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
----

=== Stati 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
----

== Buone Pratiche e Ottimizzazioni

=== 1. Gestione delle Versioni

Utilizza tag Git semantici :

----

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

=== 2. Test di matrice

Per testare su più versioni di Python:

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

=== 3. Cache delle Dipendenze

Accelerate i build con la cache :

----

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

=== 4. Ambienti di distribuzione

Utilizza gli ambienti GitHub per le distribuzioni controllate:

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

== Caso d'uso e architettura

=== Diagramma dei casi d'uso

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

left to right direction

actor "Sviluppatore" as dev
actor "GitHub Actions" as ga
actor "utente finale" as user

package "Sistema CI/CD" {
  usecase "Esegui il linting" as lint
  usecase "Esegui i test" as test
  usecase "Costruisci pacchetto" as build
  usecase "Pubblica su PyPI" as publish
  usecase "Crea Release" 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
----

=== Architettura del deployment

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

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

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

node "CI/CD Pipeline" {
  [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
----

== Monitoraggio e debugging

=== Log e Monitoraggio

GitHub Actions fornisce log dettagliati per ogni passaggio. Per il debug:

1. **Esamini i log**di ogni step
2. **Attiva il debug**con`ACTIONS_STEP_DEBUG`
3. **Utilizza gli artefatti**per salvare i file di build

----

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

=== Notifiche

Aggiungi notifiche Slack o email :

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

== Conclusione

L'implementazione di un pipeline CI/CD robusto con GitHub Actions trasforma radicalmente l'esperienza di sviluppo. Automatizzando il linting, i test e il deployment, tu:

- **Riduci gli errori**in produzione - **Accelerate i cicli**di sviluppo - **Migliorate la fiducia**nei vostri rilasci - **Facilita la collaborazione**in squadra

Questo pipeline può essere adattato a diversi tipi di progetti Python regolando gli strumenti di linting, i framework di test o le destinazioni di distribuzione.

L'investimento iniziale nella configurazione di questi flussi di lavoro è rapidamente ripagato dal guadagno di tempo e dalla riduzione degli errori manuali durante i rilasci.

== Risorse Complementari

- https://docs.github.com/en/actions[Documentazione GitHub Actions] - https://packaging.python.org/[Python Packaging Guide] - https://docs.pytest.org/[Documentazione Pytest] - https://docs.astral.sh/ruff/[Documentazione Ruff] - https://twine.readthedocs.io/[Documentazione Twine]

✅ Pipeline funzionale raggiunto ! Ora hai un semplice pipeline CI/CD che ti permette di automatizzare i test e di pubblicare il tuo pacchetto Python su PyPI direttamente da GitHub Actions.

Tuttavia, questo pipeline rimane volontariamente minimalista. Non copre ancora alcuni aspetti indispensabili in un contesto professionale:

Test di più versioni di Python

Analisi di sicurezza automatica,

Distribuzione progressiva tramite Test PyPI,

Monitoraggio e metriche della pipeline,

Automazione del versioning e integrazione delle buone pratiche moderne (pyproject.toml).

Nella prossima parte, passeremo al livello successivo. Imparerai a trasformare questo pipeline di base in una vera catena di distribuzione industriale, robusta e sicura, pronta per progetti Python di produzione.
----

Articoli correlati