¿Cuál es la mejor estructura de proyecto para una aplicación Python?

Imagine que desea desarrollar una aplicación de escritorio de usuario final no trivial (no web) en Python. ¿Cuál es la mejor manera de estructurar la jerarquía de carpetas del proyecto?

Las características deseables son la facilidad de mantenimiento, la compatibilidad con IDE, la idoneidad para la bifurcación / fusión de control de fuente y la fácil generación de paquetes de instalación.

En particular:

  1. ¿Dónde pones la fuente?
  2. ¿Dónde pones guiones de inicio de aplicación?
  3. ¿Dónde pones el proyecto IDE cruft?
  4. ¿Dónde pones las pruebas de unidad / aceptación?
  5. ¿Dónde coloca los datos que no son de Python, como los archivos de configuración?
  6. ¿Dónde coloca las fonts que no son de Python, como C ++ para los módulos de extensión binaria pyd / so?

    No importa demasiado. Todo lo que te haga feliz funcionará. No hay muchas reglas tontas porque los proyectos de Python pueden ser simples.

    • /scripts o /bin para ese tipo de cosas de la interfaz de línea de comandos
    • /tests para tus pruebas
    • /lib para tus bibliotecas en lenguaje C
    • /doc para la mayoría de la documentación
    • /apidoc para los documentos API generados por Epydoc.

    Y el directorio de nivel superior puede contener README’s, Config’s y todo eso.

    La elección difícil es si usar o no un árbol /src . Python no tiene una distinción entre /src , /lib y /bin como Java o C tiene.

    Como algunos consideran que un directorio de nivel superior /src tiene sentido, su directorio de nivel superior puede ser la architecture de nivel superior de su aplicación.

    • /foo
    • /bar
    • /baz

    Recomiendo poner todo esto en el directorio “nombre de mi producto”. Por lo tanto, si está escribiendo una aplicación llamada quux , el directorio que contiene todas estas cosas se llama /quux .

    El PYTHONPATH otro proyecto, entonces, puede incluir /path/to/quux/foo para reutilizar el módulo QUUX.foo .

    En mi caso, ya que uso Komodo Edit, mi mazo IDE es un solo archivo .KPF. De hecho, puse eso en el directorio de nivel superior /quux , y /quux agregarlo a SVN.

    De acuerdo con la estructura del sistema de archivos de Jean-Paul Calderone de un proyecto Python :

     Project/ |-- bin/ | |-- project | |-- project/ | |-- test/ | | |-- __init__.py | | |-- test_main.py | | | |-- __init__.py | |-- main.py | |-- setup.py |-- README 

    Esta publicación de blog de Jean-Paul Calderone se da comúnmente como una respuesta en #python en Freenode.

    Estructura del sistema de archivos de un proyecto Python

    Hacer:

    • Nombre el directorio algo relacionado con su proyecto. Por ejemplo, si su proyecto se llama “Twisted”, nombre el directorio de nivel superior para sus archivos de origen Twisted . Cuando realice lanzamientos, debe incluir un sufijo de número de versión: Twisted-2.5 .
    • cree un directorio Twisted/bin y ponga sus ejecutables allí, si tiene alguno. No les dé una extensión .py , incluso si son archivos de origen de Python. No coloque ningún código en ellos excepto una importación y una llamada a una función principal definida en algún otro lugar de sus proyectos. (Arruga leve: ya que en Windows, el intérprete es seleccionado por la extensión de archivo, sus usuarios de Windows realmente quieren la extensión .py. Entonces, cuando empaqueta para Windows, puede agregarlo. Desafortunadamente, no hay un truco fácil de distutils que Sé de automatizar este proceso. Teniendo en cuenta que en POSIX la extensión .py es solo una verruga, mientras que en Windows la falta es un error real, si su base de usuarios incluye usuarios de Windows, puede optar por tener solo el .py extensión a todas partes.)
    • Si su proyecto se puede express como un único archivo fuente de Python, colóquelo en el directorio y asígnele un nombre relacionado con su proyecto. Por ejemplo, Twisted/twisted.py . Si necesita varios archivos de origen, cree un paquete en su lugar ( Twisted/twisted/ , con un Twisted/twisted/__init__.py ) y coloque sus archivos de origen en él. Por ejemplo, Twisted/twisted/internet.py .
    • ponga sus pruebas de unidad en un subpaquete de su paquete (nota: esto significa que la única opción de archivo fuente de Python anterior fue un truco; siempre necesita al menos otro archivo para sus pruebas de unidad). Por ejemplo, Twisted/twisted/test/ . Por supuesto, haz que sea un paquete con Twisted/twisted/test/__init__.py . Coloque las pruebas en archivos como Twisted/twisted/test/test_internet.py .
    • agregue Twisted/README y Twisted/setup.py para explicar e instalar su software, respectivamente, si se siente bien.

    No hagas

    • ponga su fuente en un directorio llamado src o lib . Esto hace que sea difícil de ejecutar sin instalar.
    • ponga sus pruebas fuera de su paquete de Python. Esto hace que sea difícil ejecutar las pruebas en una versión instalada.
    • cree un paquete que solo tenga un __init__.py y luego coloque todo su código en __init__.py . Simplemente haga un módulo en lugar de un paquete, es más simple.
    • intente crear hacks mágicos para que Python pueda importar su módulo o paquete sin que el usuario agregue el directorio que lo contiene a su ruta de importación (ya sea a través de PYTHONPATH o algún otro mecanismo). No manejará correctamente todos los casos y los usuarios se enojarán con usted cuando su software no funcione en su entorno.

    Echa un vistazo a Open Sourcing a Python Project de la manera correcta .

    Permítanme un extracto de la parte del diseño del proyecto de ese excelente artículo:

    Al configurar un proyecto, el diseño (o estructura de directorios) es importante para hacerlo bien. Un diseño sensato significa que los contribuyentes potenciales no tienen que gastar eternamente buscando un trozo de código; Las ubicaciones de los archivos son intuitivas. Ya que estamos tratando con un proyecto existente, significa que probablemente deba mover algunas cosas.

    Empecemos por la parte superior. La mayoría de los proyectos tienen una serie de archivos de alto nivel (como setup.py, README.md, Requirements.txt, etc.). Luego hay tres directorios que cada proyecto debería tener:

    • Un directorio de documentos que contiene la documentación del proyecto.
    • Un directorio nombrado con el nombre del proyecto que almacena el paquete Python real
    • Un directorio de prueba en uno de dos lugares.
      • Bajo el directorio del paquete que contiene el código de prueba y los recursos
      • Como un directorio autónomo de nivel superior Para tener una mejor idea de cómo deberían organizarse sus archivos, aquí hay una instantánea simplificada del diseño de uno de mis proyectos, Sandman:
     $ pwd ~/code/sandman $ tree . |- LICENSE |- README.md |- TODO.md |- docs | |-- conf.py | |-- generated | |-- index.rst | |-- installation.rst | |-- modules.rst | |-- quickstart.rst | |-- sandman.rst |- requirements.txt |- sandman | |-- __init__.py | |-- exception.py | |-- model.py | |-- sandman.py | |-- test | |-- models.py | |-- test_sandman.py |- setup.py 

    Como puede ver, hay algunos archivos de nivel superior, un directorio docs (generado es un directorio vacío donde sphinx colocará la documentación generada), un directorio sandman y un directorio de prueba debajo de sandman.

    La “Python Packaging Authority” tiene un proyecto de muestra:

    https://github.com/pypa/sampleproject

    Es un proyecto de muestra que existe como una ayuda para el Tutorial de Empaquetado y Distribución de Proyectos de la Guía del Usuario de Python Packaging.

    Intente iniciar el proyecto utilizando la plantilla python_boilerplate . En gran medida, sigue las mejores prácticas (p. Ej., Las que se incluyen aquí ), pero se adapta mejor en caso de que esté dispuesto a dividir su proyecto en más de un huevo en algún momento (y créame, con cualquier cosa que no sean los proyectos más simples, lo hará. situación común es cuando tiene que usar una versión modificada localmente de la biblioteca de otra persona).

    • ¿Dónde pones la fuente?

      • Para proyectos decentemente grandes, tiene sentido dividir la fuente en varios huevos. Cada huevo iría como un setuptools-layout separado bajo PROJECT_ROOT/src/ .
    • ¿Dónde pones guiones de inicio de aplicación?

      • La opción ideal es tener el script de inicio de la aplicación registrado como un punto de entry_point en uno de los huevos.
    • ¿Dónde pones el proyecto IDE cruft?

      • Depende del IDE. Muchos de ellos mantienen sus cosas en PROJECT_ROOT/. en la raíz del proyecto, y esto está bien.
    • ¿Dónde pones las pruebas de unidad / aceptación?

      • Cada huevo tiene un conjunto separado de pruebas, que se mantienen en su directorio PROJECT_ROOT/src//tests . Personalmente prefiero usar py.test para ejecutarlos.
    • ¿Dónde coloca los datos que no son de Python, como los archivos de configuración?

      • Depende. Puede haber diferentes tipos de datos que no sean de Python.
        • “Recursos” , es decir, datos que deben empaquetarse dentro de un huevo. Estos datos entran en el directorio de huevo correspondiente, en algún lugar dentro del espacio de nombres del paquete. Se puede utilizar a través del paquete pkg_resources .
        • Los “archivos de configuración” , es decir, los archivos que no son de Python deben considerarse externos a los archivos de origen del proyecto, pero deben inicializarse con algunos valores cuando la aplicación comienza a ejecutarse. Durante el desarrollo, prefiero mantener estos archivos en PROJECT_ROOT/config . Para la implementación puede haber varias opciones. En Windows se puede usar %APP_DATA%//config , en Linux, /etc/ o /opt//config .
        • Archivos generados , es decir, archivos que pueden ser creados o modificados por la aplicación durante la ejecución. Preferiría mantenerlos en PROJECT_ROOT/var durante el desarrollo, y bajo /var durante la implementación de Linux.
    • ¿Dónde coloca las fonts que no son de Python, como C ++ para los módulos de extensión binaria pyd / so?
      • En PROJECT_ROOT/src//native

    La documentación normalmente iría a PROJECT_ROOT/doc o PROJECT_ROOT/src//doc (esto depende de si considera que algunos de los huevos son proyectos grandes separados). Algunas configuraciones adicionales estarán en archivos como PROJECT_ROOT/buildout.cfg y PROJECT_ROOT/setup.cfg .

    En mi experiencia, es sólo una cuestión de iteración. Ponga sus datos y código donde quiera que piensen que van. Lo más probable es que, de todos modos, estarás equivocado Pero una vez que tenga una mejor idea de cómo se van a dar las cosas, estará en una mejor posición para hacer este tipo de conjeturas.

    En cuanto a las fonts de extensión, tenemos un directorio de código debajo del tronco que contiene un directorio para python y un directorio para varios otros idiomas. Personalmente, estoy más inclinado a intentar colocar cualquier código de extensión en su propio repository la próxima vez.

    Dicho esto, vuelvo a mi punto inicial: no hagas demasiado problema con eso. Ponlo en algún lugar que parezca funcionar para ti. Si encuentra algo que no funciona, puede (y debería) cambiarse.

    Los datos que no son de Python se combinan mejor dentro de sus módulos de Python usando el soporte package_data en setuptools . Una cosa que recomiendo encarecidamente es usar paquetes de espacio de nombres para crear espacios de nombres compartidos que puedan usar múltiples proyectos, como la convención de Java de poner paquetes en com.yourcompany.yourproject (y poder tener un espacio de nombres com.yourcompany.utils ).

    Re ramificación y fusión, si usa un sistema de control de fuente suficientemente bueno, manejará las combinaciones incluso a través de renombramientos; Bazar es particularmente bueno en esto.

    Al contrario de algunas otras respuestas aquí, tengo +1 en tener un directorio src de nivel superior (con test directorios doc y test lado). Las convenciones específicas para los árboles de directorios de documentación variarán según lo que esté usando; Sphinx , por ejemplo, tiene sus propias convenciones que admite su herramienta de inicio rápido.

    Por favor, aproveche setuptools y pkg_resources; esto hace que sea mucho más fácil para otros proyectos confiar en versiones específicas de su código (y para que varias versiones se instalen simultáneamente con diferentes archivos que no son de código, si está usando package_data ).