使用setup.py包含非python文件

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

我希望能够控制文件的位置。在原始源文件夹中,文件位于包的根目录中。(即与最顶层的__init__.py在同一层。)我希望它在安装包时保持在那里,而不管操作系统是什么。我怎么做呢?

169586 次浏览

我想出了一个变通办法:我将我的lgpl2.1_license.txt重命名为lgpl2.1_license.txt.py,并在文本周围放了一些三引号。现在我不需要使用data_files选项,也不需要指定任何绝对路径。我知道把它变成Python模块很难看,但我认为它没有指定绝对路径难看。

最好的方法可能是使用setuptools package_data指令。这确实意味着使用setuptools(或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是包名(空=所有包)到模式列表(可以包括glob)的dict。例如,如果你只想在你的包中指定文件,你也可以这样做:

package_data={'yourpackage': ['*.txt', 'path/to/resources/*.txt']}

这里的解决方案肯定是,用.py扩展名重命名非-py文件。

更多信息请参见伊恩·比金的演讲

更新:另一个[更好的]方法

如果你只是想控制源分发(sdist)的内容,并且在包之外有文件(例如顶级目录),另一种工作得很好的方法是添加一个MANIFEST.in文件。该文件的格式参见Python文档

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

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

include requirements.txt
recursive-include data *

然而,为了在安装时将这些文件复制到site-packages内的包的文件夹中,你需要将include_package_data=True提供给setup()函数。更多信息见添加非代码文件

要实现你所描述的需要两步……

  • 需要将该文件添加到源压缩文件中
  • 需要修改Setup.py,将数据文件安装到源路径

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

在包含setup.py的文件夹中创建清单模板

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

下面是我项目的MANIFEST:

  • CHANGELOG.txt
  • INSTALL.txt
  • LICENSE.txt
  • pypreprocessor.py
  • 固定
  • setup . py
  • test.py
  • TODO.txt

注意:虽然sdist does自动添加一些文件,我更喜欢显式地指定它们来确定,而不是预测它做什么和不做什么。

步骤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'])]

注意:上面的步骤应该以标准的方式完成您所描述的工作,而不需要任何扩展库。

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

首先,根据上面Python Dev的注释,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']},
)

在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
},

我只是想跟进我在Centos 6上的Python 2.7中发现的一些东西。如上所述,添加package_data或data_files对我不起作用。我加了一份清单。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'}},
)

file - MANIFEST.in:

include license.txt

File - filewithinstallcommands:

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

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

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

的文档可以在这里找到

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

以下是对我有效的方法(参考文档后想到的):

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


include_package_data: False

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

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

步骤1:和setup.py在同一个文件夹中创建一个MANIFEST.in文件

步骤2:包含你想在MANIFEST.in中添加的文件的相对路径

include README.rst
include docs/*.txt
include funniest/data.json

步骤3:setup()函数中设置include_package_data=True来将这些文件复制到site-package

Reference is here. .

现在是2019年,这里是工作的方式 尽管这里和那里都有建议,但我在互联网上发现的是使用setuptools_scm,作为选项传递给setuptools.setup。这将包括在你的VCS上的任何版本的数据文件,无论是git还是其他,到wheel包中,并将“;pip install"

因此,我只是在“setup.py”上添加了这两行到设置调用中。无需额外安装或导入:

    setup_requires=['setuptools_scm'],
include_package_data=True,

不需要手动列出package_data,或在MANIFEST中。在文件中-如果它是版本控制的,则包含在包中。setuptools_scm"将重点放在从提交位置创建版本号上,而忽略添加数据文件这一真正重要的部分。(我不介意我的中间车轮文件命名为"*0.2.2.dev45+g3495a1f"或者将使用硬编码的版本号“0.3.0dev0”;我已经输入了-但留下关键文件让程序工作是有些重要的)

这在2020年有效!

正如其他人所说,create &;MANIFEST.in"你的setup.py所在的位置。

接下来在manifest中包含/排除所有必要的东西。这里要注意语法。 例如:假设我们有一个模板文件夹要包含在源包中

在manifest文件中这样做:

recursive-include template *

确保在dir-name和pattern之间像上面那样为文件/dirs留出空格。 不要像我们在。gitignore中那样做

recursive-include template/* [this won't work]

其他选项是使用include。有很多选择。看看他们的Manifest.in文件

最后重要的一步是,在setup.py中包含这个参数,然后就可以开始了!

   setup(
...
include_package_data=True,
......
)

希望有帮助!编码快乐!

没有一个答案对我有用,因为我的文件在顶层,在包之外。我使用了自定义构建命令。

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,
...
)
以上这些对我来说都没用。拯救我的是答案。
显然,为了在安装期间提取这些数据文件,我必须做一些事情:

  1. 如前所述-向项目中添加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模块中,我看到它们被压缩在site-packages中的.egg文件中。所以我必须将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文件,它们必须位于已安装包目录中的一个目录中。如果您在MANIFEST中指定包目录之外的非python文件。中,它们将包含在您的发行版中,但不会被安装。“documented"在包目录之外安装任意文件的方法不可靠(现在每个人都注意到了)。

上面来自朱利安·曼的答案将文件复制到build目录中的包目录,所以它可以工作,但如果你在可编辑/开发模式下安装(pip install -epython setup.py develop)则不行。基于对相关问题的回答(和Julian的回答),下面是一个例子,在所有其他安装/开发任务完成后,以任何一种方式将文件复制到已安装的包位置。这里的假设是,你的根目录data中的文件file1file2将被复制到你安装的包目录(my_package),并且它们可以从包中的python模块中使用os.path.join(os.path.dirname(__file__), 'file1')等访问。

记得也要做清单。在上面描述的东西中,以便这些文件也包含在您的发行版中。为什么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'],
)