In questo articolo analiziamo il processo di packaging di un progetto Python. Vediamo come aggiungere i file e la struttura necessari per creare il pacchetto, come compilare il pacchetto e come caricarlo sul Python Package Index (PyPI).
Requisiti
Come prima cosa assicuriamoci di aver installato l’ultima versione di PIP
python3 -m pip install --upgrade pip
Lanciamo l’aggiornamento di build
python3 -m pip install --upgrade build
E assicuriamoci che anche twine sia all’ultima versione
python3 -m pip install --upgrade twine
Creazione del progetto
Questo tutorial utilizza un semplice progetto denominato example_package_YOUR_USERNAME_HERE
. Il mio username è alessandro, quindi il pacchetto sarà example_package_alessandro
; questo ci assicura di avere un nome di pacchetto univoco che non entri in conflitto con i pacchetti caricati da altre persone che seguono questo tutorial. Ti consiglio di seguire questo tutorial utilizzando questo progetto, prima di passare al packaging del tuo progetto reale.
Crea la seguente struttura di file in locale:
packaging_tutorial/
└── src/
└── example_package_YOUR_USERNAME_HERE/
├── __init__.py
└── example.py
La directory contenente i file Python dovrebbe chiamarsi con il nome del progetto perché ciò semplifica la configurazione ed è di più facile interpretazione per gli utenti che installano il pacchetto.
È inoltre consigliata la creazione del file __init__.py
perché l’esistenza di un file __init__.py
consente agli utenti di importare la directory come un pacchetto normale, anche se (come nel caso di questo tutorial) __init__.py
è vuoto.
example.py
è un esempio di un modulo all’interno del pacchetto che potrebbe contenere la logica (funzioni, classi, costanti, ecc.) del pacchetto. Per questo esempio utilizzeremo qualcosa di molto semplice. Apri quindi il file example.py
e inserisci il seguente contenuto:
def add_one(number):
return number + 1
Creazione dei package files
Ora aggiungiamo tutti i file necessari affinché il nostro progetto sia conforme e pronto per la sua distribuzione. Al termine, la struttura del progetto apparirà così:
packaging_tutorial/
├── LICENSE
├── pyproject.toml
├── README.md
├── src/
│ └── example_package_YOUR_USERNAME_HERE/
│ ├── __init__.py
│ └── example.py
└── tests/
Creazione della test directory
tests/
è un placeholder per i file di test. Per il momento lo lasceremo semplicemente vuoto. Scelta del backend di build
Strumenti come pip e build non convertono effettivamente i sorgenti in un pacchetto di distribuzione (come una wheel). Per svolgere questo lavoro dobbiamo affidarci ad un backend di build.
Esistono diversi backend, ognuno con caratteristiche diverse. In questo tutorial utilizziamo Hatchling di default, ma funzionerà in modo identico con Setuptools, Flit, PDM e tutti gli altri backend che supportano la tabella [project] per i metadati.
pyproject.toml
indica agli strumenti di frontend di build come pip e build quale backend utilizzare per il progetto. Di seguito sono riportati alcuni esempi di backend di build comuni Hatchling
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
setuptools
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
Flit
[build-system]
requires = ["flit_core>=3.4"]
build-backend = "flit_core.buildapi"
PDM
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
La key requires
è un elenco di pacchetti necessari per compilare il nostro progetto. Il frontend dovrebbe installarli automaticamente prima di compilare il progetto. Essendo che i frontend solitamente eseguono le build in ambienti isolati, omettere di specificare le dipendenze potrebbe causare errori in fase di compilazione. È pertanto necessario includere sempre il pacchetto del backend di build che si sta utilizzando, in quanto almeno quello è necessario che venga installato nell’ambiente isolato di compilazione.
build-backend
è invece il nome dell’oggetto Python che il frontend utilizzerà per eseguire la build. Configurazione dei metadata
pyproject.toml
e incolliamo il seguente contenuto cambiando opportunamente i dati relativi allo username. Questo ci assicura che il nome del package non andrà in conflitto con i packages caricati da altre persone che seguono questo tutorial.
[project]
name = "example_package_YOUR_USERNAME_HERE"
version = "0.0.1"
authors = [
{ name="Example Author", email="author@example.com" },
]
description = "A small example package"
readme = "README.md"
requires-python = ">=3.8"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
[project.urls]
Homepage = "https://github.com/pypa/sampleproject"
Issues = "https://github.com/pypa/sampleproject/issues"
Creazione del file README.md
README.md
e inseriamo il seguente contenuto.
# Example Package
This is a simple example package. You can use
[GitHub-flavored Markdown](https://guides.github.com/features/mastering-markdown/)
to write your content.
Creazione del file README.md
È importante che ogni pacchetto caricato sul Python Package Index includa una licenza in quanto indica agli utenti che installeranno e utilizzeranno il pacchetto i termini in base ai quali lo possono utilizzare. Per assistenza nella scelta di una licenza, un sito di riferimento è https://choosealicense.com/. Dopo aver scelto una licenza, apriamo LICENSE e inseriamo il testo della licenza.
Nel nostro esempio utiliziamo la licenza MIT:
Copyright (c) 2018 The Python Packaging Authority
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Generazione dei distribution archives
pyproject.toml
e lanciamo il seguente comando:
python3 -m build
L’esecuzione di questo comando darà come output una cospiqua quantità di testo. Una volta completato troveremo una directory chiamata dist/ la quale conterrà due file.
dist/
├── example_package_YOUR_USERNAME_HERE-0.0.1-py3-none-any.whl
└── example_package_YOUR_USERNAME_HERE-0.0.1.tar.gz
Upload del package su PyPI
Finalmente possiamo caricare il nostro pacchetto su PyPI. Però sono necessari alcuni passaggi preliminari. Vediamoli in dettaglio.
Registrazione account
La prima cosa da fare è registrare un account su TestPyPI. Seguiamo questo link e registriamo il nostro account https://test.pypi.org/account/register/
Generazione del token
Dopo aver registrato il nostro account su TestPyPI, dobbiamo generare il token che usiamo per caricare i nostri pacchetti. Seguiamo questo link e creiamo il token https://test.pypi.org/manage/account/#api-tokens
Nel mio account di esempio ho impostato un token in questo modo:
Creazione del file di configurazione di twine
.pypirc
e incolliamo la seguente configurazione personalizzando la key password con il token che abbiamo appena creato.
[testpypi]
username = __token__
password = pypi-[your-token]
Pubblicazione del pacchetto
Ora siamo davvero pronti! Pubblichiamo il nostro pacchetto con il seguente comando:
python3 -m twine upload --repository testpypi dist/*