исходный дистрибутив python (sdist) — сгенерированные файлы данных

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

Я хотел бы создать исходный дистрибутив (setup.py sdist), например, если бы он изначально находился в дереве исходного кода, НО я хочу создавать его не в дереве исходного кода, а в другом месте (желательно построить/сгенерировать), чтобы не загромождать мой исходный код (и случайно его зафиксировать).

Например, в конце концов я хочу иметь файл data.txt в разделе dist_root/generated/data.txt ( «dist_root» — это место, где находится setup.py).

Я использовал инструменты настройки data_files (а не package_data, поскольку эти данные не относятся к пакету) и столкнулся со следующими проблемами:

  1. Если я создаю data.txt при сборке, он обрезается, так как процесс фильтрует любой файл в build_base.
  2. Если я сгенерирую его в какой-нибудь временной папке, скажем, dist_root/temp/data.txt, эта «временная» папка будет связана.

поэтому если я поставлю data_files = [('generated, temp/data.txt)], я получу в раздаче цепочку путей dist_root/generated/temp/data.txt >

Кажется, мой единственный выбор — сгенерировать его в dist_root/generated/data.txt, но опять же, я захламляю свое исходное дерево и не знаю, как его очистить, поскольку эта «сгенерированная» папка имя динамическое.

Есть обходные пути?


person Lior Cohen    schedule 18.04.2018    source источник


Ответы (1)


Предпочтительное решение: записать файлы в исходный каталог, удалить их после завершения sdist

Вы можете переопределить команду sdist, чтобы записывать файлы в исходный каталог и очищать их после завершения команды:

import os
from distutils import dir_util

from setuptools import setup
from setuptools.command.sdist import sdist as sdist_orig


class sdist(sdist_orig):

    def run(self):
        # generate data files
        genbase = os.path.join(os.path.dirname(__file__), 'temp')
        self.mkpath(genbase)
        with open(os.path.join(genbase, 'data.txt'), 'w') as fp:
            fp.write('hello distutils world')
        # run original sdist
        super().run()
        # clean up generated data files
        dir_util.remove_tree(genbase, dry_run=self.dry_run)


setup(
    ...
    data_files=[
        ('generated', ['temp/data.txt']),
    ],
    cmdclass={'sdist': sdist},
)

Создание файлов данных без записи их в исходный каталог

Адаптация исходных метаданных в sdist temp

Несмотря на то, что это достаточно грязно, наименее хакерским способом может быть обновление исходных метаданных непосредственно в каталоге sdist. Таким образом, у вас все еще будут действительные метаданные яйца, и вам не придется иметь дело с отсутствующими исходными файлами на всем пути sdist.

genfiles = ['temp/data.txt']


class sdist(sdist_orig):

    def make_release_tree(self, base_dir, files):
        super().make_release_tree(base_dir, files)
        for path in genfiles:
            fullpath = os.path.join(base_dir, path)
            self.mkpath(os.path.dirname(fullpath))
            if not self.dry_run:
                with open(fullpath, 'w') as fp:
                fp.write('hello distutils world')
            # also adapt source metadata file
            cmd_egg_info = self.get_finalized_command('egg_info')
            sourcemeta = os.path.join(base_dir, 
                                      cmd_egg_info.egg_name + '.egg-info', 
                                      'SOURCES.txt')
            with open(sourcemeta, 'a') as fp:
                fp.write('\n')
                fp.write(path)


setup(
    ...,
    data_files=[
        ('generated', genfiles),
    ],
    cmdclass={'sdist': sdist},
)

По сути, сгенерированные файлы данных просто игнорируются до тех пор, пока не произойдет фактическое копирование исходных файлов. Затем генерируются файлы (вместо копирования существующих файлов), и, поскольку в противном случае исходные метаданные были бы неполными, они обновляются созданными файлами.

Записать несуществующие файлы в метаданные

Я бы настоятельно не рекомендовал этого делать.

Все остальные подходы были бы намного более грязными, так как они записывали бы несуществующие файлы в метаданные и заставляли бы distutils/setuptools игнорировать несуществующие файлы на всем пути генерации исходного дистрибутива. Но если вы настаиваете, вот решение с наименьшим возможным исправлением обезьяны:

genfiles = ['temp/data.txt']


class FileList(setuptools.command.egg_info.FileList):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.files += genfiles

    def _safe_path(self, path):
        return path in genfiles or super()._safe_path(path)


class sdist(sdist_orig):

    def run(self):
        # monkeypatch begin
        FileListOrig = setuptools.command.egg_info.FileList
        setuptools.command.egg_info.FileList = FileList
        # monkeypatch end
        super().run()
        # restore the original class
        setuptools.command.egg_info.FileList = FileListOrig

    def make_release_tree(self, base_dir, files):
        super().make_release_tree(base_dir, files)
        for path in genfiles:
            fullpath = os.path.join(base_dir, path)
            self.mkpath(os.path.dirname(fullpath))
            if not self.dry_run:
                with open(fullpath, 'w') as fp:
                    fp.write('hello distutils world')


setup(
    ...,
    data_files=[
        ('generated', genfiles),
    ],
    cmdclass={'sdist': sdist},
)
person hoefling    schedule 12.05.2018
comment
Спасибо, я знаю об этой душе. Я специально спрашиваю, могу ли я сделать это без записи/генерации в src. - person Lior Cohen; 13.05.2018
comment
Хотя вы можете это сделать, это очень хакерский метод, и я бы не рекомендовал этого делать. Причина этого в том, что setuptools сначала генерирует метаданные яйца, а затем копирует файлы в sdist, поэтому вам придется дополнительно адаптировать команду egg_info, которая может легко сломать ваш рабочий каталог, пока вы не очистите mypkg.egg_info/ вручную. Если вы хотите пойти на этот риск, я могу обновить ответ с помощью прямого подхода. - person hoefling; 13.05.2018
comment
Спасибо. Думаю пойти на вариант записи src с защитой clean и .gitignore. - person Lior Cohen; 13.05.2018