saltar al contenido
Simone Gigante Gassó
Logotipo de Python en el centro rodeado de muchos ordenadores

Una plantilla para gobernarlos a todos: gestionar proyectos Python con Cookiecutter y Cruft

/ 5 min de lectura

Índice

Crea proyectos más rápido y mantenlos coherentes

Empezar un proyecto Python nuevo a menudo significa repetir el mismo trabajo de configuración: crear un Dockerfile, configurar Poetry, organizar las carpetas, añadir reglas de linting y conectar las herramientas de desarrollo. Algunos proyectos necesitan pequeñas variaciones, pero la mayor parte de la estructura suele ser la misma.

Hacerlo a mano cada vez es lento y es fácil equivocarse. Ahí es donde ayuda Cookiecutter. Y cuando tienes muchos proyectos generados a partir de la misma plantilla, Cruft te ayuda a mantenerlos actualizados.

¿Qué es Cookiecutter?

Cookiecutter es una herramienta de Python para generar proyectos a partir de plantillas con la sintaxis de Jinja. Aunque es un paquete de Python, puedes usarlo para crear la estructura de proyectos en muchos lenguajes.

Crear proyectos con Cookiecutter

Hay dos formas habituales de usar Cookiecutter:

  • Instalar y usar una plantilla existente.
  • Construir tu propia plantilla.

Este artículo se centra en la segunda opción.

Para instalar Cookiecutter, ejecuta uno de estos comandos:

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

Una vez instalado, puedes empezar a construir tu plantilla.

Estructura típica de un proyecto

Aquí tienes un ejemplo de la estructura de proyecto Python que reutilizo a menudo:

Ejemplo de proyecto Python

Esta estructura me permite crear un proyecto nuevo en segundos. Incluye:

  • Una carpeta package con scripts y utils, donde vive la lógica de la aplicación.
  • Una carpeta .devcontainer para la configuración de Docker y del contenedor de desarrollo.
  • Una carpeta config para los archivos de configuración.
  • Una carpeta data para conjuntos de datos, imágenes y archivos de texto.
  • Archivos a nivel de repositorio como pyproject.toml, .pre-commit-config.yaml y los archivos de ignore.

Estos elementos aparecen en muchos de mis proyectos, y la mayor parte de su configuración es compartida.

Construir una plantilla de Cookiecutter propia

Para crear una plantilla, añade un archivo cookiecutter.json al repositorio de la plantilla. Este archivo define los valores que Cookiecutter te pedirá al generar un proyecto nuevo.

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

Este ejemplo es sencillo a propósito, pero basta para muchos proyectos. Cada valor hace de valor por defecto de la pregunta que se muestra al crear el proyecto.

Cookiecutter usa la sintaxis de Jinja, así que puedes hacer referencia a los valores así:

{{ cookiecutter.project_slug }}

En mi caso, la plantilla usa carpetas como {{ cookiecutter.project_slug }} y {{ cookiecutter.project_name }}. Todo lo que hay dentro de esas carpetas pasa a formar parte del proyecto generado.

Mi plantilla de Cookiecutter, project-wizard, es así:

Plantilla de Cookiecutter propia

Por ejemplo, genero automáticamente un README a partir de los valores definidos en cookiecutter.json:

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

En archivos como pyproject.toml también defino los paquetes y las herramientas que quiero en cada repositorio nuevo. La carpeta scripts puede incluir scripts de utilidad que reutilizo a menudo. Es aquí donde Cookiecutter empieza a ahorrarte tiempo de verdad.

Crear un proyecto nuevo

Cuando la plantilla esté lista, genera un proyecto con:

Terminal window
cookiecutter .

Si la plantilla está en GitHub, también puedes ejecutar:

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

Cookiecutter te pedirá entonces los valores definidos en cookiecutter.json. Los valores por defecto aparecen entre corchetes.

Preguntas de Cookiecutter

A partir de aquí puedes crear tantos proyectos como quieras desde la misma plantilla. El siguiente problema es el mantenimiento: cuando la plantilla evoluciona, mantener alineados todos los proyectos generados se vuelve tedioso. Ese es el problema que resuelve Cruft.

¿Qué es Cruft?

Cruft es un paquete de Python que mantiene los proyectos generados sincronizados con la plantilla de Cookiecutter de la que provienen.

Vincular un proyecto con Cruft

Empieza instalando Cruft:

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

Después añade un archivo .cruft.json a tu 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
}

Este archivo guarda la información que Cruft necesita para seguir la plantilla. Los dos campos más importantes son:

  • template, que apunta al repositorio de la plantilla de Cookiecutter.
  • commit, que identifica la versión de la plantilla que usa el proyecto.

Cuando la plantilla cambia, también cambia su último commit. Cruft compara el commit registrado en el proyecto con el de la plantilla y te avisa cuando hay una actualización disponible.

En mi plantilla también uso un script auxiliar para asegurarme de que el commit guardado en .cruft.json coincide con el último 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 ya has creado un proyecto con Cookiecutter pero no incluye .cruft.json, puedes vincularlo manualmente:

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

Por defecto, esto vincula el proyecto al último commit de la plantilla y te crea el archivo .cruft.json.

Actualizar un proyecto

Una vez vinculado el proyecto, comprueba si hay actualizaciones con:

Terminal window
cruft check

Si no las hay, verás un mensaje de éxito:

Mensaje de éxito de Cruft

Si la plantilla ha cambiado, Cruft te mostrará que hay una actualización disponible:

Mensaje de error de Cruft

Para aplicar las actualizaciones de la plantilla, ejecuta:

Terminal window
cruft update

Cruft te permite revisar los cambios, saltártelos o aplicarlos directamente. Después de aplicarlos, reescribe automáticamente los archivos correspondientes.

Cambios hechos por Cruft

Si hay conflictos, Cruft crea archivos .rej. Funcionan de forma parecida a los rechazos de los parches y te ayudan a revisar lo que no se ha podido fusionar automáticamente. Cuando resuelves o descartas los cambios, el campo commit de .cruft.json se actualiza a la última versión de la plantilla.

Recursos

Enlaces útiles:

Plantillas de Cookiecutter listas para usar que vale la pena explorar: