setuptools: ubicación de la carpeta de datos del paquete

94

Utilizo setuptools para distribuir mi paquete de Python. Ahora necesito distribuir archivos de datos adicionales.

Por lo que he recopilado de la documentación de setuptools, necesito tener mis archivos de datos dentro del directorio del paquete. Sin embargo, preferiría tener mis archivos de datos dentro de un subdirectorio en el directorio raíz.

Lo que me gustaría evitar:

/ #root
|- src/
|  |- mypackage/
|  |  |- data/
|  |  |  |- resource1
|  |  |  |- [...]
|  |  |- __init__.py
|  |  |- [...]
|- setup.py

Lo que me gustaría tener en su lugar:

/ #root
|- data/
|  |- resource1
|  |- [...]
|- src/
|  |- mypackage/
|  |  |- __init__.py
|  |  |- [...]
|- setup.py

Simplemente no me siento cómodo con tener tantos subdirectorios, si no es esencial. No encuentro una razón por la que tengo que poner los archivos dentro del directorio del paquete. También es engorroso trabajar con tantos subdirectorios anidados en mi humilde opinión. ¿O hay alguna buena razón que justifique esta restricción?

phant0m
fuente
8
Hice una pregunta similar sobre el uso de 'data_files' para distribuir recursos (documentos, imágenes, etc.): stackoverflow.com/questions/5192386/… ... y las (dos) respuestas dijeron que usaran 'package_data' en su lugar. Ahora estoy usando datos de paquetes, pero eso implica que tengo que poner mis datos y documentos dentro de mi paquete, es decir, mezclados con mi código fuente. No me gusta esto. Al hacer grepping en mi fuente, encuentro no solo la definición de clase que estoy buscando, sino también las docenas de menciones que obtienen dentro de mis archivos RST, HTML e intermedios. :-(
Jonathan Hartley
2
Sé que esta respuesta llega muy tarde, @JonathanHartley, pero puedes convertir cualquier directorio en un "paquete" agregando un __init__.pyarchivo, incluso si ese archivo está en blanco. Por lo tanto, podría mantener un directorio de datos separado con un __init__.pyarchivo vacío para que parezca un paquete. Eso debería evitar que grep de su árbol de fuentes los recoja, pero Python y sus herramientas de compilación lo reconocerán como un paquete.
dhj
@dhj Una idea interesante, gracias.
Jonathan Hartley
4
@dhj, el único problema con ese enfoque es que Python cree que ha instalado un paquete llamado 'datos'. Si otro paquete que instaló intentó empaquetar datos de la misma manera, tendría instalados dos paquetes de 'datos' en conflicto.
dedos de los pies

Respuestas:

111

Opción 1: instalar como datos de paquete

La principal ventaja de colocar archivos de datos dentro de la raíz de su paquete Python es que le permite evitar preocuparse por dónde vivirán los archivos en el sistema de un usuario, que puede ser Windows, Mac, Linux, alguna plataforma móvil o dentro de un huevo. Siempre puede encontrar el directorio datarelativo a la raíz de su paquete Python, sin importar dónde o cómo esté instalado.

Por ejemplo, si tengo un diseño de proyecto como este:

project/
    foo/
        __init__.py
        data/
            resource1/
                foo.txt

Puede agregar una función para __init__.pylocalizar una ruta absoluta a un archivo de datos:

import os

_ROOT = os.path.abspath(os.path.dirname(__file__))
def get_data(path):
    return os.path.join(_ROOT, 'data', path)

print get_data('resource1/foo.txt')

Salidas:

/Users/pat/project/foo/data/resource1/foo.txt

Una vez que el proyecto se instala como Egg, la ruta datacambiará, pero no es necesario que cambie el código:

/Users/pat/virtenv/foo/lib/python2.6/site-packages/foo-0.0.0-py2.6.egg/foo/data/resource1/foo.txt

Opción 2: instalar en una ubicación fija

La alternativa sería colocar sus datos fuera del paquete de Python y luego:

  1. Tener la ubicación datapasada a través de un archivo de configuración, argumentos de línea de comando o
  2. Incruste la ubicación en su código Python.

Esto es mucho menos deseable si planea distribuir su proyecto. Si realmente desea hacer esto, puede instalar su datadonde quiera en el sistema de destino especificando el destino para cada grupo de archivos pasando una lista de tuplas:

from setuptools import setup
setup(
    ...
    data_files=[
        ('/var/data1', ['data/foo.txt']),
        ('/var/data2', ['data/bar.txt'])
        ]
    )

Actualizado : ejemplo de una función de shell para grep recursivamente archivos Python:

atlas% function grep_py { find . -name '*.py' -exec grep -Hn $* {} \; }
atlas% grep_py ": \["
./setup.py:9:    package_data={'foo': ['data/resource1/foo.txt']}
samplebias
fuente
7
Muchas gracias por ayudarme a aceptar la situación. Así que estoy feliz de ejecutar el uso de package_data como usted (y todos los demás) sugiere. Sin embargo: ¿Soy solo yo quien encuentra que poner sus datos y documentos dentro del directorio de origen de su paquete es inconvenientemente complicado? (por ejemplo, grepping mi fuente devuelve docenas de accesos no deseados de mi documentación. Podría agregar parámetros '--exclude-dir' a grep cada vez que lo use, lo cual diferiría de un proyecto a otro, pero eso parece asqueroso) es posible incluir algo así como un 'origen' subdirectorio dentro de mi dir paquete sin romper las importaciones, etc
Jonathan Hartley
Por lo general, solo coloco los archivos de datos que requiere el paquete en el directorio del paquete. Instalaría los documentos como data_files. Además, podría crear un alias de shell para que grep ignore los archivos que no son de Python, algo como grep_py.
samplebias
Oye, samplebias. Gracias por las actualizaciones. Sin embargo, no es solo grep, lo es todo , desde la búsqueda en archivos del editor de texto hasta ctags y awk. Voy a intentar reordenar mi proyecto para poner documentos en data_files como sugieres, mira cómo funciona. De vuelta pronto ... :-)
Jonathan Hartley
... eso parece funcionar bien. Gracias por ponerme en el camino correcto. ¿Son sabrosos los +50 puntos de reputación?
Jonathan Hartley
¡Gracias! ¡Es genial escucharlo, me alegro de que haya funcionado y de que estés progresando!
samplebias
13

Creo que encontré un buen compromiso que te permitirá mantener la siguiente estructura:

/ #root
|- data/
|  |- resource1
|  |- [...]
|- src/
|  |- mypackage/
|  |  |- __init__.py
|  |  |- [...]
|- setup.py

Debe instalar los datos como package_data, para evitar los problemas descritos en la respuesta de samplebias, pero para mantener la estructura del archivo, debe agregar a su setup.py:

try:
    os.symlink('../../data', 'src/mypackage/data')
    setup(
        ...
        package_data = {'mypackage': ['data/*']}
        ...
    )
finally:
    os.unlink('src/mypackage/data')

De esta manera creamos la estructura apropiada "justo a tiempo" y mantenemos nuestro árbol de fuentes organizado.

Para acceder a dichos archivos de datos dentro de su código, "simplemente" usa:

data = resource_filename(Requirement.parse("main_package"), 'mypackage/data')

Todavía no me gusta tener que especificar 'mypackage' en el código, ya que los datos podrían no tener nada que ver con este módulo, pero supongo que es un buen compromiso.

polvoazul
fuente
-4

Creo que básicamente puedes dar cualquier cosa como argumento * data_files * a setup () .

lgautier
fuente
Hmm ... Puedo ver que está en la documentación de distutils, aunque no puedo verlo en la documentación de setuptools. De todos modos, ¿cómo podría acceder a él eventualmente?
phant0m
Creo que data_files solo debe usarse para datos que se comparten entre varios paquetes. por ejemplo, si pip install desde PyPI, los archivos enumerados en data_files se instalan en directorios directamente debajo de su directorio de instalación principal de Python. (es decir, no en Python27 / Lib / site-packages / mypackage, sino en paralelo con 'Python27 / Lib')
Jonathan Hartley