setuptools setup.py文件中install_requires kwarg的参考requirements.txt


279

我有一个requirements.txt与Travis-CI一起使用的文件。在requirements.txt和中都重复要求似乎很愚蠢setup.py,所以我希望将文件句柄传递给install_requiresin setuptools.setup

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

这是我的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

4
install_requires用于声明对程序包起作用并由程序包开发人员使用的程序包的依赖性,而requirements.txt用于自动安装环境,该程序包允许安装额外的软件并进行版本固定,并且由部署该程序包的系统管理员使用。包。他们的角色和目标受众有很大的不同,因此尝试像OP的愿望那样组合他们是一个真正的设计错误。
Zart

7
我的2美分。不要在setup.py中使用requirements.txt。宗旨不同,ared caremad.io/2013/07/setup-vs-requirement
Philippe Ombredanne

3
我看到很多复杂的答案。普通老人[line.strip() for line in open("requirements.txt").readlines()]怎么了?
Felipe SS Schneider

不建议这样做。但是,如果真的需要,那就很简单了:setuptools本身已经具有一切必要的条件pkg_resources.parse_requirements()
sinoroc

Answers:


246

你可以翻转它周围,列表中的依赖关系setup.py,并有单字符-点.-在requirements.txt代替。


另外,即使不建议使用,也可以requirements.txt通过以下技巧(通过进行测试pip 9.0.1)来分析文件(如果它没有通过URL引用任何外部要求):

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

但是,这不会过滤环境标记


在pip的旧版本中(更具体地讲,版本早于6.0),可以使用公共API来实现此目的。需求文件可以包含注释(#),也可以包含其他一些文件(--requirement-r)。因此,如果您确实要解析a 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
)

26
如果用户没有安装点子怎么办?!
Gringo Suave

82
@GringoSuave如果用户未安装pip,则需要先安装它。
guettli

7
如果有任何指向非pypi包的-e或-f(“可编辑” git repo)行,则还需要在需求文件中提供url。使用这个:setup(..., dependency_links=[str(req_line.url) for req_line in parse_requirements(<requirements_path>)], ...)
滚刀

91
您真的不想这样做。作为pip维护者,pip根本不支持像这样被称为API。实际上,pip 1.6(当前为下一版本)将移动此功能。
唐纳德·史达夫

26
如果有的话,这不再是公认的答案。它被公然打破了。即使工作了,也绝对没有必要。由于pip默认setup.py情况下默认情况下不解析依赖项requirements.txt,因此Tobu敏锐指出的简单答案列出所有依赖项并删除对于同时需要两者的应用程序,只需将依赖关系列表减少为仅字符即可。做完了 setup.pyrequirements.txtrequirements.txt.
塞西尔·库里

194

在它面前,它似乎是requirements.txtsetup.py是愚蠢的重复,而是要明白,虽然形式是相似的,预定的功能有很大的不同是很重要的。

程序包作者在指定依赖项时的目的是说:“无论您在何处安装此程序包,都需要其他程序包,此程序包才能工作。”

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

软件包作者针对各种各样的场景进行了写作,因为他们将自己的工作放到那里以他们可能不了解的方式使用,并且无法知道将在软件包旁边安装哪些软件包。为了成为一个好邻居并避免依赖版本与其他软件包冲突,他们需要指定尽可能广泛的依赖版本。这就是install_requiressetup.py做。

部署作者写的是一个非常不同,非常特定的目标:在特定计算机上安装的已安装应用程序或服务的单个实例。为了精确控制部署,并确保测试和部署了正确的软件包,部署作者必须指定要安装的每个软件包的确切版本和源位置,包括依赖关系和依赖关系。使用此规范,可以将部署重复地应用于多台计算机,或在测试计算机上进行测试,并且部署作者可以确信每次都部署了相同的程序包。这就是一个requirements.txt

因此,您可以看到,尽管它们看起来都像是一个很大的软件包和版本列表,但是这两件事的工作却截然不同。而且混合起来并弄错它肯定很容易!但是考虑这一点的正确方法是,requirements.txt对所有各种setup.py软件包文件中的需求提出的“问题”进行“解答” 。而不是手工编写,它通常是通过告诉pip查看setup.py一组所需软件包中的所有文件,找到它认为符合所有要求的一组软件包,然后在安装后冻结来生成的。 ”将软件包列表转换为文本文件(这就是pip freeze名称的来源)。

因此,外卖:

  • setup.py应该声明仍然可用的最宽松的依赖版本。它的工作是说特定的软件包可以使用什么。
  • requirements.txt是一个定义整个安装工作​​的部署清单,不应将其视为与任何一个软件包捆绑在一起。它的工作是为完成部署工作而声明所有必要软件包的详尽清单。
  • 由于这两件事具有如此不同的内容和存在的原因,因此简单地将其中一项复制到另一项中是不可行的。

参考文献:


10
这是最好的解释之一,它可以使我在所谓的“软件包安装”一类的混乱中下订单!:)
Kounavi 2015年

6
我仍然不清楚为什么开发人员会保持版本控制requirements.txt以及包含安装或测试的具体/冻结要求的软件包的来源。当然setup.py可以在项目本身中用于此目的吗?我只能想象将这样的文件用于支持项目管理的工具(例如,重构,发布等)。
Sam Brightman

2
@samBrightman我完全同意,我不认为库程序包应用程序程序包应将它们的requirements.txt文件与代码一起提交到存储库。我认为这应该是在构建测试期间生成的工件,然后用于记录构建清单并最终生成部署工件。
乔纳森·汉森

6
因此,您是说requirements.txt,即使在构建过程本身中通常不使用它,但还是有更多有关生成给定构建的世界状况的文档?这就说得通了。但是,似乎有几个系统需要复制:Travis在您的virtualenv中安装了一些默认(旧)软件包,并说要使用requirements.txt。如果我问如何确保最新使用依赖项setup.py,人们会坚持我应该使用requirements.txt
Sam Brightman

2
您可以从中获得的最佳建议是找到一个适合您的模型,对其进行良好的记录,并确保与您合作的每个人都理解它。想一想为什么要做每件事,以及这对于您的用例是否真的有意义。并尽量保持对Python的构建,打包和发布的当前状态的了解,以防万一情况变得更好。但不要屏住呼吸。
乔纳森·汉森

89

它不能使用文件句柄。该install_requires参数可以仅仅是一个字符串或字符串列表

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

import os
from setuptools import setup

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

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

5
尽管很有用,但是这将需求的说明从声明式更改为命令式。这使得某些工具无法找出您的需求。例如,PyCharm可自动安装中指定的所有要求install_requires。但是,如果不使用声明性语法,则无法使用。
Piotr Dobrogost

55
@PiotrDobrogost也许PyCharm开发人员应该然后修复他们的程序。setup.py是应运行的程序,而不是应分析的数据文件。这不会使这个答案变得更糟。
Fredrick Brennan

5
我只是指出可能的问题;这个答案是完全可以的。不仅是PyCharm,信息被“隐藏”在代码背后也有问题。这是一个普遍的问题,因此,通常朝着Python封装中的元数据的声明性规范发展。
Piotr Dobrogost 2013年

32
做工精细,只要你把include requirements.txt到您MANIFEST.in或您将无法从一个源代码分发安装磁带库。
Pankrat 2013年

4
我知道这是一个老问题,但是您至少现在可以配置PyCharm以在“首选项”->“工具”->“ Python集成工具”->“打包需求”文件中
解析

64

需求文件使用扩展的pip格式,仅当您需要setup.py用更严格的约束来补充时(例如,指定某些依赖项必须来自的确切网址,或pip freeze将整个包冻结为已知工作的输出),该文件才有用版本。如果您不需要额外的约束,请仅使用setup.py。如果您觉得确实需要运送requirements.txt,可以将其放在一行中:

.

这将是有效的,并且完全引用setup.py同一目录中的内容。


9
但是在这种情况下,它也会尝试安装我的应用程序。如果我不需要它,只想安装install_requires怎么办?
最早2016年

2
要详细说明@ffeast的问题,如果需求仅存在于setup.py中,是否可以在pip install -r requirements.txt 不安装软件包本身的情况下安装需求(等同于)?
haridsv

1
@ffeast @haridsv -e .应该足够了。检查此页:caremad.io/posts/2013/07/setup-vs-requirement
dexhunter

4
@ DexD.Hunter仍然尝试自行安装应用程序。这不是我们想要的东西
ffeast

38

虽然不是该问题的确切答案,但我还是推荐Donald Stufft的博客文章,网址https://caremad.io/2013/07/setup-vs-requirement/,以很好地解决这个问题。我一直在使用它取得巨大的成功。

简而言之, requirements.txt这不是setup.py替代方案,而是部署的补充。在中保持适当的软件包依赖关系抽象setup.py。设置一个requirements.txt或多个'em以获取特定版本的软件包依赖项以进行开发,测试或生产。

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

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

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

pip执行包的程序setup.py并安装在中声明的依赖项的特定版本install_requires。没有重复性,并且保留了两个工件的目的。


7
当您想提供软件包供他人通过进行安装时,此方法将无效pip install my-package。如果my-package / setup.py中未列出my-package的依赖项,则不会通过安装它们pip install my-package。我一直无法确定如何为包含依赖项的其他人提供一个包,而没有在setup.py中明确说明它们。很想知道是否有人想出了使其保持DRY的方式,同时允许其他人安装my-package +依赖项而无需下载需求文件和手动调用pip install -r my-package/requirements.txt
Malina 2014年

2
@Malina此处的软件包无需安装即可完美安装requirements.txt。这就是重点。更新了问题以使情况更清楚。还更新了过时的博客文章链接。
2015年

因此,在运行setup.py时,它将为在stup.py中列出的文件的特定版本调用requirements.txt吗?
dtracers

@dtracers的另一种方式。requirements.txt指向它自己的包,可以在其中获取setup.py的依赖项。因此,在使用需求进行安装时,它可以工作,并且通过pip进行安装时,它也可以工作-在两种情况下都使用setup.py的依赖关系,但也可以在使用
Requirements.txt

20

使用parse_requirements存在问题,因为未公开记录和支持pip API。在第1.6点中,该功能实际上正在移动,因此该功能的现有用途可能会中断。

消除setup.py和之间重复的一种更可靠的方法requirements.txt是特定于您的依赖项setup.py,然后将其-e .放入requirements.txt文件中。从一个有些信息pip为什么这是去一个更好的办法开发人员可以在这里找到:https://caremad.io/blog/setup-vs-requirement/


@Tommy尝试以下操作:caremad.io/2013/07/setup-vs-requirement这与另一个答案中发布的链接相同。
amit

18

以上大多数其他答案不适用于当前版本的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
    ....
)

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


14

在Travis中安装当前软件包。这样可以避免使用requirements.txt文件。例如:

language: python
python:
  - "2.7"
  - "2.6"
install:
  - pip install -q -e .
script:
  - python runtests.py

2
到目前为止,这是“正确”和“实用”的最佳组合。我要补充的是,如果在测试通过之后,您可以让Travis生成一个requirements.txt pip freeze并将该文件导出为工件(例如S3之类),那么您将有一个很好的方法来重复安装您所需要的文件经过测试。
乔纳森·汉森

4

from pip.req import 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,
    ...
)

4

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

4

以下界面在点10中已弃用:

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

所以我将其切换为简单的文本解析:

with open('requirements.txt', 'r') as f:
    install_reqs = [
        s for s in [
            line.split('#', 1)[0].strip(' \t\n') for line in f
        ] if s != ''
    ]

这种简单的方法在90%的时间内都有效。对于使用Python 3.6+的用户,我已经写了一个答案,它是pathlib变体
Acumenus

3

这种简单的方法从中读取需求文件setup.py。这是Dmitiry S.答案的变形。此答案仅与Python 3.6+兼容。

DSrequirements.txt可以记录与特定的版本号具体要求,而setup.py可以记录与宽松版范围抽象的要求。

以下是我的摘录setup.py

import distutils.text_file
from pathlib import Path
from typing import List

def _parse_requirements(filename: str) -> List[str]:
    """Return requirements from requirements file."""
    # Ref: https://stackoverflow.com/a/42033122/
    return distutils.text_file.TextFile(filename=str(Path(__file__).with_name(filename))).readlines()

setup(...
      install_requires=_parse_requirements('requirements.txt'),
   ...)

请注意,这distutils.text_file.TextFile将删除注释。而且,根据我的经验,您显然不需要采取任何特殊步骤将需求文件捆绑在一起。


2

小心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']

1
使用unsafe_name获取下划线版本:[ir.req.unsafe_name for ir in req_deps if ir.req is not None]
alanjds 2015年

5
如在其他地方指出的那样,PIP是应用程序,而不是库。它没有公开同意的API,并且不支持将其导入您的代码中。它具有意外的行为也就不足为奇了。它的内部功能从未打算以此方式使用。
乔纳森·汉森

1

我为此创建了一个可重用的函数。它实际上解析需求文件的整个目录,并将它们设置为extras_require。

最新的始终可用在这里:https : //gist.github.com/akatrevorjay/293c26fefa24a7b812f5

import glob
import itertools
import os

# This is getting ridiculous
try:
    from pip._internal.req import parse_requirements
    from pip._internal.network.session import PipSession
except ImportError:
    try:
        from pip._internal.req import parse_requirements
        from pip._internal.download import PipSession
    except ImportError:
        from pip.req import parse_requirements
        from pip.download import PipSession


def setup_requirements(
        patterns=[
            'requirements.txt', 'requirements/*.txt', 'requirements/*.pip'
        ],
        combine=True):
    """
    Parse a glob of requirements and return a dictionary of setup() options.
    Create a dictionary that holds your options to setup() and update it using this.
    Pass that as kwargs into setup(), viola

    Any files that are not a standard option name (ie install, tests, setup) are added to extras_require with their
    basename minus ext. An extra key is added to extras_require: 'all', that contains all distinct reqs combined.

    Keep in mind all literally contains `all` packages in your extras.
    This means if you have conflicting packages across your extras, then you're going to have a bad time.
    (don't use all in these cases.)

    If you're running this for a Docker build, set `combine=True`.
    This will set `install_requires` to all distinct reqs combined.

    Example:

    >>> import setuptools
    >>> _conf = dict(
    ...     name='mainline',
    ...     version='0.0.1',
    ...     description='Mainline',
    ...     author='Trevor Joynson <github@trevor.joynson,io>',
    ...     url='https://trevor.joynson.io',
    ...     namespace_packages=['mainline'],
    ...     packages=setuptools.find_packages(),
    ...     zip_safe=False,
    ...     include_package_data=True,
    ... )
    >>> _conf.update(setup_requirements())
    >>> # setuptools.setup(**_conf)

    :param str pattern: Glob pattern to find requirements files
    :param bool combine: Set True to set install_requires to extras_require['all']
    :return dict: Dictionary of parsed setup() options
    """
    session = PipSession()

    # Handle setuptools insanity
    key_map = {
        'requirements': 'install_requires',
        'install': 'install_requires',
        'tests': 'tests_require',
        'setup': 'setup_requires',
    }
    ret = {v: set() for v in key_map.values()}
    extras = ret['extras_require'] = {}
    all_reqs = set()

    files = [glob.glob(pat) for pat in patterns]
    files = itertools.chain(*files)

    for full_fn in files:
        # Parse
        reqs = {
            str(r.req)
            for r in parse_requirements(full_fn, session=session)
            # Must match env marker, eg:
            #   yarl ; python_version >= '3.0'
            if r.match_markers()
        }
        all_reqs.update(reqs)

        # Add in the right section
        fn = os.path.basename(full_fn)
        barefn, _ = os.path.splitext(fn)
        key = key_map.get(barefn)

        if key:
            ret[key].update(reqs)
            extras[key] = reqs

        extras[barefn] = reqs

    if 'all' not in extras:
        extras['all'] = list(all_reqs)

    if combine:
        extras['install'] = ret['install_requires']
        ret['install_requires'] = list(all_reqs)

    def _listify(dikt):
        ret = {}

        for k, v in dikt.items():
            if isinstance(v, set):
                v = list(v)
            elif isinstance(v, dict):
                v = _listify(v)
            ret[k] = v

        return ret

    ret = _listify(ret)

    return ret


__all__ = ['setup_requirements']

if __name__ == '__main__':
    reqs = setup_requirements()
    print(reqs)

非常好!甚至可以使用最新的点来处理递归要求:)
amohr '18

@amohr谢谢!我最近对其进行了更新,以供日后参考。我不确定为什么他们通过将内容移至pip._internal.. 来发挥作用,如果您不提供可用的外部API,那么您不应该破坏所有这些内容正在使用您提供的所有内容。
trevorj '18

0

另一种可能的解决方案...

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,
    ...
)

哪里tree来的?
Francesco Boi

@FrancescoBoi,如果您原谅我不提供完整的解决方案的话,...实际上,它只是对本地文件系统的扫描(非常类似于linux中的“ tree”命令)。另外,我的上述解决方案目前可能无法完全正常工作,因为pip会不断更新,并且我使用了pip内部结构。
布赖恩·布鲁格曼

0

我不建议这样做。正如多次提到的install_requiresrequirements.txt绝对不应该是同一列表。但是,由于涉及pip的专用内部API的周围存在许多误导性答案,因此可能值得寻找更明智的选择...

不需要piprequirements.txtsetuptools setup.py脚本中解析文件。该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,
)

如果您不知道,那么许多(包括我自己在内)一直在使用pip'解析pkg_resources'的原因是自github.com/pypa/setuptools/issues/470之类的bug 。如今,此确切的问题已修复,但是我仍然有点害怕使用它,因为这两种实现似乎是分别开发的。
trevorj

@trevorj感谢您指出这一点,我不知道。事实如今已行得通,参与点子对我来说似乎是一个荒谬的主意(尤其是以这种方式)。看看其他答案,大多数似乎都是同一个想法不佳的想法的微小变化,几乎没有任何警告提示。而新来者可能只是跟随这一趋势。希望诸如PEP517和PEP518之类的倡议能够使社区摆脱这种疯狂。
sinoroc

-1

这个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放在项目根目录下即可。


-1

我这样做:

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

-2

另一个parse_requirements将环境标记解析为extras_require以下内容的黑客:

from collections import defaultdict
from pip.req import parse_requirements

requirements = []
extras = defaultdict(list)
for r in parse_requirements('requirements.txt', session='hack'):
    if r.markers:
        extras[':' + str(r.markers)].append(str(r.req))
    else:
        requirements.append(str(r.req))

setup(
    ...,
    install_requires=requirements,
    extras_require=extras
)

它应该同时支持sdist和二进制dists。

正如其他人所述,它parse_requirements有几个缺点,因此这不是您应该在公共项目上执行的操作,但对于内部/个人项目来说就足够了。


pip 20.1更改了其API,并且标记不再可用parse_requirements(),因此现在失败了。
Tuukka Mustonen

-3

这是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)

1
这只是部分正确。如果您打电话,r.match_markers()您实际上是在评估标记,对于sdist来说这是正确的做法。但是,如果您要构建二进制dist(例如wheel),则该软件包将仅列出与您的构建时环境匹配那些库。
Tuukka Mustonen

@TuukkaMustonen,那么在哪里可以找到该wheel environment标记(如果这是人们试图做的事情)以评估标记呢?
anatoly techtonik

请参阅stackoverflow.com/a/41172125/165629,它也应支持bdist_wheel。它不评估标记,只是将其添加到中extras_require
Tuukka Mustonen
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.