setuptools: percorso della cartella dei dati del pacchetto


94

Uso setuptools per distribuire il mio pacchetto python. Ora ho bisogno di distribuire file di dati aggiuntivi.

Da quello che ho raccolto dalla documentazione di setuptools, ho bisogno di avere i miei file di dati all'interno della directory del pacchetto. Tuttavia, preferirei avere i miei file di dati all'interno di una sottodirectory nella directory principale.

Cosa vorrei evitare:

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

Quello che vorrei avere invece:

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

Semplicemente non mi sento a mio agio con così tante sottodirectory, se non è essenziale. Non riesco a trovare una ragione per cui devo / devo / mettere i file nella directory del pacchetto. È anche scomodo lavorare con così tante sottodirectory nidificate IMHO. O c'è qualche buona ragione che giustifichi questa restrizione?


8
Ho posto una domanda simile sull'utilizzo di 'data_files' per distribuire risorse (documenti, immagini, ecc.): Stackoverflow.com/questions/5192386/… ... e le (due) risposte hanno entrambe detto di usare invece 'package_data'. Ora sto usando i dati del pacchetto, ma questo implica che devo mettere i miei dati e documenti all'interno del mio pacchetto, cioè mescolati nel mio codice sorgente. Non mi piace questo. Quando raccolgo la mia fonte, trovo non solo la definizione di classe che sto cercando, ma anche le dozzine di menzioni che ottengono nei miei file RST, HTML e intermedi. :-(
Jonathan Hartley

2
So che questa risposta è molto tarda, @JonathanHartley, ma puoi rendere qualsiasi directory un "pacchetto" aggiungendo un __init__.pyfile, anche se quel file è vuoto. Quindi potresti mantenere una directory di dati separata con un __init__.pyfile vuoto per farlo sembrare un pacchetto. Ciò dovrebbe impedire a grep all'interno del tuo albero dei sorgenti di raccoglierli, ma sarà comunque riconosciuto come pacchetto da Python e dai suoi strumenti di compilazione.
dhj

@dhj Un'idea interessante, grazie.
Jonathan Hartley,

4
@dhj l'unico problema con questo approccio è che python pensa che tu abbia installato un pacchetto chiamato "data". Se un altro pacchetto installato provasse a creare un pacchetto di dati nello stesso modo, verranno installati due pacchetti "dati" in conflitto.
dita dei piedi

Risposte:


111

Opzione 1: installa come dati del pacchetto

Il vantaggio principale di posizionare i file di dati all'interno della radice del tuo pacchetto Python è che ti consente di evitare di preoccuparti di dove vivranno i file sul sistema di un utente, che può essere Windows, Mac, Linux, qualche piattaforma mobile o all'interno di un Egg. Puoi sempre trovare la directory datarelativa alla radice del tuo pacchetto Python, indipendentemente da dove o come sia installato.

Ad esempio, se ho un layout di progetto in questo modo:

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

È possibile aggiungere una funzione a __init__.pyper individuare un percorso assoluto a un file di dati:

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

Uscite:

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

Dopo che il progetto è stato installato come Egg, il percorso datacambierà, ma non è necessario modificare il codice:

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

Opzione 2: installazione in una posizione fissa

L'alternativa sarebbe inserire i dati fuori dal pacchetto Python e poi:

  1. Avere il percorso di datapassato tramite un file di configurazione, argomenti della riga di comando o
  2. Incorpora la posizione nel tuo codice Python.

Questo è molto meno desiderabile se prevedi di distribuire il tuo progetto. Se vuoi davvero farlo, puoi installarlo datadove preferisci sul sistema di destinazione specificando la destinazione per ogni gruppo di file passando un elenco di tuple:

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

Aggiornato : esempio di una funzione di shell per eseguire il grep ricorsivo di file Python:

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

7
Grazie mille per avermi aiutato a venire a patti con la situazione. Quindi sono felice di utilizzare package_data come suggerisci tu (e tutti gli altri). Tuttavia: sono solo io che trovo scomodo l'inserimento di dati e documenti nella directory dei sorgenti del pacchetto? (es. grepping la mia fonte restituisce dozzine di hit indesiderati dalla mia documentazione. Potrei aggiungere i parametri '--exclude-dir' a grep ogni volta che lo uso, il che differirebbe da un progetto all'altro, ma sembra icky) è possibile qualcosa come includere una sottodirectory 'src' nella directory del mio pacchetto senza interrompere le importazioni, ecc.
Jonathan Hartley

Di solito metto solo i file di dati richiesti dal pacchetto nella directory del pacchetto. Installerei i documenti come data_files. Inoltre, potresti trovare un alias di shell per grep per ignorare i file non Python, qualcosa come grep_py.
samplebias

Ehi samplebias. Grazie per gli aggiornamenti. Tuttavia, non è solo grep, è tutto , dalla ricerca nei file dell'editor di testo ai ctags fino ad awk. Proverò a rifare il mio progetto per inserire i documenti in data_files come suggerisci, guarda come funziona. Di ritorno presto ... :-)
Jonathan Hartley

... sembra che funzioni bene. Grazie per avermi messo sulla strada giusta. I +50 punti reputazione sono gustosi?
Jonathan Hartley

Grazie! Bello sentire, felice che abbia funzionato e che tu stia facendo progressi!
samplebias

13

Penso di aver trovato un buon compromesso che ti permetterà di mantenere la seguente struttura:

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

Dovresti installare i dati come package_data, per evitare i problemi descritti nella risposta samplebias, ma per mantenere la struttura del file dovresti aggiungere al tuo setup.py:

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

In questo modo creiamo la struttura appropriata "just in time" e manteniamo organizzato il nostro albero dei sorgenti.

Per accedere a tali file di dati all'interno del codice, si utilizza "semplicemente":

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

Non mi piace ancora dover specificare "mypackage" nel codice, poiché i dati potrebbero non avere nulla a che fare necessariamente con questo modulo, ma immagino sia un buon compromesso.


-4

Penso che puoi fondamentalmente dare qualsiasi cosa come argomento * data_files * a setup () .


Hmm ... posso vedere che è nella documentazione di distutils, ma non riesco a vederlo nella documentazione di setuptools. Ad ogni modo, come potrei accedervi alla fine?
phant0m

Penso che data_files dovrebbe essere usato solo per i dati condivisi tra diversi pacchetti. per esempio, se installi pip da PyPI, i file elencati in data_files vengono installati nelle directory direttamente sotto la directory di installazione principale di Python. (cioè non in Python27 / Lib / site-packages / mypackage, ma in parallelo con 'Python27 / Lib')
Jonathan Hartley
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.