如何在Python setup.py中递归添加包数据?

时间:2014-12-27 04:58:36

标签: python distutils setup.py

我有一个新的库,必须包含很多小数据文件的子文件夹,我试图将它们作为包数据添加。想象一下,我有我的图书馆:

 library
    - foo.py
    - bar.py
 data
   subfolderA
      subfolderA1
      subfolderA2
   subfolderB
      subfolderB1 
      ...

我想通过setup.py添加所有子文件夹中的所有数据,但似乎我手动必须进入每个子文件夹(大约有100个)并添加 init .py文件。此外,setup.py会递归地找到这些文件,还是需要在setup.py中手动添加所有这些文件,如:

package_data={
  'mypackage.data.folderA': ['*'],
  'mypackage.data.folderA.subfolderA1': ['*'],
  'mypackage.data.folderA.subfolderA2': ['*']
   },

我可以用脚本做到这一点,但似乎是一种超级痛苦。我怎样才能在setup.py中实现这一点?

PS,这些文件夹的层次结构非常重要,因为这是一个包含材料文件的数据库,我们希望在我们将GUI文件呈现给用户时保留文件树,因此保留这个文件对我们有利。文件结构完好无损。

8 个答案:

答案 0 :(得分:32)

glob答案的问题在于它只做了这么多。即它没有完全递归。 copy_tree答案的问题是复制的文件将在卸载时留下。

正确的解决方案是递归解决方案,您可以在设置调用中设置package_data参数。

我已经写了这个小方法来做到这一点:

import os

def package_files(directory):
    paths = []
    for (path, directories, filenames) in os.walk(directory):
        for filename in filenames:
            paths.append(os.path.join('..', path, filename))
    return paths

extra_files = package_files('path_to/extra_files_dir')

setup(
    ...
    packages = ['package_name'],
    package_data={'': extra_files},
    ....
)

您注意到,当您执行pip uninstall package_name时,您会看到列出的其他文件(与软件包一起跟踪)。

答案 1 :(得分:24)

  1. 使用Setuptools代替distutils。
  2. 使用data files代替包数据。这些不需要__init__.py
  3. 使用标准Python代码生成文件和目录列表,而不是按字面编写:

    data_files = []
    directories = glob.glob('data/subfolder?/subfolder??/')
    for directory in directories:
        files = glob.glob(directory+'*')
        data_files.append((directory, files))
    # then pass data_files to setup()
    

答案 2 :(得分:4)

如果您在使用distutils.dir_util.copy_tree时无法解决setup.py代码问题。 整个问题是如何从中排除文件 下面是一些代码:

import os.path
from distutils import dir_util
from distutils import sysconfig
from distutils.core import setup

__packagename__ = 'x' 
setup(
    name = __packagename__,
    packages = [__packagename__],
)

destination_path = sysconfig.get_python_lib()
package_path = os.path.join(destination_path, __packagename__)

dir_util.copy_tree(__packagename__, package_path, update=1, preserve_mode=0)

一些笔记

  • 此代码以递归方式将源代码复制到目标路径中。
  • 您可以使用相同的setup(...),但可以使用copy_tree()将您想要的目录扩展到安装路径中。
  • distutil安装的默认路径可以在API找到。
  • 有关distutils的copy_tree()模块的更多信息,请访问here

  • 答案 3 :(得分:2)

    我可以建议一些代码在setup()中添加data_files:

    data_files = []
    
    start_point = os.path.join(__pkgname__, 'static')
    for root, dirs, files in os.walk(start_point):
        root_files = [os.path.join(root, i) for i in files]
        data_files.append((root, root_files))
    
    start_point = os.path.join(__pkgname__, 'templates')
    for root, dirs, files in os.walk(start_point):
        root_files = [os.path.join(root, i) for i in files]
        data_files.append((root, root_files))
    
    setup(
        name = __pkgname__,
        description = __description__,
        version = __version__,
        long_description = README,
        ...
        data_files = data_files,
    )
    

    答案 4 :(得分:1)

    使用glob在setup.py中选择所有子文件夹。

    ...
    packages=['your_package'],
    package_data={'your_package': ['data/**/*']},
    ...
    

    答案 5 :(得分:0)

    要使用setup.py中的package_data添加所有子文件夹: 根据您的子目录结构添加*条目数

    package_data={
      'mypackage.data.folderA': ['*','*/*','*/*/*'],
    }
    

    答案 6 :(得分:0)

    我可以使用脚本来执行此操作,但是似乎很痛苦。如何在setup.py中实现此目标?

    这是一种可重用的简单方法:

    在您的setup.py中添加以下函数,并按照“用法”说明进行调用。这实质上是接受答案的通用版本。

    def find_package_data(specs):
        """recursively find package data as per the folders given
    
        Usage:
            # in setup.py
            setup(...
                  include_package_data=True,
                  package_data=find_package_data({
                     'package': ('resources', 'static')
                  }))
    
        Args:
            specs (dict): package => list of folder names to include files from
    
        Returns:
            dict of list of file names
        """
        return {
            package: list(''.join(n.split('/', 1)[1:]) for n in
                          flatten(glob('{}/{}/**/*'.format(package, f), recursive=True) for f in folders))
            for package, folders in specs.items()}
    
    

    答案 7 :(得分:0)

    @gbonetti的answer(使用递归glob模式,即**)将是完美的。

    但是,正如@ daniel-himmelstein所评论的那样,setuptools package_data中的does not work yet

    因此,暂时,我想根据pathlib的{​​{3}}使用以下变通方法:

    def glob_fix(package_name, glob):
        # this assumes setup.py lives in the folder that contains the package
        package_path = Path(f'./{package_name}').resolve()
        return [str(path.relative_to(package_path)) 
                for path in package_path.glob(glob)]
    

    这将返回与包路径有关的路径字符串列表,如Path.glob()

    这是使用它的一种方法:

    setuptools.setup(
        ...
        package_data={'my_package': [*glob_fix('my_package', 'my_data_dir/**/*'), 
                                     'my_other_dir/some.file', ...], ...},
        ...
    )
    

    只要setuptools在glob_fix()中支持**,就可以删除package_data