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?


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']}

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.


-4

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


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.