setuptools: emplacement du dossier de données du package

94

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 recueilli dans la documentation de setuptools, j'ai besoin d'avoir mes fichiers de données dans le répertoire du package. Cependant, je préférerais avoir mes fichiers de données dans un sous-répertoire dans le répertoire racine.

Ce que je voudrais é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 avec autant de sous-répertoires, si ce n'est pas essentiel. Je n'arrive pas à trouver une raison pour laquelle je / dois / pour mettre les fichiers dans le répertoire du package. Il est également fastidieux de travailler avec autant de sous-répertoires imbriqués IMHO. Ou y a-t-il une bonne raison qui justifierait cette restriction?

phant0m
la source
8
J'ai posé une question similaire sur l'utilisation de 'data_files' pour distribuer des ressources (documents, images, etc.): stackoverflow.com/questions/5192386/… ... et les (deux) réponses ont toutes deux dit utiliser 'package_data' à la place. Maintenant, j'utilise des données de package, mais cela implique que je dois mettre mes données et documents dans mon package, c'est-à-dire mélangés dans mon code source. Je n'aime pas ça. Lorsque je saisis ma source, je trouve non seulement la définition de classe que je recherche, mais aussi les dizaines de mentions qu'ils reçoivent dans mes fichiers RST, HTML et intermédiaires. :-(
Jonathan Hartley
2
Je sais que cette réponse est très tardive, @JonathanHartley, mais vous pouvez faire de n'importe quel répertoire un «package» en ajoutant un __init__.pyfichier, même si ce fichier est vide. Vous pouvez donc conserver un répertoire de données séparé avec un __init__.pyfichier vide pour le faire ressembler à un package. Cela devrait empêcher grep de votre arborescence source de les récupérer, mais il sera toujours reconnu comme un paquet par python et ses outils de construction.
dhj le
@dhj Une idée intéressante, merci.
Jonathan Hartley
4
@dhj le seul problème avec cette approche est que python pense que vous avez installé un package appelé 'data'. Si un autre package que vous avez installé essayait de conditionner les données de la même manière, vous auriez deux packages «data» en conflit installés.
orteils

Réponses:

111

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 que cela vous permet d'éviter de 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 œuf. Vous pouvez toujours trouver le répertoire datarelatif à la racine de votre package Python, peu importe où et comment il est installé.

Par exemple, si j'ai une mise en page de projet comme celle-ci:

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

Vous pouvez ajouter une fonction à __init__.pypour 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 d'accès datachangera, 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, puis soit:

  1. Avoir l'emplacement de datatransmis 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 dataoù vous le souhaitez sur le système cible en spécifiant la destination de 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 de fonction shell pour grep récursivement les fichiers 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
la source
7
Merci beaucoup de m'avoir aidé à accepter la situation. Je suis donc heureux d'utiliser package_data comme vous (et tout le monde) le suggère. Cependant: est-ce seulement moi qui trouve que le fait de mettre leurs données et documents dans le répertoire source de leur package est gênant? (par exemple, grepping ma source renvoie des dizaines de hits indésirables de ma documentation. Je pourrais ajouter les paramètres '--exclude-dir' à grep chaque fois que je l'utilise, ce qui différerait d'un projet à l'autre, mais cela semble dégoûtant) il est possible d'inclure un sous-répertoire 'src' dans mon répertoire de package sans interrompre les importations, etc.
Jonathan Hartley
Je ne mets généralement que les fichiers de données dont le package a besoin sous le répertoire du package. J'installerais les documents en tant que data_files. De plus, vous pouvez créer un alias shell pour que grep ignore les fichiers non Python, quelque chose comme grep_py.
samplebias
Hé samplebias. Merci pour les mises à jour. Ce n'est pas seulement grep, c'est tout , de la recherche dans les fichiers de l'éditeur de texte aux ctags en passant par awk. Je vais essayer de réorganiser mon projet pour mettre des documents dans data_files comme vous le suggérez, voyez comment cela fonctionne. De retour bientôt ... :-)
Jonathan Hartley
... cela semble bien fonctionner. Merci de m'avoir mis sur la bonne voie. Les +50 points de réputation sont-ils savoureux?
Jonathan Hartley
Merci! Super à entendre, heureux que cela a fonctionné et que vous faites des progrès!
samplebias
13

Je pense avoir trouvé un bon compromis qui vous permettra de conserver 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 samplebias, mais pour 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 peuvent absolument rien avoir à faire avec ce module, mais je suppose que c'est un bon compromis.

polvoazul
la source
-4

Je pense que vous pouvez fondamentalement donner n'importe quoi en argument * data_files * à setup () .

lgautier
la source
Hmm ... Je peux voir que c'est dans la documentation distutils, mais je ne peux pas le voir dans la documentation setuptools. Quoi qu'il en soit, comment pourrais-je y accéder éventuellement?
phant0m
Je pense que data_files ne doit être utilisé que pour les données partagées entre plusieurs packages. par exemple, si vous installez à partir de PyPI, les fichiers répertoriés dans data_files sont installés dans les répertoires directement sous votre répertoire d'installation Python principal. (c'est-à-dire pas dans Python27 / Lib / site-packages / mypackage, mais en parallèle avec 'Python27 / Lib')
Jonathan Hartley