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

如何在Python setup.py中递归添加包数据?,python,distutils,setup.py,Python,Distutils,Setup.py,我有一个新的库,它必须包含许多小数据文件的子文件夹,我正在尝试将它们添加为包数据。假设我的图书馆是这样的: library - foo.py - bar.py data subfolderA subfolderA1 subfolderA2 subfolderB subfolderB1 ... 我想通过setup.py添加所有子文件夹中的所有数据,但似乎我必须手动进入每个子文件夹(大约有100个)并添加一个init

我有一个新的库,它必须包含许多小数据文件的子文件夹,我正在尝试将它们添加为包数据。假设我的图书馆是这样的:

 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中向用户显示它们时保留文件树,因此保持此文件结构完整对我们有利

  • 使用而不是distutils
  • 使用而不是包数据。这些不需要
    \uuuu init\uuuuu.py
  • 使用标准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()
    

  • 如果将setup.py代码弄脏没有任何问题,请使用
    distutils.dir\u util.copy\u tree

    整个问题是如何从中排除文件。
    下面是一些代码:

    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)
    
    一些注释:
  • 这段代码递归地将源代码复制到目标路径中
  • 您可以使用相同的
    设置(…)
    ,但可以使用
    copy_tree()
    将所需目录扩展到安装路径中
  • distutil安装的默认路径可以在它的

  • 可以找到有关distutils的copy_tree()模块的更多信息。

    glob答案的问题是它只做了这么多。也就是说,它不是完全递归的。
    copy_树
    答案的问题是,卸载时会留下复制的文件

    正确的解决方案是递归的,它允许您在设置调用中设置
    package\u 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卸载程序包\u name
    时,您会看到列出了其他文件(与程序包一起跟踪)。

    我可以建议您使用一些代码在setup()中添加数据文件:


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

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

    要使用setup.py中的package_数据添加所有子文件夹,请执行以下操作: 根据子目录结构添加*条目数

    package_data={
      'mypackage.data.folderA': ['*','*/*','*/*/*'],
    }
    
    我可以用一个脚本来完成这项工作,但这似乎非常痛苦。如何在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()}
    
    
    @gbonetti使用递归全局模式,即
    ***
    ,将是完美的

    然而,正如@daniel himmelstein所评论的,在setuptools
    package\u data

    因此,目前,我喜欢使用以下基于
    pathlib
    的变通方法:

    这将返回相对于包路径的路径字符串列表,如下所示

    这里有一种使用方法:

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

    只要setuptools支持
    **
    中的
    package\u data

    中的
    glob\u fix()
    就可以删除
    。如果以这种方式安装,我可以在名为
    data\u dir
    的路径中访问它们:pkg\u dir=op.abspath(op.dirname(file))data\u dir=op.join(pkg\u dir,'data'),然后我可以在我的程序open('data\u dir/somedatafile,'r')中执行操作,其中data\u dir将引用它们安装的任何位置。而不是
    path.append('../'+os.path.join(path,filename))
    do
    path.append(os.path.join('..',path,filename))
    @madpysicast谢谢。编辑了我的答案。我一直在使用
    os.path.join
    错误。我没有意识到它需要可变数量的参数。谢谢,非常有用。值得注意的是,为了让它工作,我必须通过添加
    directory=str来包含
    setup.py
    文件所在目录的路径(pathlib.Path(uuu file_uuu).parent.absolute())+str(pathlib.Path(directory))
    作为
    包文件(directory)
    方法中的第一行。文档说要始终使用正斜杠,而不是os.Path.join;请参阅此答案澄清数据文件和包文件之间的区别:根据,setuptools不支持递归glob。
    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)]
    
    setuptools.setup(
        ...
        package_data={'my_package': [*glob_fix('my_package', 'my_data_dir/**/*'), 
                                     'my_other_dir/some.file', ...], ...},
        ...
    )