salta al contingut
Simone Gigante Gassó
Logotip de Python al centre envoltat de molts ordinadors

Una plantilla per governar-los a tots: gestionar projectes Python amb Cookiecutter i Cruft

/ 5 min de lectura

Índex

Crea projectes més de pressa i mantén-los coherents

Començar un projecte Python nou sovint vol dir repetir la mateixa feina de configuració: crear un Dockerfile, configurar Poetry, organitzar les carpetes, afegir regles de linting i connectar les eines de desenvolupament. Alguns projectes necessiten petites variacions, però la major part de l’estructura normalment és la mateixa.

Fer-ho a mà cada vegada és lent i és fàcil equivocar-se. Aquí és on Cookiecutter ajuda. I quan tens molts projectes generats a partir de la mateixa plantilla, Cruft t’ajuda a mantenir-los actualitzats.

Què és Cookiecutter?

Cookiecutter és una eina de Python per generar projectes a partir de plantilles amb la sintaxi de Jinja. Tot i que és un paquet de Python, el pots fer servir per crear l’estructura de projectes en molts llenguatges.

Crear projectes amb Cookiecutter

Hi ha dues maneres habituals de fer servir Cookiecutter:

  • Instal·lar i fer servir una plantilla existent.
  • Construir la teva pròpia plantilla.

Aquest article se centra en la segona opció.

Per instal·lar Cookiecutter, executa una d’aquestes ordres:

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

Un cop instal·lat, pots començar a construir la teva plantilla.

Estructura típica d’un projecte

Aquí tens un exemple de l’estructura de projecte Python que reutilitzo sovint:

Exemple de projecte Python

Aquesta estructura em permet crear un projecte nou en pocs segons. Inclou:

  • Una carpeta package amb scripts i utils, on viu la lògica de l’aplicació.
  • Una carpeta .devcontainer per a la configuració de Docker i del contenidor de desenvolupament.
  • Una carpeta config per als fitxers de configuració.
  • Una carpeta data per a conjunts de dades, imatges i fitxers de text.
  • Fitxers a nivell de repositori com pyproject.toml, .pre-commit-config.yaml i els fitxers d’ignore.

Aquests elements apareixen en molts dels meus projectes, i la major part de la configuració és compartida.

Construir una plantilla de Cookiecutter pròpia

Per crear una plantilla, afegeix un fitxer cookiecutter.json al repositori de la plantilla. Aquest fitxer defineix els valors que Cookiecutter et demanarà quan generi un projecte nou.

{
"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"
}

Aquest exemple és senzill a propòsit, però n’hi ha prou per a molts projectes. Cada valor fa de valor per defecte de la pregunta que es mostra en crear el projecte.

Cookiecutter fa servir la sintaxi de Jinja, així que pots fer referència als valors d’aquesta manera:

{{ cookiecutter.project_slug }}

En el meu cas, la plantilla fa servir carpetes com {{ cookiecutter.project_slug }} i {{ cookiecutter.project_name }}. Tot el que hi ha dins d’aquestes carpetes passa a formar part del projecte generat.

La meva plantilla de Cookiecutter, project-wizard, és així:

Plantilla de Cookiecutter pròpia

Per exemple, genero automàticament un README a partir dels valors definits a cookiecutter.json:

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

En fitxers com pyproject.toml també defineixo els paquets i les eines que vull a cada repositori nou. La carpeta scripts pot incloure scripts d’utilitat que reutilitzo sovint. És aquí on Cookiecutter comença a estalviar-te temps de veritat.

Crear un projecte nou

Quan la plantilla estigui a punt, genera un projecte amb:

Terminal window
cookiecutter .

Si la plantilla és a GitHub, també pots executar:

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

Llavors Cookiecutter et demanarà els valors definits a cookiecutter.json. Els valors per defecte apareixen entre claudàtors.

Preguntes de Cookiecutter

En aquest punt pots crear tants projectes com vulguis a partir de la mateixa plantilla. El següent problema és el manteniment: quan la plantilla evoluciona, mantenir alineats tots els projectes generats es fa feixuc. Aquest és el problema que resol Cruft.

Què és Cruft?

Cruft és un paquet de Python que manté els projectes generats sincronitzats amb la plantilla de Cookiecutter de què provenen.

Vincular un projecte amb Cruft

Comença instal·lant Cruft:

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

Després afegeix un fitxer .cruft.json a la teva plantilla de 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
}

Aquest fitxer guarda la informació que Cruft necessita per seguir la plantilla. Els dos camps més importants són:

  • template, que apunta al repositori de la plantilla de Cookiecutter.
  • commit, que identifica la versió de la plantilla que fa servir el projecte.

Quan la plantilla canvia, també canvia el seu últim commit. Cruft compara el commit registrat al projecte amb el de la plantilla i t’avisa quan hi ha una actualització disponible.

A la meva plantilla també faig servir un script auxiliar per assegurar-me que el commit guardat a .cruft.json coincideix amb l’últim commit de la plantilla:

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_

Si ja has creat un projecte amb Cookiecutter però no inclou .cruft.json, el pots vincular manualment:

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

Per defecte, això vincula el projecte a l’últim commit de la plantilla i et crea el fitxer .cruft.json.

Actualitzar un projecte

Un cop vinculat el projecte, comprova si hi ha actualitzacions amb:

Terminal window
cruft check

Si no n’hi ha, veuràs un missatge d’èxit:

Missatge d'èxit de Cruft

Si la plantilla ha canviat, Cruft et mostrarà que hi ha una actualització disponible:

Missatge d'error de Cruft

Per aplicar les actualitzacions de la plantilla, executa:

Terminal window
cruft update

Cruft et permet revisar els canvis, saltar-los o aplicar-los directament. Després d’aplicar-los, reescriu automàticament els fitxers corresponents.

Canvis fets per Cruft

Si hi ha conflictes, Cruft crea fitxers .rej. Funcionen de manera semblant als rebuigs dels pedaços i t’ajuden a revisar el que no s’ha pogut fusionar automàticament. Quan resols o descartes els canvis, el camp commit de .cruft.json s’actualitza a l’última versió de la plantilla.

Recursos

Enllaços útils:

Plantilles de Cookiecutter a punt per fer servir que val la pena explorar: