web-dev-qa-db-fra.com

setuptools: emplacement du dossier de données du package

J'utilise setuptools pour distribuer mon package python. Maintenant, je dois distribuer des fichiers de données supplémentaires.

D'après ce que j'ai rassemblé dans la documentation de setuptools, j'ai besoin d'avoir mes fichiers de données dans le répertoire du paquet. Cependant, je préfère avoir mes fichiers de données dans un sous-répertoire du répertoire racine.

Ce que j'aimerais éviter:

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

Ce que j'aimerais avoir à la place:

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

Je ne me sens tout simplement pas à l'aise d'avoir autant de sous-répertoires, si ce n'est pas essentiel. Je n'arrive pas à trouver une raison, pourquoi j'ai/ai/pour mettre les fichiers dans le répertoire du paquet. Il est également fastidieux de travailler avec autant de sous-répertoires imbriqués à mon humble avis. Ou existe-t-il une bonne raison qui justifierait cette restriction?

79
phant0m

Option 1: installer en tant que données de package

Le principal avantage de placer des fichiers de données à la racine de votre package Python est qu'il vous permet de ne pas vous soucier de l'emplacement des fichiers sur le système d'un utilisateur, qui peut être Windows, Mac, Linux, une plate-forme mobile, ou à l'intérieur d'un Egg. Vous pouvez toujours trouver le répertoire data relatif à votre racine de package Python, peu importe où ou comment il est installé.

Par exemple, si j'ai une disposition de projet comme celle-ci:

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

Vous pouvez ajouter une fonction à __init__.py pour localiser un chemin absolu vers un fichier de données:

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')

Les sorties:

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

Une fois le projet installé en tant qu'œuf, le chemin vers data changera, mais le code n'a pas besoin de changer:

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

Option 2: installer à un emplacement fixe

L'alternative serait de placer vos données en dehors du package Python et ensuite:

  1. Faites passer l'emplacement de data via un fichier de configuration, des arguments de ligne de commande ou
  2. Intégrez l'emplacement dans votre code Python.

C'est beaucoup moins souhaitable si vous prévoyez de distribuer votre projet. Si vous voulez vraiment faire cela, vous pouvez installer votre data où vous voulez sur le système cible en spécifiant la destination pour chaque groupe de fichiers en passant une liste de tuples:

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

Mise à jour: Exemple d'une fonction Shell pour grep récursivement Python:

atlas% function grep_py { find . -name '*.py' -exec grep -Hn $* {} \; }
atlas% grep_py ": \["
./setup.py:9:    package_data={'foo': ['data/resource1/foo.txt']}
99
samplebias

Je pense avoir trouvé un bon compromis qui vous permettra de maintenir la structure suivante:

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

Vous devez installer les données en tant que package_data, pour éviter les problèmes décrits dans la réponse de l'échantillon, mais afin de conserver la structure du fichier, vous devez ajouter à votre setup.py:

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

De cette façon, nous créons la structure appropriée "juste à temps" et maintenons notre arbre source organisé.

Pour accéder à ces fichiers de données dans votre code, vous utilisez "simplement":

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

Je n'aime toujours pas avoir à spécifier "mypackage" dans le code, car les données ne pourraient avoir rien à voir nécessairement avec ce module, mais je suppose que c'est un bon compromis.

11
polvoazul