252

如何使setup.py包含不属于代码的文件?(具体来说,它是一个许可文件,但也可以是其他任何东西。)

我希望能够控制文件的位置。在原始源文件夹中,该文件位于包的根目录中。(即与最顶层处于同一级别__init__.py。)无论操作系统如何,我都希望它在安装软件包时完全保留在那里。我怎么做?

4

14 回答 14

275

可能最好的方法是使用该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 的演示文稿

更新:另一种 [更好] 方法

如果您只想控制源代码分发的内容sdistMANIFEST.in有关此文件的格式,请参阅Python 文档

自从写了这个回复后,我发现使用MANIFEST.in通常是一种不那么令人沮丧的方法,它只是确保你的源代码分发 ( tar.gz) 有你需要的文件。

例如,如果你想包含requirements.txtfrom 顶层,递归地包含顶层的“data”目录:

include requirements.txt
recursive-include data *

然而,为了在安装时将这些文件复制到站点包内的​​包文件夹中,您需要提供include_package_data=True给该setup()函数。有关详细信息,请参阅添加非代码文件

于 2009-12-07T02:20:16.857 回答
44

要完成你所描述的将需要两个步骤......

  • 该文件需要添加到源 tarball
  • 需要修改setup.py 将数据文件安装到源路径

第 1 步:要将文件添加到源 tarball,请将其包含在 MANIFEST 中

在包含 setup.py 的文件夹中创建一个MANIFEST模板

MANIFEST 基本上是一个文本文件,其中包含将包含在源 tarball 中的所有文件的列表。

这是我的项目的清单的样子:

  • 变更日志.txt
  • 安装.txt
  • 许可证.txt
  • pypreprocessor.py
  • 自述文件.txt
  • 安装程序.py
  • 测试.py
  • 待办事项.txt

注意:虽然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'])]

注意:上述步骤应该完全按照您以标准方式描述的内容,而不需要任何扩展库。

于 2010-06-15T04:00:06.853 回答
25

现在是 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”,我不在乎 - 但将关键文件留给程序后面的工作有点重要)

于 2019-09-14T03:32:58.247 回答
20

MANIFEST.in在项目根目录中创建recursive-include所需的目录或include文件名。

include LICENSE
include README.rst
recursive-include package/static *
recursive-include package/templates *

文档可以在这里找到

于 2017-09-20T11:24:53.847 回答
13

第 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

参考在这里。

于 2018-10-26T21:43:59.663 回答
8

我想对其中一个问题发表评论,但我没有足够的声誉来做到这一点>。>

这对我有用(在参考文档后提出):

package_data={
    'mypkg': ['../*.txt']
},

include_package_data: False

奇怪的是,最后一行对我来说也很重要(你也可以省略这个关键字参数 - 它的工作原理相同)。

它的作用是复制您的顶级或根目录中的所有文本文件(mypkg您要分发的包的上一级)。

希望这可以帮助!

于 2018-09-28T19:18:52.987 回答
6

这在 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,
    ......
)

希望有帮助!快乐编码!

于 2020-07-14T17:59:59.020 回答
5

在 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
   },
于 2014-12-27T04:49:53.780 回答
4

以上都没有真正为我工作。拯救我的是这个答案。
显然,为了在安装过程中提取这些数据文件,我必须做几件事:

  1. 就像已经提到的 - 将 a 添加MANIFEST.in到项目并指定要包含的文件夹/文件。就我而言:recursive-include folder_with_extra_stuff *
  2. 再次,就像已经提到的 - 添加include_package_data=True到您的setup.py. 这是至关重要的,因为没有它只会*.py带来匹配的文件。
  3. 这就是缺少的__init__.py-在您的数据文件夹中添加一个空文件。对我来说,我必须将此文件添加到我的folder-with-extra-stuff.
  4. 额外 - 不确定这是否是一项要求,但使用我自己的 python 模块,我看到它们被压缩在 .egg 文件中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
于 2021-03-26T00:44:13.663 回答
3

这是一个对我有用的更简单的答案。

首先,根据上述 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']},
)
于 2013-08-05T23:08:39.163 回答
2

我只是想跟进我发现在 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
于 2015-06-01T17:22:40.253 回答
2

没有一个答案对我有用,因为我的文件位于包外的顶层。我改用自定义构建命令。

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,
    ...
)
于 2020-09-19T07:54:28.053 回答
1

对于要包含在安装中的非 python 文件,它们必须位于已安装的包目录之一中。如果您在 MANIFEST.in 中的包目录之外指定非 python 文件,它们将包含在您的发行版中,但不会安装。在包目录之外安装任意文件的“记录”方式并不可靠(现在每个人都注意到了)。

Julian Mann的上述回答将文件复制到构建目录中的包目录中,因此它确实有效,但如果您以可编辑/开发模式(pip install -epython setup.py develop)安装,则不能。基于这个对相关问题的回答(和 Julian 的回答),下面是一个示例,它在完成所有其他安装/开发任务后将文件复制到您安装的包位置。这里的假设是根目录中的文件file1和文件将被复制到安装的包目录(file2datamy_packageos.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'],
)

于 2022-01-09T03:52:35.130 回答
-13

想出了一个解决方法:我将 my 重命名lgpl2.1_license.txtlgpl2.1_license.txt.py,并在文本周围加上了一些三引号。现在我不需要使用该data_files选项,也不需要指定任何绝对路径。我知道,将其作为 Python 模块很难看,但我认为它不如指定绝对路径难看。

于 2009-10-23T12:22:55.733 回答