setuptools: расположение папки с данными пакета

98

Я использую setuptools для распространения моего пакета python. Теперь мне нужно распространить дополнительные файлы данных.

Из того, что я собрал из документации setuptools, мне нужно, чтобы мои файлы данных находились внутри каталога пакета. Однако я бы предпочел, чтобы мои файлы данных находились в подкаталоге корневого каталога.

Чего бы я хотел избежать:

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

Вместо этого я бы хотел:

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

Мне просто неудобно иметь такое количество подкаталогов, если это не важно. Я не могу найти причину, по которой я / должен / помещать файлы в каталог пакета. Также неудобно работать с таким количеством вложенных подкаталогов IMHO. Или есть веская причина, оправдывающая это ограничение?

phant0m
источник
9
Я задал аналогичный вопрос об использовании data_files для распространения ресурсов (документов, изображений и т. Д.): Stackoverflow.com/questions/5192386/… ... и в (двух) ответах было сказано использовать вместо этого package_data. Теперь я использую данные пакета, но это означает, что я должен поместить свои данные и документы в свой пакет, то есть смешать их с моим исходным кодом. Мне это не нравится. При поиске своего источника я нахожу не только определение класса, которое ищу, но и десятки упоминаний, которые они получают в моих RST, HTML и промежуточных файлах. :-(
Джонатан Хартли
2
Я знаю, что этот ответ пришел очень поздно, @JonathanHartley, но вы можете сделать любой каталог «пакетом», добавив __init__.pyфайл, даже если этот файл пустой. Таким образом, вы можете сохранить каталог данных отдельно от пустого __init__.pyфайла, чтобы он выглядел как пакет. Это должно удерживать grep от вашего исходного дерева от их сбора, но он все равно будет распознаваться как пакет python и его инструментами сборки.
dhj
@dhj Интересная идея, спасибо.
Джонатан Хартли
4
@dhj единственная проблема с этим подходом заключается в том, что python считает, что вы установили пакет с именем 'data'. Если другой установленный вами пакет попытается упаковать данные таким же образом, у вас будут установлены два конфликтующих пакета «данных».
toes

Ответы:

112

Вариант 1. Установить как данные пакета

Основное преимущество размещения файлов данных в корне вашего пакета Python заключается в том, что это позволяет вам не беспокоиться о том, где файлы будут находиться в системе пользователя, которая может быть Windows, Mac, Linux, какой-либо мобильной платформой или внутри Egg. Вы всегда можете найти каталог dataотносительно корня вашего пакета Python, независимо от того, где и как он установлен.

Например, если у меня такой макет проекта:

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

Вы можете добавить функцию для __init__.pyопределения абсолютного пути к файлу данных:

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

Выходы:

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

После того, как проект будет установлен как Egg, путь к нему dataизменится, но код менять не нужно:

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

Вариант 2: установка в фиксированное место

Альтернативой может быть размещение ваших данных вне пакета Python, а затем либо:

  1. Передать местоположение dataчерез файл конфигурации, аргументы командной строки или
  2. Вставьте местоположение в свой код Python.

Это гораздо менее желательно, если вы планируете распространять свой проект. Если вы действительно хотите это сделать, вы можете установить его в dataлюбом месте целевой системы, указав место назначения для каждой группы файлов, передав список кортежей:

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

Обновлено : пример функции оболочки для рекурсивного поиска файлов 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
Большое спасибо за то, что помог мне разобраться в ситуации. Так что я счастлив работать с использованием package_data, как вы (и все остальные) предлагаете. Однако: только я считаю, что размещение их данных и документов в исходном каталоге пакета неудобно? (например, grepping my source возвращает десятки нежелательных совпадений из моей документации. Я мог бы добавлять параметры '--exclude-dir' в grep каждый раз, когда я когда-либо его использую, которые будут отличаться от одного проекта к другому, но это кажется неприятным) можно что-то вроде включения подкаталога 'src' в мой каталог пакетов без нарушения импорта и т. д.
Джонатан Хартли,
Обычно я помещаю только те файлы данных, которые требуются для пакета, в каталог пакета. Я бы установил документы как data_files. Кроме того, вы можете придумать псевдоним оболочки для grep, чтобы игнорировать файлы, отличные от Python, например grep_py.
samplebias
Привет, сэмплер. Спасибо за обновления. Это не просто grep, это все , от поиска в файлах текстового редактора до ctags и awk. Я собираюсь попробовать изменить структуру своего проекта, чтобы поместить документы в data_files, как вы предлагаете, посмотрите, как это сработает. Скоро вернусь ... :-)
Джонатан Хартли
... кажется, все в порядке. Спасибо, что наставили меня на правильный путь. +50 очков репутации вкусны?
Джонатан Хартли
Благодарность! Приятно слышать, рад, что все получилось, и вы добиваетесь прогресса!
samplebias
14

Думаю, я нашел хороший компромисс, который позволит вам сохранить следующую структуру:

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

Вы должны установить данные как package_data, чтобы избежать проблем, описанных в ответе samplebias, но чтобы сохранить файловую структуру, вы должны добавить в свой setup.py:

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

Таким образом, мы создаем соответствующую структуру «точно в срок» и поддерживаем организованное дерево исходных текстов.

Чтобы получить доступ к таким файлам данных в вашем коде, вы «просто» используете:

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

Мне все еще не нравится указывать mypackage в коде, поскольку данные не могут иметь ничего общего с этим модулем, но я думаю, что это хороший компромисс.

полвоазул
источник
-4

Я думаю, что вы можете в принципе указать что угодно в качестве аргумента * data_files * для setup () .

lgautier
источник
Хм ... Я вижу, что это есть в документации distutils, но не вижу в документации setuptools. В любом случае, как я смогу получить к нему доступ?
phant0m
Я думаю, что data_files следует использовать только для данных, которые используются несколькими пакетами. например, если вы устанавливаете pip из PyPI, то файлы, перечисленные в data_files, устанавливаются в каталоги непосредственно в вашем основном каталоге установки Python. (т.е. не в Python27 / Lib / site-packages / mypackage, а параллельно с Python27 / Lib)
Джонатан Хартли,