如何使setup.py
包含不属于代码的文件?(具体来说,它是一个许可文件,但也可以是其他任何东西。)
我希望能够控制文件的位置。在原始源文件夹中,该文件位于包的根目录中。(即与最顶层处于同一级别__init__.py
。)无论操作系统如何,我都希望它在安装软件包时完全保留在那里。我怎么做?
可能最好的方法是使用该setuptools
package_data
指令。这确实意味着使用setuptools
(or distribute
) 而不是distutils
,但这是一个非常无缝的“升级”。
这是一个完整的(但未经测试的)示例:
from setuptools import setup, find_packages
setup(
name='your_project_name',
version='0.1',
description='A description.',
packages=find_packages(exclude=['ez_setup', 'tests', 'tests.*']),
package_data={'': ['license.txt']},
include_package_data=True,
install_requires=[],
)
请注意此处至关重要的特定行:
package_data={'': ['license.txt']},
include_package_data=True,
package_data
是一个dict
包名(空 = 所有包)到一个模式列表(可以包括 glob)。例如,如果您只想指定包中的文件,您也可以这样做:
package_data={'yourpackage': ['*.txt', 'path/to/resources/*.txt']}
这里的解决方案绝对不是用扩展名重命名您的非py
文件。.py
有关更多信息,请参阅Ian Bicking 的演示文稿。
如果您只想控制源代码分发的内容sdist
(MANIFEST.in
有关此文件的格式,请参阅Python 文档。
自从写了这个回复后,我发现使用MANIFEST.in
通常是一种不那么令人沮丧的方法,它只是确保你的源代码分发 ( tar.gz
) 有你需要的文件。
例如,如果你想包含requirements.txt
from 顶层,递归地包含顶层的“data”目录:
include requirements.txt
recursive-include data *
然而,为了在安装时将这些文件复制到站点包内的包文件夹中,您需要提供include_package_data=True
给该setup()
函数。有关详细信息,请参阅添加非代码文件。
要完成你所描述的将需要两个步骤......
第 1 步:要将文件添加到源 tarball,请将其包含在 MANIFEST 中
在包含 setup.py 的文件夹中创建一个MANIFEST模板
MANIFEST 基本上是一个文本文件,其中包含将包含在源 tarball 中的所有文件的列表。
这是我的项目的清单的样子:
注意:虽然sdist确实会自动添加一些文件,但我更愿意明确指定它们以确保而不是预测它做什么和不做什么。
第 2 步:要将数据文件安装到源文件夹,请修改 setup.py
由于您希望将数据文件 (LICENSE.txt) 添加到源安装文件夹,因此您需要修改数据安装路径以匹配源安装路径。这是必要的,因为默认情况下,数据文件安装到与源文件不同的位置。
要修改数据安装目录以匹配源安装目录...
从 distutils 中提取安装目录信息:
from distutils.command.install import INSTALL_SCHEMES
修改数据安装目录以匹配源安装目录:
for scheme in INSTALL_SCHEMES.values():
scheme['data'] = scheme['purelib']
并且,将数据文件和位置添加到 setup():
data_files=[('', ['LICENSE.txt'])]
注意:上述步骤应该完全按照您以标准方式描述的内容,而不需要任何扩展库。
现在是 2019 年,这就是有效的方法 - 尽管到处都有建议,但我在互联网上发现的中途记录的是 using setuptools_scm
,作为选项传递给setuptools.setup
. 这将包括在您的 VCS 上进行版本控制的任何数据文件,无论是 git 还是其他任何数据文件,都将包含在 wheel 包中,并将从 git 存储库进行“pip install”以将这些文件带到一起。
所以,我只是将这两行添加到“setup.py”的设置调用中。无需额外安装或导入:
setup_requires=['setuptools_scm'],
include_package_data=True,
无需手动列出 package_data,或在 MANIFEST.in 文件中列出 - 如果它是版本控制的,则它包含在包中。“setuptools_scm”上的文档强调从提交位置创建版本号,而忽略了添加数据文件的真正重要部分。(如果我的中间轮文件被命名为“*0.2.2.dev45+g3495a1f”或者将使用我输入的硬编码版本号“0.3.0dev0”,我不在乎 - 但将关键文件留给程序后面的工作有点重要)
MANIFEST.in
在项目根目录中创建recursive-include
所需的目录或include
文件名。
include LICENSE
include README.rst
recursive-include package/static *
recursive-include package/templates *
第 1 步:MANIFEST.in
在与 setup.py 相同的文件夹中创建一个文件
第 2 步:包含要添加的文件的相对路径MANIFEST.in
include README.rst
include docs/*.txt
include funniest/data.json
第 3 步:include_package_data=True
在函数中设置setup()
将这些文件复制到 site-package
我想对其中一个问题发表评论,但我没有足够的声誉来做到这一点>。>
这对我有用(在参考文档后提出):
package_data={
'mypkg': ['../*.txt']
},
include_package_data: False
奇怪的是,最后一行对我来说也很重要(你也可以省略这个关键字参数 - 它的工作原理相同)。
它的作用是复制您的顶级或根目录中的所有文本文件(mypkg
您要分发的包的上一级)。
希望这可以帮助!
这在 2020 年有效!
正如其他人所说,在 setup.py 所在的位置创建“MANIFEST.in”。
清单中的下一步包括/排除所有必要的东西。请注意这里的语法。例如:假设我们有模板文件夹要包含在源包中。
在清单文件中这样做:
recursive-include template *
确保在 dir-name 和 pattern 之间为上述文件/目录留出空格。不要像我们在 .gitignore 中那样做
recursive-include template/* [this won't work]
其他选项是使用包含。有很多选择。在这里查看他们的 Manifest.in 文档
最后一个重要步骤,将这个参数包含在你的 setup.py 中,你就可以开始了!
setup(
...
include_package_data=True,
......
)
希望有帮助!快乐编码!
在 setup.py 下 setup( :
setup(
name = 'foo library'
...
package_data={
'foolibrary.folderA': ['*'], # All files from folder A
'foolibrary.folderB': ['*.txt'] #All text files from folder B
},
以上都没有真正为我工作。拯救我的是这个答案。
显然,为了在安装过程中提取这些数据文件,我必须做几件事:
MANIFEST.in
到项目并指定要包含的文件夹/文件。就我而言:recursive-include folder_with_extra_stuff *
include_package_data=True
到您的setup.py
. 这是至关重要的,因为没有它只会*.py
带来匹配的文件。__init__.py
-在您的数据文件夹中添加一个空文件。对我来说,我必须将此文件添加到我的folder-with-extra-stuff
.site-packages
。所以我不得不添加zip_safe=False
到我的setup.py
文件中。最终目录结构
my-app/
├─ app/
│ ├─ __init__.py
│ ├─ __main__.py
├─ folder-with-extra-stuff/
│ ├─ __init__.py
│ ├─ data_file.json
├─ setup.py
├─ MANIFEST.in
这是一个对我有用的更简单的答案。
首先,根据上述 Python 开发人员的评论,不需要 setuptools:
package_data is also available to pure distutils setup scripts
since 2.3. – Éric Araujo
这很好,因为在你的包上设置了 setuptools 要求意味着你也必须安装它。简而言之:
from distutils.core import setup
setup(
# ...snip...
packages = ['pkgname'],
package_data = {'pkgname': ['license.txt']},
)
我只是想跟进我发现在 Centos 6 上使用 Python 2.7 的事情。如上所述添加 package_data 或 data_files 对我不起作用。我添加了一个包含我想要的文件的 MANIFEST.IN,它将非 python 文件放入 tarball,但没有通过 RPM 将它们安装在目标机器上。
最后,我能够使用 setup/setuptools 中的“选项”将文件放入我的解决方案中。选项文件允许您从 setup.py 修改规范文件的各个部分。如下。
from setuptools import setup
setup(
name='theProjectName',
version='1',
packages=['thePackage'],
url='',
license='',
author='me',
author_email='me@email.com',
description='',
options={'bdist_rpm': {'install_script': 'filewithinstallcommands'}},
)
文件 - MANIFEST.in:
include license.txt
文件 - 带有安装命令的文件:
mkdir -p $RPM_BUILD_ROOT/pathtoinstall/
#this line installs your python files
python setup.py install -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES
#install license.txt into /pathtoinstall folder
install -m 700 license.txt $RPM_BUILD_ROOT/pathtoinstall/
echo /pathtoinstall/license.txt >> INSTALLED_FILES
没有一个答案对我有用,因为我的文件位于包外的顶层。我改用自定义构建命令。
import os
import setuptools
from setuptools.command.build_py import build_py
from shutil import copyfile
HERE = os.path.abspath(os.path.dirname(__file__))
NAME = "thepackage"
class BuildCommand(build_py):
def run(self):
build_py.run(self)
if not self.dry_run:
target_dir = os.path.join(self.build_lib, NAME)
for fn in ["VERSION", "LICENSE.txt"]:
copyfile(os.path.join(HERE, fn), os.path.join(target_dir,fn))
setuptools.setup(
name=NAME,
cmdclass={"build_py": BuildCommand},
description=DESCRIPTION,
...
)
对于要包含在安装中的非 python 文件,它们必须位于已安装的包目录之一中。如果您在 MANIFEST.in 中的包目录之外指定非 python 文件,它们将包含在您的发行版中,但不会安装。在包目录之外安装任意文件的“记录”方式并不可靠(现在每个人都注意到了)。
Julian Mann的上述回答将文件复制到构建目录中的包目录中,因此它确实有效,但如果您以可编辑/开发模式(pip install -e
或python setup.py develop
)安装,则不能。基于这个对相关问题的回答(和 Julian 的回答),下面是一个示例,它在完成所有其他安装/开发任务后将文件复制到您安装的包位置。这里的假设是根目录中的文件file1
和文件将被复制到安装的包目录(file2
data
my_package
os.path.join(os.path.dirname(__file__), 'file1')
请记住还要执行上述 MANIFEST.in 内容,以便这些文件也包含在您的发行版中。为什么 setuptools 会在您的发行版中包含文件,然后默默地从不安装它们,这超出了我的理解。尽管将它们安装在您的包目录之外可能更加可疑。
import os
from setuptools import setup
from setuptools.command.develop import develop
from setuptools.command.install import install
from shutil import copyfile
HERE = os.path.abspath(os.path.dirname(__file__))
NAME = 'my_package'
def copy_files (target_path):
source_path = os.path.join(HERE, 'data')
for fn in ["file1", "file2"]:
copyfile(os.path.join(source_path, fn), os.path.join(target_path,fn))
class PostDevelopCommand(develop):
"""Post-installation for development mode."""
def run(self):
develop.run(self)
copy_files (os.path.abspath(NAME))
class PostInstallCommand(install):
"""Post-installation for installation mode."""
def run(self):
install.run(self)
copy_files (os.path.abspath(os.path.join(self.install_lib, NAME)))
setup(
name=NAME,
cmdclass={
'develop': PostDevelopCommand,
'install': PostInstallCommand,
},
version='0.1.0',
packages=[NAME],
include_package_data=True,
setup_requires=['setuptools_scm'],
)
想出了一个解决方法:我将 my 重命名lgpl2.1_license.txt
为lgpl2.1_license.txt.py
,并在文本周围加上了一些三引号。现在我不需要使用该data_files
选项,也不需要指定任何绝对路径。我知道,将其作为 Python 模块很难看,但我认为它不如指定绝对路径难看。