setuptools: Speicherort des Paketdatenordners

93

Ich benutze Setuptools, um mein Python-Paket zu verteilen. Jetzt muss ich zusätzliche Datendateien verteilen.

Nach dem, was ich aus der setuptools-Dokumentation zusammengetragen habe, muss ich meine Datendateien im Paketverzeichnis haben. Ich möchte meine Datendateien jedoch lieber in einem Unterverzeichnis im Stammverzeichnis haben.

Was ich vermeiden möchte:

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

Was ich stattdessen gerne hätte:

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

Ich fühle mich einfach nicht wohl mit so vielen Unterverzeichnissen, wenn es nicht wesentlich ist. Ich finde keinen Grund, warum ich die Dateien im Paketverzeichnis ablegen muss. Es ist meiner Meinung nach auch umständlich, mit so vielen verschachtelten Unterverzeichnissen zu arbeiten. Oder gibt es einen guten Grund, der diese Einschränkung rechtfertigen würde?

phant0m
quelle
7
Ich habe eine ähnliche Frage zur Verwendung von 'Datendateien' zum Verteilen von Ressourcen (Dokumente, Bilder usw.) gestellt: stackoverflow.com/questions/5192386/… ... und die (zwei) Antworten, die beide angeblich 'package_data' verwenden. Jetzt verwende ich Paketdaten, aber das bedeutet, dass ich meine Daten und Dokumente in mein Paket einfügen muss, dh unter meinen Quellcode mischen. Ich mag das nicht. Beim Durchsuchen meiner Quelle finde ich nicht nur die Klassendefinition, nach der ich suche, sondern auch die Dutzende von Erwähnungen, die sie in meinen RST-, HTML- und Zwischendateien erhalten. :-(
Jonathan Hartley
2
Ich weiß, dass diese Antwort sehr spät ist, @JonathanHartley, aber Sie können jedes Verzeichnis zu einem "Paket" machen, indem Sie eine __init__.pyDatei hinzufügen , auch wenn diese Datei leer ist. Sie können also ein Datenverzeichnis mit einer leeren __init__.pyDatei getrennt halten , damit es wie ein Paket aussieht. Das sollte verhindern, dass grep aus Ihrem Quellbaum sie aufnimmt, aber es wird von Python und seinen Build-Tools weiterhin als Paket erkannt.
Dhj
@dhj Eine interessante Idee, danke.
Jonathan Hartley
4
@dhj Das einzige Problem bei diesem Ansatz ist, dass Python glaubt, Sie hätten ein Paket namens 'data' installiert. Wenn ein anderes Paket, das Sie installiert haben, versucht hat, Daten auf die gleiche Weise zu verpacken, sind zwei widersprüchliche Datenpakete installiert.
Zehen

Antworten:

111

Option 1: Als Paketdaten installieren

Der Hauptvorteil des Platzierens von Datendateien im Stammverzeichnis Ihres Python-Pakets besteht darin, dass Sie sich keine Gedanken darüber machen müssen, wo sich die Dateien auf dem System eines Benutzers befinden, z. B. Windows, Mac, Linux, eine mobile Plattform oder in einem Ei. Sie können das Verzeichnis immer datarelativ zu Ihrem Python-Paketstamm finden, unabhängig davon, wo oder wie es installiert ist.

Zum Beispiel, wenn ich ein Projektlayout wie dieses habe:

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

Sie können eine Funktion hinzufügen __init__.py, um einen absoluten Pfad zu einer Datendatei zu finden:

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

Ausgänge:

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

Nachdem das Projekt als Ei installiert wurde, dataändert sich der Pfad zu , aber der Code muss nicht geändert werden:

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

Option 2: Installation an einem festen Ort

Die Alternative wäre, Ihre Daten außerhalb des Python-Pakets zu platzieren und dann entweder:

  1. Lassen Sie den Speicherort dataüber eine Konfigurationsdatei, Befehlszeilenargumente oder übergeben
  2. Betten Sie den Speicherort in Ihren Python-Code ein.

Dies ist weitaus weniger wünschenswert, wenn Sie Ihr Projekt verteilen möchten. Wenn Sie dies wirklich tun möchten, können Sie Ihre dataDatei auf dem Zielsystem installieren, wo immer Sie möchten, indem Sie das Ziel für jede Dateigruppe angeben, indem Sie eine Liste mit Tupeln übergeben:

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

Aktualisiert : Beispiel einer Shell-Funktion zum rekursiven Grepen von Python-Dateien:

atlas% function grep_py { find . -name '*.py' -exec grep -Hn $* {} \; }
atlas% grep_py ": \["
./setup.py:9:    package_data={'foo': ['data/resource1/foo.txt']}
samplebias
quelle
5
Vielen Dank, dass Sie mir geholfen haben, mit der Situation fertig zu werden. Daher bin ich froh, mit package_data arbeiten zu können, wie Sie (und alle anderen) vorschlagen. Allerdings: Ist es nur mir unangenehm, ihre Daten und Dokumente in ihrem Paketquellverzeichnis abzulegen? (Wenn ich z. B. meine Quelle greife, werden Dutzende unerwünschter Treffer aus meiner Dokumentation zurückgegeben. Ich könnte jedes Mal, wenn ich sie verwende, '--exclude-dir'-Parameter hinzufügen, um sie zu grepen, was sich von Projekt zu Projekt unterscheiden würde, aber das scheint schwierig zu sein.) Es ist möglich, so etwas wie ein 'src'-Unterverzeichnis in mein Paketverzeichnis aufzunehmen, ohne Importe usw. zu unterbrechen
Jonathan Hartley
Normalerweise füge ich nur Datendateien, die das Paket benötigt, unter das Paketverzeichnis ein. Ich würde die Dokumente als installieren data_files. Sie können auch einen Shell-Alias ​​für grep erstellen, um Nicht-Python-Dateien zu ignorieren grep_py.
samplebias
Hey samplebias. Danke für die Updates. Es ist jedoch nicht nur grep, es ist alles , von der Suche nach Dateien im Texteditor über ctags bis hin zu awk. Ich werde versuchen, mein Projekt neu zu ordnen, um Dokumente in data_files zu speichern, wie Sie vorschlagen. Sehen Sie, wie das funktioniert. Bald zurück ... :-)
Jonathan Hartley
... das scheint in Ordnung zu sein. Danke, dass du mich auf den richtigen Weg gebracht hast. Sind die +50 Reputationspunkte lecker?
Jonathan Hartley
Vielen Dank! Schön zu hören, froh, dass es geklappt hat und Sie Fortschritte machen!
samplebias
13

Ich glaube, ich habe einen guten Kompromiss gefunden, mit dem Sie die folgende Struktur beibehalten können:

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

Sie sollten Daten als package_data installieren, um die in samplebias answer beschriebenen Probleme zu vermeiden. Um jedoch die Dateistruktur beizubehalten, sollten Sie Ihre setup.py hinzufügen:

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

Auf diese Weise erstellen wir "just in time" die entsprechende Struktur und pflegen unseren Quellbaum organisiert.

Um auf solche Datendateien in Ihrem Code zuzugreifen, verwenden Sie "einfach":

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

Ich mag es immer noch nicht, 'mypackage' im Code angeben zu müssen, da die Daten mit diesem Modul nicht unbedingt etwas zu tun haben könnten, aber ich denke, es ist ein guter Kompromiss.

polvoazul
quelle
-4

Ich denke, dass Sie setup () grundsätzlich alles als Argument * data_files * geben können .

lgautier
quelle
Hmm ... Ich kann sehen, dass es in der distutils-Dokumentation ist, kann es aber nicht in der setuptools-Dokumentation sehen. Wie würde ich irgendwann darauf zugreifen können?
Phant0m
Ich denke, data_files sollten nur für Daten verwendet werden, die von mehreren Paketen gemeinsam genutzt werden. Wenn Sie beispielsweise die Installation von PyPI aus durchführen, werden die in data_files aufgelisteten Dateien in Verzeichnissen direkt unter Ihrem Hauptinstallationsverzeichnis für Python installiert. (dh nicht in Python27 / Lib / site-packages / mypackage, sondern parallel zu 'Python27 / Lib')
Jonathan Hartley