Saltar a contenido

Estructura de directorios de una librería Python

En Python hay fundamentalmente dos recomendaciones distintas de cómo estructurar los directorios (project layout) de una librería o herramienta, que queramos distribuir.

No incluyo en este artículo estructuras para proyectos. Por ejemplo una aplicación web monorepo con el backend y el front en el mismo repositorio. Proyectos que quieren desarrollar distintas herramientas bajo el mismo namespace. U otros casos particulares.

Sin directorio raíz

Es la estructura más sencilla, especialmente para proyectos pequeños o scripts individuales.

En algunos artículos llaman a esta estructura flat layout. Esto induce a error, porqué flat layout es el nombre más usado para la siguiente estructura que veremos. El artículo es antiguo pero en realpython llaman a esto one-off-script

my_project/
├── __init__.py
├── my_project.py
├── requirements.txt
├── tests/
│   └── test_my_project.py
└── README.md

Tiene a favor que es simple, no requiere subdirectorios, ni siquiera el fichero __init__.py, y todo está a la vista.

Pero nunca deberíamos usarlo. El boilerplate de generar una estructura mejor tiene un coste cero si usamos un cookiecutter, y con esta estructura enseguida aparecen problemas de cómo organizar el código, empaquetar el proyecto y a la mínima tendremos problemas con los import.

Módulo en la raíz (flat layout)

También llamado adhoc layout.

Es probablemente la más usada. El paquete (directorio) principal del proyecto está directamente en la raíz.

my-project/
├── my_project/
│   ├── __init__.py
│   ├── main.py
│   └── utils.py
├── tests/
│   ├── test_main.py
│   └── test_utils.py
├───.github
│   └───workflows
├───docs
│   └───mkdocs.yml
├── pyproject.toml
└── README.md

Pros:

  • Más directa que src layout al eliminar un nivel de anidación.
  • No es necesario instalar (pip install) para ejecutar el código.
  • Muchos proyectos siguen este patrón.

Contras:

  • Riesgo de import locales: Existe mayor riesgo de que Python importe el módulo en desarrollo (en el source tree digamos) en lugar del módulo instalado. Esto puede llevar a errores difíciles de detectar a la hora de ejecutar los tests, empaquetar e instalar el proyecto.
  • No es tan explícito como src layout sobre lo que se empaqueta y lo que no.

Subdirectorio src/ (src layout)

Es una de las más recomendadas.

my-project/
├── src/
│   └── my_project/
│       ├── __init__.py
│       ├── main.py
│       └── utils.py
├── tests/
│   ├── test_main.py
│   └── test_utils.py
├───.github
│   └───workflows
├───docs
│   └───mkdocs.yml
├── pyproject.toml
└── README.md

Pros:

  • Al colocar el código en src/my_project/, el intérprete de Python siempre importará la versión instalada del paquete, no la versión en desarrollo en el directorio raíz. Esto ayuda a asegurar que el comportamiento en desarrollo sea el mismo que en producción.
  • Claridad de empaquetado: Deja claro qué partes del proyecto son el código fuente que se va a empaquetar y distribuir. Otros archivos (tests, documentación, configuración de desarrollo) se mantienen fuera del paquete.
  • Fomenta buenas prácticas: Al requerir una "instalación" (aunque sea editable) para que el código funcione correctamente en desarrollo.
  • Mejor aislamiento de pruebas: Los tests se colocan fuera del directorio src/, lo que significa que no se incluyen en el paquete distribuido y evita dependencias de prueba en el código de producción.
  • Es el que prefiere uv

Contras:

  • Requiere más boilerplate. Para ejecutar el código se requiere una instalación editable o ajustar el PYTHONPATH.
  • La carpeta src/ adicional puede ser redundante.

Variantes y Comentarios

  • Alguna gente prefiere introducir un nivel extra de indirección donde por ejemplo incluir venv
  • Cuando escojamos un nombre para el proyecto, si vamos a publicarlo deberíamos comprobar que el nombre no está cogido en PyPy.
  • Separadores de nombre. Los módulos y paquetes de Python no admiten hyphens, -, sólo underscore, _. Es habitual usar kebab-case (hyphenated) para el nombre del repositorio (carpeta raíz) y snake_case (underscored) para los paquetes y módulos.
  • Alguna gente considera que lo correcto es empaquetar los tests dentro del distributable package. Yo no.

Mi preferida

Mi preferida es scr layout.

Las funcionalidades de los editores (compact folders, quick open) y herramientas cómo uv o cookiecutter reducen las incomodidades que introduce esta estructura.

Visual y organizativamente me gusta cómo queda la raíz del proyecto.

Me molesta mucho perder tiempo en los "esto no puede estar pasando", y esta estructura minimiza esos subtle bugs

Creo que el simple hecho de preocuparse de entenderla ayuda a que la gente no se líe a tocar sys.path, o probar con python -m a ver si sus import funcionan de esa forma. Aunque de "los scripts dentro de proyectos" hablaremos en otro artículo.

Referencias