我有一个新库,它必须包含许多小数据文件的子文件夹,我正试图将它们添加为包数据。想象一下我的图书馆是这样的:
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 形式呈现给用户时保留文件树,因此保留此文件结构对我们有利完好无损。
glob
答案的问题在于它只能做这么多。 IE。它不是完全递归的。 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
时,您会看到列出了您的其他文件(与包裹一起跟踪)。
__init__.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()
在
package_data
中使用setup.py
添加所有子文件夹:
根据您的子目录结构添加 *
条目的数量
package_data={
'mypackage.data.folderA': ['*','*/*','*/*/*'],
}
使用 glob 选择你的所有子文件夹
setup.py
:
...
packages=['your_package'],
package_data={'your_package': ['data/**/*']},
...
根据更改日志
setuptools
现在支持递归glob,使用**
,在package_data
(截至v62.3.0
,2022年5月发布)。
@gbonetti 的 answer,使用递归 glob 模式,即
**
,将是完美的。
但是,正如@daniel-himmelstein 所评论的那样,在设置工具中还不起作用
package_data
.
所以,暂时,我喜欢使用以下解决方法,基于
pathlib
的Path.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', ...], ...},
...
)
glob_fix()
可以在 setuptools 支持 **
package_data
后立即删除。
如果你的 setup.py 代码没有任何问题,请使用
distutils.dir_util.copy_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)
一些注意事项:
setup(...)
,但使用copy_tree()
将您想要的目录扩展到安装路径中。我可以建议一些代码来在 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,
)
我可以用脚本做到这一点,但似乎超级痛苦。我怎样才能在 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()}
我会把我的解决方案放在这里,以防有人正在寻找一种干净的方法来将他们编译的狮身人面像文档作为
data_files
.
setup.py
from setuptools import setup
import pathlib
import os
here = pathlib.Path(__file__).parent.resolve()
# Get documentation files from the docs/build/html directory
documentation = [doc.relative_to(here) for doc in here.glob("docs/build/html/**/*") if pathlib.Path.is_file(doc)]
data_docs = {}
for doc in documentation:
doc_path = os.path.join("your_top_data_dir", "docs")
path_parts = doc.parts[3:-1] # remove "docs/build/html", ignore filename
if path_parts:
doc_path = os.path.join(doc_path, *path_parts)
# create all appropriate subfolders and append relative doc path
data_docs.setdefault(doc_path, []).append(str(doc))
setup(
...
include_package_data=True,
# <sys.prefix>/your_top_data_dir
data_files=[("your_top_data_dir", ["data/test-credentials.json"]), *list(data_docs.items())]
)
使用上述解决方案,安装包后,您将在
os.path.join(sys.prefix, "your_top_data_dir", "docs")
获得所有已编译的文档。所以,如果你想使用 nginx 提供现在静态的文档,你可以将以下内容添加到你的 nginx 文件中:
location /docs {
# handle static files directly, without forwarding to the application
alias /www/your_app_name/venv/your_top_data_dir/docs;
expires 30d;
}
完成后,您应该能够访问
{your-domain.com}/docs
并查看您的 Sphinx 文档。
如果您不想添加自定义代码来遍历目录内容,您可以使用
pbr
库,它扩展了 setuptools
。有关如何使用它复制整个目录并保留目录结构的文档,请参见此处:
需要写一个函数返回所有文件及其路径,可以使用如下
def sherinfind():
# Add all folders contain files or other sub directories
pathlist=['templates/','scripts/']
data={}
for path in pathlist:
for root,d_names,f_names in os.walk(path,topdown=True, onerror=None, followlinks=False):
data[root]=list()
for f in f_names:
data[root].append(os.path.join(root, f))
fn=[(k,v) for k,v in data.items()]
return fn
现在更改 setup() 中的 data_files 如下,
data_files=sherinfind()