Программно определить расположение файлов данных distutils в Python

Я пытаюсь включить файлы данных в distutils для своего пакета, а затем ссылаюсь на них с использованием относительных путей (следуя http://docs.python.org/distutils/setupscript.html#distutils-additional-files)

Моя структура:

myproject/
  mycode.py
  data/
    file1.dat

код в mycode.py, который на самом деле является script в пакете. Он полагается на доступ к data/file1.dat, ссылаясь на него, используя этот относительный путь. В setup.py у меня есть:

setup(
 ...
 scripts = "myproject/mycode.py"
 data_files = [('data', 'myproject/data/file1.dat')]
)

Предположим, что пользователь теперь использует:

python setup.py --prefix=/home/user/

Тогда mycode.py появится в некотором месте, как /home/user/bin/. Но ссылка на data/file1.dat теперь сломана, так как script живет в другом месте от данных.

Как узнать, от mycode.py, абсолютный путь к myproject/data/file1.dat, поэтому я могу правильно обращаться к нему в зависимости от того, где пользователь установил пакет?

ИЗМЕНИТЬ
Когда я устанавливаю это с помощью prefix=/home/user/, я получаю data/file1.dat, созданный в /home/user/, который является именно тем, что я хочу, единственная недостающая часть - это то, как получить абсолютный путь к этому файлу программно, учитывая только относительный путь и не зная где пользователь установил пакет. Когда я пытаюсь использовать package_data вместо data_files, он не работает - я просто не получаю data/file1.dat, созданный где угодно, даже если я удалю файл MANIFEST.

Я прочитал все текущие обсуждения этой, по-видимому, очень распространенной проблемы. Однако все предлагаемые решения не имеют отношения к случаю, когда у меня есть выше, , где код, которому необходимо получить доступ к data_files, является script, и его местоположение может измениться в зависимости от аргумента --prefix до setup.py. Единственное, что я могу решить, это добавить файл данных в scripts= в setup(), как в:

setup(
  ...
  scripts = ["myproject/mycode.py", "myproject/data/file1.data"]
)

Это ужасный взлом, но это единственный способ, которым я могу думать, чтобы file1.data был в том же месте, что и скрипты, определенные в scripts=, так как я не могу найти какой-либо независимый от платформы и чувствительный к установке API для восстановления местоположения data_files после запуска пользователем setup.py install (возможно, с аргументами --prefix=).

Ответ 1

Я думаю, что путаница возникает из-за использования скриптов. Сценарии должны ссылаться на исполняемый исполняемый файл, возможно, утилиту script, связанную с вашим пакетом или, возможно, точку входа в функциональность вашего пакета. В любом случае вы должны ожидать, что любые скрипты не будут установлены вместе с остальной частью вашего пакета. Это ожидание объясняется главным образом тем, что пакеты считаются библиотеками (и устанавливаются в каталоги lib), тогда как сценарии считаются исполняемыми (и устанавливаются в каталоги bin или скриптов). Кроме того, файлы данных не являются ни исполняемыми файлами, ни библиотеками, а являются полностью отдельными.

Итак, из script вам нужно определить, где находятся файлы данных. В соответствии с документами Python,

Если каталог является относительным путем, он интерпретируется относительно установочный префикс.

Следовательно, вы должны написать что-то вроде следующего в mycode script, чтобы найти файл данных:

import sys
import os

def my_func():
    with open(os.path.join(sys.prefix, 'data', 'file1.dat')) as f:
        print(next(f))

if __name__ == '__main__':
    my_func()

Если вам не нравится, что ваш код и данные не объединены вместе (и я бы этого не сделал), я бы реструктурировал ваш пакет, чтобы у вас был реальный пакет (и модуль) Python и используйте пакеты = и package_data = для ввода данных в пакет, а затем создать простой script, который вызывает модуль в пакете.

Я сделал это, создав это дерево:

.
│   setup.py
│
├───myproject
│   │   mycode.py
│   │   __init__.py
│   │
│   └───data
│           file1.dat
│
└───scripts
        run-my-code.py

С setup.py:

from distutils.core import setup

setup(
    name='myproject',
    version='1.0',
    scripts=['scripts/run-my-code.py'],
    packages=['myproject'],
    package_data = {
        'myproject': ['data/file1.dat'],
    },
)

run-my-code.py просто:

from myproject import mycode

mycode.my_func()

__init__ пуст, а mycode.py выглядит так:

import os

here = os.path.dirname(__file__)

def my_func():
    with open(os.path.join(here, 'data', 'file1.dat')) as f:
        print(next(f))

Этот последний подход хранит данные и код вместе (в site-packages/myproject) и устанавливает только script в другом месте (поэтому он отображается в $PATH).

Ответ 2

Вы можете использовать pkg_resources.resource_filename, чтобы получить имя файла в ваших файлах data_files.

Ответ 3

Для решения, которое будет хорошо работать внутри/снаружи virtualenv в Windows/Linux pip import и os запустите:

os.path.split(os.path.split(pip.__file__)[0])[0]

Полный пример

from setuptools import setup, find_packages
from os import path
from functools import partial
from pip import __file__ as pip_loc


if __name__ == '__main__':
    package_name = 'gen'

    templates_join = partial(path.join, path.dirname(__file__),
                             package_name, 'templates')
    install_to = path.join(path.split(path.split(pip_loc)[0])[0],
                           package_name, 'templates')

    setup(
        name=package_name,
        version='0.0.1',
        test_suite=package_name + '.tests',
        packages=find_packages(),
        package_dir={package_name: package_name},
        data_files=[(install_to, [templates_join('.gitignore'),
                                  templates_join('logging.conf')])]
    )

Ссылка (моя собственная): fooobar.com/info/526338/...