vai al contenuto
Simone Gigante Gassó
Logo di Python al centro circondato da tanti computer

Un template per domarli tutti: gestire progetti Python con Cookiecutter e Cruft

/ 4 min di lettura

Indice

Crea progetti più in fretta e mantienili coerenti

Iniziare un nuovo progetto Python spesso significa ripetere lo stesso lavoro di configurazione: creare un Dockerfile, configurare Poetry, organizzare le cartelle, aggiungere le regole di linting e collegare gli strumenti di sviluppo. Alcuni progetti hanno bisogno di piccole variazioni, ma la maggior parte della struttura di solito resta la stessa.

Farlo a mano ogni volta è lento e porta facilmente a errori. Qui entra in gioco Cookiecutter. E quando hai tanti progetti generati dallo stesso template, Cruft ti aiuta a tenerli aggiornati.

Cos’è Cookiecutter?

Cookiecutter è uno strumento Python per generare progetti a partire da template, usando la sintassi Jinja. Anche se è un pacchetto Python, puoi usarlo per creare la struttura di progetti in molti linguaggi.

Creare progetti con Cookiecutter

Ci sono due modi comuni di usare Cookiecutter:

  • Installare e usare un template esistente.
  • Costruire un template personalizzato.

Questo articolo si concentra sul secondo approccio.

Per installare Cookiecutter, esegui uno dei seguenti comandi:

Terminal window
poetry add cookiecutter
Terminal window
pip install cookiecutter
Terminal window
conda install -c conda-forge cookiecutter

Una volta installato, puoi iniziare a costruire il tuo template.

Struttura tipica di un progetto

Ecco un esempio della struttura di progetto Python che riuso spesso:

Esempio di progetto Python

Questa struttura mi permette di creare un nuovo progetto in pochi secondi. Include:

  • Una cartella package con scripts e utils, dove vive la logica dell’applicazione.
  • Una cartella .devcontainer per la configurazione di Docker e del dev container.
  • Una cartella config per i file di configurazione.
  • Una cartella data per dataset, immagini e file di testo.
  • File a livello di repository come pyproject.toml, .pre-commit-config.yaml e i file di ignore.

Questi elementi compaiono in molti dei miei progetti, e la maggior parte della loro configurazione è condivisa.

Costruire un template Cookiecutter personalizzato

Per creare un template, aggiungi un file cookiecutter.json al repository del template. Questo file definisce i valori che Cookiecutter ti chiederà quando genera un nuovo progetto.

{
"project_name": "Name of the project",
"project_slug": "Slug of the project",
"package_name": "Name of the package",
"repository_url": "URL of the project",
"project_description": "Description of the project"
}

Questo esempio è volutamente semplice, ma basta per molti progetti. Ogni valore fa da predefinito per la domanda mostrata durante la creazione del progetto.

Cookiecutter usa la sintassi Jinja, quindi puoi riferirti ai valori così:

{{ cookiecutter.project_slug }}

Nel mio caso, il template usa cartelle come {{ cookiecutter.project_slug }} e {{ cookiecutter.project_name }}. Tutto ciò che sta dentro quelle cartelle diventa parte del progetto generato.

Il mio template Cookiecutter personalizzato, project-wizard, è fatto così:

Template Cookiecutter personalizzato

Per esempio, genero automaticamente un README a partire dai valori definiti in cookiecutter.json:

# {{ cookiecutter.project_name }}
{{ cookiecutter.project_description }}

In file come pyproject.toml definisco anche i pacchetti e gli strumenti che voglio in ogni nuovo repository. La cartella scripts può contenere script di utilità che riuso spesso. È qui che Cookiecutter inizia a farti risparmiare tempo sul serio.

Creare un nuovo progetto

Quando il template è pronto, genera un progetto con:

Terminal window
cookiecutter .

Se il template è su GitHub, puoi anche eseguire:

Terminal window
cookiecutter https://github.com/simoncraf/project-wizard.git

Cookiecutter ti chiederà quindi i valori definiti in cookiecutter.json. I valori predefiniti compaiono tra parentesi.

Domande di Cookiecutter

A questo punto puoi creare tutti i progetti che vuoi dallo stesso template. Il problema successivo è la manutenzione: quando il template evolve, tenere allineati tutti i progetti generati diventa noioso. È il problema che risolve Cruft.

Cos’è Cruft?

Cruft è un pacchetto Python che mantiene i progetti generati sincronizzati con il template Cookiecutter da cui provengono.

Collegare un progetto con Cruft

Inizia installando Cruft:

Terminal window
poetry add cruft
Terminal window
pip install cruft
Terminal window
conda install -c conda-forge cruft

Poi aggiungi un file .cruft.json al tuo template Cookiecutter:

{
"template": "https://github.com/simoncraf/project-wizard",
"commit": "069a78495a5d0cbffc8245f42bd190adaa1df698",
"checkout": "null",
"context": {
"cookiecutter": {
"project_name": "{{cookiecutter.project_name }}",
"project_slug": "{{cookiecutter.project_slug }}",
"package_name": "{{cookiecutter.package_name }}",
"project_short_description": "{{cookiecutter.project_description }}",
"repository_url": "{{cookiecutter.repository_url }}",
"_template": "https://github.com/simoncraf/project-wizard"
}
},
"directory": null
}

Questo file contiene le informazioni di cui Cruft ha bisogno per seguire il template. I due campi più importanti sono:

  • template, che punta al repository del template Cookiecutter.
  • commit, che identifica la versione del template usata dal progetto.

Quando il template cambia, cambia anche il suo ultimo commit. Cruft confronta il commit registrato nel progetto con quello del template e ti avvisa quando c’è un aggiornamento disponibile.

Nel mio template personalizzato uso anche uno script di supporto per assicurarmi che il commit salvato in .cruft.json corrisponda all’ultimo commit del template:

def get_last_commit_hash():
"""
Update the commit hash in `.cruft.json` to the latest commit.
"""
result = subprocess.run(["git", "log", "-n", "1", "--format=%H"], stdout=subprocess.PIPE)
hash_ = result.stdout.decode().strip()
file_path = Path("{{cookiecutter.project_slug}}") / ".cruft.json"
with file_path.open("r", encoding="utf-8") as f:
data = json.load(f)
data["commit"] = hash_
with file_path.open("w", encoding="utf-8") as f:
json.dump(data, f, indent=4)
return hash_

Se hai già creato un progetto con Cookiecutter ma non include .cruft.json, puoi collegarlo a mano:

Terminal window
cruft link https://github.com/simoncraf/project-wizard

Per impostazione predefinita, questo collega il progetto all’ultimo commit del template e crea il file .cruft.json per te.

Aggiornare un progetto

Una volta collegato un progetto, controlla gli aggiornamenti con:

Terminal window
cruft check

Se non ci sono aggiornamenti, vedrai un messaggio di successo:

Messaggio di successo di Cruft

Se il template è cambiato, Cruft ti mostrerà che c’è un aggiornamento disponibile:

Messaggio di errore di Cruft

Per applicare gli aggiornamenti del template, esegui:

Terminal window
cruft update

Cruft ti permette di esaminare le modifiche, saltarle o applicarle direttamente. Dopo averle applicate, riscrive automaticamente i file corrispondenti.

Modifiche fatte da Cruft

Se ci sono conflitti, Cruft crea dei file .rej. Funzionano in modo simile ai rifiuti delle patch e ti aiutano a vedere cosa non è stato possibile unire automaticamente. Quando risolvi o scarti le modifiche, il campo commit in .cruft.json viene aggiornato all’ultima versione del template.

Risorse

Link utili:

Template Cookiecutter pronti all’uso che vale la pena esplorare: