setuptools: lokalizacja folderu danych pakietu

98

Używam setuptools do dystrybucji mojego pakietu Pythona. Teraz muszę rozprowadzić dodatkowe pliki danych.

Z tego, co zebrałem z dokumentacji setuptools, muszę mieć moje pliki danych w katalogu pakietu. Jednak wolałbym, aby moje pliki danych znajdowały się w podkatalogu w katalogu głównym.

Czego chciałbym uniknąć:

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

Co chciałbym zamiast tego mieć:

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

Po prostu nie czuję się komfortowo mając tak wiele podkatalogów, jeśli nie jest to konieczne. Nie mogę znaleźć powodu, dla którego / mam / umieszczam pliki w katalogu pakietu. Praca z tak wieloma zagnieżdżonymi podkatalogami IMHO jest również uciążliwa. A może jest jakiś dobry powód, który uzasadniałby to ograniczenie?

phant0m
źródło
9
Poprosiłem podobne pytanie o używaniu „data_files” do dystrybucji zasobów (Dokumenty, zdjęcia, itp): stackoverflow.com/questions/5192386/... ... a (dwa) reakcje zarówno powiedział w użyciu „package_data” zamiast. Teraz używam danych pakietu, ale to oznacza, że ​​muszę umieścić moje dane i dokumenty wewnątrz pakietu, tj. Wymieszane z kodem źródłowym. Nie lubię tego. Podczas grepowania mojego źródła znajduję nie tylko definicję klasy, której szukam, ale także dziesiątki wzmianek, które otrzymują w moich plikach RST, HTML i pośrednich. :-(
Jonathan Hartley
2
Wiem, że ta odpowiedź jest bardzo późna, @JonathanHartley, ale możesz uczynić z dowolnego katalogu „pakiet”, dodając __init__.pyplik, nawet jeśli ten plik jest pusty. Możesz więc oddzielić katalog danych pustym __init__.pyplikiem, aby wyglądał jak pakiet. Powinno to uniemożliwić grepowi pobranie ich z drzewa źródłowego, ale nadal będzie rozpoznawany jako pakiet przez Pythona i jego narzędzia do budowania.
dhj
@dhj Ciekawy pomysł, dzięki.
Jonathan Hartley,
4
@dhj jedynym problemem związanym z tym podejściem jest to, że Python myśli, że zainstalowałeś pakiet o nazwie „dane”. Jeśli inny zainstalowany pakiet próbowałby spakować dane w ten sam sposób, zostałyby zainstalowane dwa sprzeczne pakiety „danych”.
palce stóp

Odpowiedzi:

112

Opcja 1: Zainstaluj jako dane pakietu

Główną zaletą umieszczania plików danych w katalogu głównym pakietu Pythona jest to, że pozwala uniknąć martwienia się o to, gdzie pliki będą znajdować się w systemie użytkownika, którym może być Windows, Mac, Linux, jakaś platforma mobilna lub wewnątrz Egg. Zawsze możesz znaleźć katalog datawzględem katalogu głównego pakietu Pythona, bez względu na to, gdzie i jak jest zainstalowany.

Na przykład, jeśli mam taki układ projektu:

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

Możesz dodać funkcję do, __init__.pyaby zlokalizować bezwzględną ścieżkę do pliku danych:

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

Wyjścia:

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

Po zainstalowaniu projektu jako Egg ścieżka do datazmieni się, ale kod nie musi się zmieniać:

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

Opcja 2: Zainstaluj w stałej lokalizacji

Alternatywą byłoby umieszczenie danych poza pakietem Pythona, a następnie:

  1. Miej lokalizację dataprzekazaną za pośrednictwem pliku konfiguracyjnego, argumentów wiersza poleceń lub
  2. Osadź lokalizację w kodzie Pythona.

Jest to znacznie mniej pożądane, jeśli planujesz rozpowszechniać swój projekt. Jeśli naprawdę chcesz to zrobić, możesz zainstalować swój w datadowolnym miejscu w systemie docelowym, określając miejsce docelowe dla każdej grupy plików, przekazując listę krotek:

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

Zaktualizowano : Przykład funkcji powłoki rekurencyjnej do plików grep 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
źródło
7
Bardzo dziękuję za pomoc w uporaniu się z sytuacją. Więc cieszę się, że mogę korzystać z package_data, jak sugerujesz (i wszyscy inni). Jednak: czy tylko ja uważam, że umieszczanie ich danych i dokumentów w katalogu źródłowym pakietu jest niewygodne? (np. grepowanie mojego źródła zwraca dziesiątki niechcianych trafień z mojej dokumentacji. Mogę dodać parametry '--exclude-dir' do grep za każdym razem, gdy go używam, co różni się w zależności od projektu, ale wydaje się to nieprzyjemne) możliwe jest dodanie podkatalogu „src” do katalogu mojego pakietu bez przerywania importu itp.
Jonathan Hartley
Zwykle umieszczam tylko pliki danych, których wymaga pakiet, pod katalogiem pakietu. Zainstalowałbym dokumenty jako data_files. Możesz także wymyślić alias powłoki dla grep, aby ignorować pliki spoza Pythona, na przykład grep_py.
samplebias,
Hej samplebias. Dzięki za aktualizacje. Jednak to nie tylko grep, to wszystko , od wyszukiwania w plikach edytora tekstu, przez ctagi, po awk. Spróbuję przeorganizować mój projekt, aby umieścić dokumenty w data_files, jak sugerujesz, zobacz, jak to działa. Wracam wkrótce ... :-)
Jonathan Hartley
... wydaje się, że działa OK. Dzięki za ustawienie mnie na właściwej drodze. Czy +50 punktów reputacji jest smaczne?
Jonathan Hartley,
Dzięki! Dobrze to słyszeć, cieszę się, że się udało i robisz postępy!
samplebias
14

Myślę, że znalazłem dobry kompromis, który pozwoli Ci zachować następującą strukturę:

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

Powinieneś zainstalować dane jako package_data, aby uniknąć problemów opisanych w przykładowej odpowiedzi bias, ale aby zachować strukturę pliku, powinieneś dodać do swojego setup.py:

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

W ten sposób tworzymy odpowiednią strukturę „just in time” i utrzymujemy uporządkowane drzewo źródłowe.

Aby uzyskać dostęp do takich plików danych w swoim kodzie, „po prostu” użyj:

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

Nadal nie lubię określać „mypackage” w kodzie, ponieważ dane mogą nie mieć nic wspólnego z tym modułem, ale myślę, że to dobry kompromis.

polvoazul
źródło
-4

Myślę, że w zasadzie możesz podać wszystko jako argument * data_files * do setup () .

lgautier
źródło
Hmm ... widzę, że jest w dokumentacji distutils, ale nie widzę tego w dokumentacji setuptools. Zresztą, jak w końcu będę mógł uzyskać do niego dostęp?
phant0m
Myślę, że data_files powinna być używana tylko do danych, które są udostępniane między kilkoma pakietami. na przykład, jeśli instalujesz pip z PyPI, wtedy pliki wymienione w data_files są instalowane w katalogach bezpośrednio w głównym katalogu instalacyjnym Pythona. (tj. nie w Python27 / Lib / site-packages / mypackage, ale równolegle z 'Python27 / Lib')
Jonathan Hartley