Costruisci pacchetti Python: Guida dallo sviluppo a PyPI

Padroneggiare l'imballaggio Python dal codice al deployment su PyPI

Indice

Packaging Python ha evoluto significativamente, con strumenti moderni e standard che rendono più facile mai distribuire il tuo codice.

Questo tutorial ti guida attraverso la creazione di pacchetti Python professionali e la loro pubblicazione su PyPI. Se sei nuovo a Python o hai bisogno di un riferimento rapido, consulta il nostro Python Cheatsheet per aggiornarti sui fondamenti di Python.

Python Packages

Perché Pacchettizzare il Tuo Codice Python?

Pacchettizzare il tuo progetto Python offre diversi vantaggi:

  • Riusabilità: Condividi il codice tra diversi progetti senza copiare e incollare
  • Distribuzione: Permette ad altri di installare il tuo codice con un semplice pip install
  • Gestione delle dipendenze: Specifica chiaramente e gestisci le dipendenze
  • Versioning: Traccia le release e mantieni la compatibilità all’indietro
  • Standard Professionali: Segui le best practice della comunità Python
  • Documentazione: La struttura incoraggia una corretta documentazione e test

Struttura Moderna di un Pacchetto Python

Un pacchetto ben organizzato segue questa struttura:

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 è ora preferibile rispetto alla layout piatta perché:

  • Previene importazioni accidentali di codice non installato durante lo sviluppo
  • Rende i test più affidabili costringendo all’installazione
  • Separa chiaramente il codice sorgente da altri file del progetto

Quando organizziamo la struttura interna del pacchetto, considera l’applicazione di principi di architettura pulita per rendere il tuo codice più mantenibile e testabile. La nostra guida su Python Design Patterns for Clean Architecture copre i principi SOLID, l’iniezione di dipendenze e i pattern di architettura a strati che funzionano eccellentemente con i pacchetti Python.

Il File pyproject.toml: Configurazione Moderna

pyproject.toml (PEP 518, 621) è lo standard moderno per la configurazione dei progetti Python, sostituendo l’obsoleto setup.py:

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

[project]
name = "mypackage"
version = "0.1.0"
description = "A fantastic Python package"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
    {name = "Your Name", email = "you@example.com"}
]
keywords = ["example", "tutorial", "package"]
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.8",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: 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"]

Sezioni Principali di Configurazione

  1. build-system: Specifica il backend di costruzione (setuptools, hatchling, poetry-core, flit-core)
  2. project: Metadati principali e dipendenze
  3. project.optional-dependencies: Dipendenze extra per funzionalità aggiuntive (dev, docs, testing)
  4. project.scripts: Punti di ingresso per il comando della riga di comando
  5. tool.*: Configurazione specifica per gli strumenti (black, pytest, mypy, ecc.)

Scegliere un Backend di Costruzione

Setuptools (Scelta Standard)

L’opzione più utilizzata e compatibile:

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

Vantaggi: Compatibilità universale, funzionalità estese, ecosistema grande Svantaggi: Configurazione più verbosa, più lenta rispetto alle alternative moderne

Hatchling (Modern e Veloce)

Backend moderno e leggero:

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

Vantaggi: Costruzioni veloci, configurazione semplice, buoni valori di default Svantaggi: Meno plugin rispetto a setuptools

Poetry (Completo)

Gestione completa delle dipendenze e dell’ambiente:

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

[tool.poetry]
name = "mypackage"
version = "0.1.0"
description = "A fantastic Python package"
authors = ["Your Name <you@example.com>"]

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

Vantaggi: Risoluzione delle dipendenze, file di blocco, workflow integrato Svantaggi: Workflow diverso, superficie strumenti più grande

Flit (Minimalista)

Strumento semplice per pacchetti semplici:

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

Vantaggi: Estremamente semplice, configurazione minima Svantaggi: Funzionalità limitate per pacchetti complessi

Costruire il Tuo Pacchetto

Installare gli Strumenti di Costruzione

# Installare lo strumento moderno di costruzione
pip install build

# Oppure per Poetry
pip install poetry

# Oppure per Hatchling
pip install hatch

Costruire i File di Distribuzione

Utilizzando il pacchetto build (funziona con qualsiasi backend):

# Pulire le costruzioni precedenti
rm -rf dist/ build/ *.egg-info

# Costruire la distribuzione sorgente e la wheel
python -m build

# Questo crea:
# dist/mypackage-0.1.0.tar.gz    (distribuzione sorgente)
# dist/mypackage-0.1.0-py3-none-any.whl  (wheel)

Utilizzando Poetry:

poetry build

Utilizzando Hatch:

hatch build

Comprendere i Formati di Distribuzione

Distribuzione Sorgente (sdist) - .tar.gz

  • Contiene il codice sorgente e le istruzioni di costruzione
  • I utenti di pip lo costruiscono durante l’installazione
  • Include test, documentazione e altri file per lo sviluppo

Wheel - .whl

  • Distribuzione binaria pre-costruita
  • Installazione veloce (nessun passo di costruzione)
  • Specifica della piattaforma o puramente in Python
  • Formato consigliato per la distribuzione

Pubblicare su PyPI

Metodo 1: Caricamento Manuale con Twine (Tradizionale)

# Installare twine
pip install twine

# Controllare il pacchetto prima di caricare
twine check dist/*

# Caricare prima su TestPyPI (consigliato)
twine upload --repository testpypi dist/*

# Testare l'installazione da TestPyPI
pip install --index-url https://test.pypi.org/simple/ mypackage

# Caricare su PyPI produttivo
twine upload dist/*

Configurare le credenziali in ~/.pypirc:

[distutils]
index-servers =
    pypi
    testpypi

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

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

Metodo 2: Pubblicazione Trasparente (Consigliato per CI/CD)

La Pubblicazione Trasparente utilizza OpenID Connect (OIDC) per autenticare da piattaforme CI/CD senza conservare i token.

Configurazione su PyPI:

  1. Vai alle impostazioni del tuo progetto PyPI
  2. Naviga nella sezione “Pubblicazione”
  3. Aggiungi un nuovo “publisher in attesa”
  4. Configura:
    • Proprietario: Il tuo username/organizzazione GitHub
    • Repository: Nome del repository
    • Workflow: publish.yml
    • Ambiente: release (opzionale ma consigliato)

Workflow GitHub Actions (.github/workflows/publish.yml):

name: Publish to PyPI

on:
  release:
    types: [published]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: Install build dependencies
        run: |
          python -m pip install --upgrade pip
          pip install build          
      
      - name: Build package
        run: python -m build
      
      - name: Store distribution packages
        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: Download distributions
        uses: actions/download-artifact@v3
        with:
          name: python-package-distributions
          path: dist/
      
      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

Vantaggi:

  • Nessun token API da gestire o proteggere
  • Autenticazione automatica tramite OIDC
  • Sicurezza migliorata tramite regole di protezione dell’ambiente
  • Tracciamento delle pubblicazioni

Buone Pratiche

1. Gestione delle Versioni

Utilizza la versioning semantica (SemVer): MAJOR.MINOR.PATCH

# Installa lo strumento per incrementare le versioni
pip install bump2version

# Incrementa la versione
bump2version patch  # 0.1.0 -> 0.1.1
bump2version minor  # 0.1.1 -> 0.2.0
bump2version major  # 0.2.0 -> 1.0.0

Configura in .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. Includi File Essenziali

README.md: Descrizione chiara del progetto, installazione, esempi di utilizzo LICENSE: Scegli la licenza appropriata (MIT, Apache 2.0, GPL, ecc.) CHANGELOG.md: Documenta le modifiche tra le versioni .gitignore: Escludi artefatti di costruzione, cache, ambienti virtuali

3. Test Completi

# Installa le dipendenze per i test
pip install pytest pytest-cov

# Esegui i test con il coverage
pytest --cov=mypackage tests/

# Genera il report del coverage
pytest --cov=mypackage --cov-report=html tests/

Configura in pyproject.toml:

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

4. Strumenti per la Qualità del Codice

# Formattazione
black src/ tests/

# Linting
flake8 src/ tests/

# Controllo del tipo
mypy src/

# Ordinamento delle importazioni
isort src/ tests/

Integra nei hook 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. Documentazione

Utilizza Sphinx per la documentazione:

# Installa Sphinx
pip install sphinx sphinx-rtd-theme

# Inizializza la documentazione
cd docs
sphinx-quickstart

# Costruisci la documentazione
make html

O utilizza MkDocs per documentazioni basate su Markdown:

pip install mkdocs mkdocs-material
mkdocs new .
mkdocs serve

6. Integrazione Continua

Testare i pacchetti Python su diverse piattaforme e ambienti è cruciale per la affidabilità. Per ulteriori informazioni sulle prestazioni di Python in diversi scenari di distribuzione, vedi la nostra comparazione di AWS Lambda performance: JavaScript vs Python vs Golang, che esplora come Python si comporta in ambienti serverless.

Flusso di lavoro completo 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: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
      
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[dev]"          
      
      - name: Run tests
        run: pytest --cov=mypackage --cov-report=xml
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage.xml
  
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: Install dependencies
        run: |
          pip install black flake8 mypy          
      
      - name: Black formatting check
        run: black --check src/ tests/
      
      - name: Flake8
        run: flake8 src/ tests/
      
      - name: MyPy
        run: mypy src/

Problemi Comuni e Soluzioni

Problema 1: Errori di Importazione Dopo l’Installazione

Problema: Il pacchetto è installato ma gli import non funzionano

Soluzione: Assicurati di avere una struttura del pacchetto corretta con i file __init__.py e la configurazione corretta in pyproject.toml:

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

Problema 2: Dipendenze Mancanti

Problema: Il pacchetto è installato ma fallisce all’esecuzione a causa di dipendenze mancanti

Soluzione: Dichiarare tutte le dipendenze necessarie in pyproject.toml:

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

Problema 3: Conflitti di Versione

Problema: Il pacchetto funziona nello sviluppo ma fallisce in produzione

Soluzione: Utilizza ambienti virtuali e specifica le versioni minime:

# Crea un ambiente isolato
python -m venv .venv
source .venv/bin/activate  # Su Windows: .venv\Scripts\activate

# Installa in modalità editabile per lo sviluppo
pip install -e ".[dev]"

Problema 4: Dimensione del Pacchetto Grande

Problema: Il pacchetto richiede troppo tempo per il download/installazione

Soluzione: Escludi i file non necessari utilizzando 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: Problemi Specifici per la Piattaforma

Problema: Il pacchetto funziona su un OS ma fallisce su un altro

Soluzione: Testa su diverse piattaforme utilizzando i build matrix CI/CD (vedi esempio di CI sopra)

Checklist di Pubblicazione

Prima di pubblicare il tuo pacchetto su PyPI, verifica:

  • Tutti i test passano su diverse versioni di Python
  • Il codice è formattato e linterizzato
  • README.md è completo con esempi
  • File LICENSE è incluso
  • Il numero di versione è aggiornato
  • CHANGELOG.md è aggiornato
  • Le dipendenze sono specificate correttamente
  • Il pacchetto si costruisce senza errori (python -m build)
  • Il pacchetto è testato su TestPyPI
  • La documentazione è aggiornata
  • Il repository Git è contrassegnato con la versione
  • È stato creato un rilascio GitHub (per la pubblicazione trasparente)

Argomenti Avanzati

Entry Points e Strumenti CLI

Creare interfacce della riga di comando:

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

Implementazione in src/mypackage/cli.py:

import click

@click.command()
@click.option('--name', default='World', help='Name to greet')
def main(name):
    """Simple CLI tool example"""
    click.echo(f'Hello, {name}!')

if __name__ == '__main__':
    main()

Per un esempio reale di creazione di pacchetti Python che interagiscono con API esterne, consulta la nostra guida su Integrating Ollama with Python, che dimostra la costruzione di client Python con entrambe le integrazioni API REST e la libreria ufficiale.

Sistemi di Plugin

Abilita la scoperta dei plugin:

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

Estensioni C

Per il codice critico per le prestazioni:

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

Pacchetti Namespace

Per i pacchetti distribuiti sotto un namespace comune:

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

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

Strumenti e Risorse Utili

Strumenti per lo Sviluppo dei Pacchetti

  • cookiecutter: Template per progetti Python
  • tox: Automazione dei test su diverse versioni di Python
  • nox: Automazione flessibile dei test (alternativa a tox)
  • pre-commit: Framework per gli hook di Git per la qualità del codice
  • commitizen: Standardizza i messaggi di commit e la versioning

Piattaforme di Documentazione

  • Read the Docs: Hosting gratuito per la documentazione
  • GitHub Pages: Host MkDocs o Sphinx documentazione
  • pdoc: Generatore semplice di documentazione API

Registri dei Pacchetti

  • PyPI: L’indice Python ufficiale (pypi.org)
  • TestPyPI: Ambiente di test (test.pypi.org)
  • Anaconda.org: Distribuzione di pacchetti Conda
  • GitHub Packages: Hosting di pacchetti privati

Monitoraggio e Analisi

  • PyPI Stats: Statistiche di download per i pacchetti
  • Libraries.io: Monitoraggio delle dipendenze e allarmi
  • Snyk: Scansione delle vulnerabilità di sicurezza
  • Dependabot: Aggiornamenti automatici delle dipendenze

Conclusione

Il packaging moderno di Python si è evoluto per essere amichevole per gli sviluppatori con standard come pyproject.toml, potenti backend di costruzione e pubblicazione sicura tramite Pubblicazione Trasparente. Seguendo questa guida, puoi creare pacchetti Python professionali e mantenibili che serviranno efficacemente i tuoi utenti.

Punti chiave:

  1. Utilizza pyproject.toml per tutti i nuovi progetti
  2. Scegli il giusto backend di costruzione per le tue esigenze
  3. Implementa test automatizzati e CI/CD
  4. Utilizza Pubblicazione Trasparente per una distribuzione sicura senza token
  5. Segui versioning semantico e mantieni i changelog
  6. Fornisci documentazione completa e esempi

L’ecosistema del packaging Python continua a migliorare con strumenti migliori, standard e funzionalità di sicurezza. Mantieniti aggiornato con le Proposte di Miglioramento Python (PEPs) e le best practice della comunità per mantenere i tuoi pacchetti moderni e mantenibili.

Altri Articoli Utili su Questo Sito