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

当前回答

从皮普。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,
    ...
)

其他回答

交叉张贴我从这个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下。

下面是一个完整的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)

我是这样做的:

import re

def requirements(filename):
    with open(filename) as f:
        ll = f.read().splitlines()
    d = {}
    for l in ll:
        k, v = re.split(r'==|>=', l)
        d[k] = v
    return d

def packageInfo():
    try:
        from pip._internal.operations import freeze
    except ImportError:
        from pip.operations import freeze

    d = {}
    for kv in freeze.freeze():
        k, v = re.split(r'==|>=', kv)
        d[k] = v
    return d

req = getpackver('requirements.txt')
pkginfo = packageInfo()

for k, v in req.items():
    print(f'{k:<16}: {v:<6} -> {pkginfo[k]}')

另一个可能的解决方案……

def gather_requirements(top_path=None):
    """Captures requirements from repo.

    Expected file format is: requirements[-_]<optional-extras>.txt

    For example:

        pip install -e .[foo]

    Would require:

        requirements-foo.txt

        or

        requirements_foo.txt

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

    session = PipSession()
    top_path = top_path or os.path.realpath(os.getcwd())
    extras = {}
    for filepath in tree(top_path):
        filename = os.path.basename(filepath)
        basename, ext = os.path.splitext(filename)
        if ext == '.txt' and basename.startswith('requirements'):
            if filename == 'requirements.txt':
                extra_name = 'requirements'
            else:
                _, extra_name = re.split(r'[-_]', basename, 1)
            if extra_name:
                reqs = [str(ir.req) for ir in parse_requirements(filepath, session=session)]
                extras.setdefault(extra_name, []).extend(reqs)
    all_reqs = set()
    for key, values in extras.items():
        all_reqs.update(values)
    extras['all'] = list(all_reqs)
    return extras

然后使用…

reqs = gather_requirements()
install_reqs = reqs.pop('requirements', [])
test_reqs = reqs.pop('test', [])
...
setup(
    ...
    'install_requires': install_reqs,
    'test_requires': test_reqs,
    'extras_require': reqs,
    ...
)

我不建议做这样的事。正如多次提到的,install_requires和requirements.txt绝对不应该是同一个列表。但是由于涉及到pip的私有内部api,有很多误导性的答案,因此可能值得考虑更理智的替代方案……


没有pip,可以从setuptools setup.py脚本中解析一个相对简单的requirements.txt文件。setuptools项目已经在其顶级包pkg_resources中包含了必要的工具。

它或多或少是这样的:

#!/usr/bin/env python3

import pathlib

import pkg_resources
import setuptools

with pathlib.Path('requirements.txt').open() as requirements_txt:
    install_requires = [
        str(requirement)
        for requirement
        in pkg_resources.parse_requirements(requirements_txt)
    ]

setuptools.setup(
    install_requires=install_requires,
)

同样,这只适用于简单的requirements.txt文件。请参阅pkg_resources文档页中的需求解析,以获得关于所处理内容的详细信息。简而言之,每一行都应该是有效的PEP 508需求。不支持真正特定于pip的符号,这将导致失败。


警告之词

如前所述,不建议这样做。requirements.txt文件和“安装依赖项”列表是两个不同的概念,它们是不可互换的。

但是如果你确实写了一个setup.py安装脚本来读取requirements.txt,那么确保requirements.txt文件包含在“源发行版”(sdist)中,否则安装显然会失败。


自从setuptools版本62.6以来,可以在setup.cfg中写这样的东西:

[options]
install_requires = file: requirements.txt

或者在pyproject.toml中:

[project]
dynamic = ["dependencies"]

[tool.setuptools.dynamic]
dependencies = requirements.txt

上述忠告同样适用:

只支持非常简单的文件 该文件必须添加到sdist

此外,它目前被认为是一个“测试版”功能。


注:

另一个答案是:https://stackoverflow.com/a/59971236/11138259 https://caremad.io/posts/2013/07/setup-vs-requirement/ https://setuptools.pypa.io/en/latest/history.html#v62-6-0