我有一个我正在使用Travis-CI的requirements.txt文件。在requirements.txt和setup.py中复制需求似乎很愚蠢,所以我希望在setuptools.setup中传递一个文件句柄给install_requires kwarg。

这可能吗?如果是,我该怎么做呢?

这是我的requirements.txt文件:

guessit>=0.5.2
tvdb_api>=1.8.2
hachoir-metadata>=1.3.3
hachoir-core>=1.3.3
hachoir-parser>=1.3.4

当前回答

下面是一个完整的hack(用pip 9.0.1测试),基于Romain的回答,解析requirements.txt,并根据当前的环境标记进行过滤:

from pip.req import parse_requirements

requirements = []
for r in parse_requirements('requirements.txt', session='hack'):
    # check markers, such as
    #
    #     rope_py3k    ; python_version >= '3.0'
    #
    if r.match_markers():
        requirements.append(str(r.req))

print(requirements)

其他回答

使用parse_requirements是有问题的,因为pip API没有公开的文档和支持。在pip 1.6中,该函数实际上是移动的,因此对它的现有使用可能会中断。

消除setup.py和requirements.txt之间重复的一个更可靠的方法是在setup.py中指定你的依赖项,然后输入-e。到requirements.txt文件中。关于为什么这是一种更好的方式,来自pip开发人员的一些信息可以在这里找到:https://caremad.io/blog/setup-vs-requirement/

从表面上看,requirements.txt和setup.py似乎是愚蠢的副本,但重要的是要理解,虽然形式相似,但预期的功能非常不同。

当指定依赖项时,包作者的目标是说“无论你在哪里安装这个包,为了使这个包工作,这些都是你需要的其他包。”

相反,部署作者(在不同的时间可能是同一个人)有不同的工作,因为他们说“这是我们收集和测试的包的列表,我现在需要安装”。

包作者为各种各样的场景编写程序,因为他们将自己的工作以他们可能不知道的方式使用,并且无法知道将在他们的包旁边安装哪些包。为了成为一个好邻居并避免与其他包的依赖版本冲突,它们需要指定尽可能广泛的依赖版本。这就是setup.py中的install_required所做的。

The deployment author writes for a very different, very specific goal: a single instance of an installed application or service, installed on a particular computer. In order to precisely control a deployment, and be sure that the right packages are tested and deployed, the deployment author must specify the exact version and source-location of every package to be installed, including dependencies and dependency's dependencies. With this spec, a deployment can be repeatably applied to several machines, or tested on a test machine, and the deployment author can be confident that the same packages are deployed every time. This is what a requirements.txt does.

So you can see that, while they both look like a big list of packages and versions, these two things have very different jobs. And it's definitely easy to mix this up and get it wrong! But the right way to think about this is that requirements.txt is an "answer" to the "question" posed by the requirements in all the various setup.py package files. Rather than write it by hand, it's often generated by telling pip to look at all the setup.py files in a set of desired packages, find a set of packages that it thinks fits all the requirements, and then, after they're installed, "freeze" that list of packages into a text file (this is where the pip freeze name comes from).

所以结论是:

setup.py should declare the loosest possible dependency versions that are still workable. Its job is to say what a particular package can work with. requirements.txt is a deployment manifest that defines an entire installation job, and shouldn't be thought of as tied to any one package. Its job is to declare an exhaustive list of all the necessary packages to make a deployment work. Because these two things have such different content and reasons for existing, it's not feasible to simply copy one into the other.

引用:

来自Python打包用户指南的install_requires vs Requirements文件。

虽然这不是问题的确切答案,但我推荐Donald Stufft在https://caremad.io/2013/07/setup-vs-requirement/上的博客文章,它很好地回答了这个问题。我用它取得了巨大的成功。

简而言之,requirements.txt不是setup.py的替代品,而是部署的补充。在setup.py中保持包依赖关系的适当抽象。设置requirements.txt或更多的文件,以获取用于开发、测试或生产的软件包依赖关系的特定版本。

例如,在deps/下的回购中包含包:

# fetch specific dependencies
--no-index
--find-links deps/

# install package
# NOTE: -e . for editable mode
.

PIP执行包的setup.py并安装在install_requires中声明的依赖项的特定版本。没有表里不一,这两件文物的用途都被保留了下来。

上面的大多数其他答案都不能与当前版本的pip API一起使用。下面是使用pip当前版本(撰写本文时为6.0.8,在7.1.2中也有效)的正确*方法。您可以使用pip -V检查您的版本。

from pip.req import parse_requirements
from pip.download import PipSession

install_reqs = parse_requirements(<requirements_path>, session=PipSession())

reqs = [str(ir.req) for ir in install_reqs]

setup(
    ...
    install_requires=reqs
    ....
)

正确,因为这是当前pip使用parse_requirements的方式。这仍然可能不是最好的方法,因为正如上面的帖子所说,pip并没有真正维护API。

它不能接受文件句柄。install_requires参数只能是字符串或字符串列表。

当然,您可以在设置脚本中读取您的文件,并将其作为字符串列表传递给install_requires。

import os
from setuptools import setup

with open('requirements.txt') as f:
    required = f.read().splitlines()

setup(...
install_requires=required,
...)