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

当前回答

我是这样做的:

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]}')

其他回答

你可以把它翻转过来,在setup.py中列出依赖项,并只有一个字符——一个点。-在requirements.txt中。


或者,即使不建议,仍然可以使用以下hack(用pip 9.0.1测试)解析requirements.txt文件(如果它没有通过URL引用任何外部需求):

install_reqs = parse_requirements('requirements.txt', session='hack')

但是这并不能过滤环境标记。


在pip的旧版本中,特别是6.0以上的版本中,可以使用一个公共API来实现这一点。需求文件可以包含注释(#),也可以包含一些其他文件(——requirement或-r)。因此,如果你真的想解析一个requirements.txt文件,你可以使用pip解析器:

from pip.req import parse_requirements

# parse_requirements() returns generator of pip.req.InstallRequirement objects
install_reqs = parse_requirements(<requirements_path>)

# reqs is a list of requirement
# e.g. ['django==1.5.1', 'mezzanine==1.4.6']
reqs = [str(ir.req) for ir in install_reqs]

setup(
    ...
    install_requires=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

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

小心parse_requirements行为!

请注意,pip.req。Parse_requirements将把下划线改为破折号。这件事让我生气了几天,后来我才发现。示例演示:

from pip.req import parse_requirements  # tested with v.1.4.1

reqs = '''
example_with_underscores
example-with-dashes
'''

with open('requirements.txt', 'w') as f:
    f.write(reqs)

req_deps = parse_requirements('requirements.txt')
result = [str(ir.req) for ir in req_deps if ir.req is not None]
print result

生产

['example-with-underscores', 'example-with-dashes']

如果你不想强迫你的用户安装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'))
)