我有一个我正在使用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

当前回答

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

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

其他回答

虽然这不是问题的确切答案,但我推荐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,你可以模仿它的行为:

import sys

from os import path as p

try:
    from setuptools import setup, find_packages
except ImportError:
    from distutils.core import setup, find_packages


def read(filename, parent=None):
    parent = (parent or __file__)

    try:
        with open(p.join(p.dirname(parent), filename)) as f:
            return f.read()
    except IOError:
        return ''


def parse_requirements(filename, parent=None):
    parent = (parent or __file__)
    filepath = p.join(p.dirname(parent), filename)
    content = read(filename, parent)

    for line_number, line in enumerate(content.splitlines(), 1):
        candidate = line.strip()

        if candidate.startswith('-r'):
            for item in parse_requirements(candidate[2:].strip(), filepath):
                yield item
        else:
            yield candidate

setup(
...
    install_requires=list(parse_requirements('requirements.txt'))
)

交叉张贴我从这个SO问题的答案,另一个简单的,pip版本证明解决方案。

try:  # for pip >= 10
    from pip._internal.req import parse_requirements
    from pip._internal.download import PipSession
except ImportError:  # for pip <= 9.0.3
    from pip.req import parse_requirements
    from pip.download import PipSession

requirements = parse_requirements(os.path.join(os.path.dirname(__file__), 'requirements.txt'), session=PipSession())

if __name__ == '__main__':
    setup(
        ...
        install_requires=[str(requirement.req) for requirement in requirements],
        ...
    )

然后把所有的需求放在项目根目录下的requirements.txt下。

从皮普。req导入parse_requirements对我来说不工作,我认为它是我的requirements.txt中的空白行,但这个函数确实工作

def parse_requirements(requirements):
    with open(requirements) as f:
        return [l.strip('\n') for l in f if l.strip('\n') and not l.startswith('#')]

reqs = parse_requirements(<requirements_path>)

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

需求文件使用扩展的pip格式,这只在您需要用更强的约束来补充setup.py时有用,例如指定某些依赖项必须来自的确切url,或者pip freeze的输出将整个包集冻结为已知的工作版本。如果不需要额外的约束,则只使用setup.py。如果你觉得你真的需要发布一个requirements.txt,你可以让它变成一行:

.

它将是有效的,并准确地引用同一目录下的setup.py的内容。