【问题标题】:Is there a way to programmatically confirm that a python package version satisfies a requirements specifier?有没有办法以编程方式确认 python 包版本满足需求说明符?
【发布时间】:2020-09-05 01:39:02
【问题描述】:

我正在尝试查找是否有办法获取已安装的软件包和版本并检查它是否满足要求规范。

例如,如果我有包 pip==20.0.2,我希望程序执行以下操作:

CheckReqSpec("pip==20.0.2", "pip>=19.0.0")  -> True
CheckReqSpec("pip==20.0.2", "pip<=20.1")    -> True
CheckReqSpec("pip==20.0.2", "pip~=20.0.0")  -> True
CheckReqSpec("pip==20.0.2", "pip>20.0.2")   -> False

我发现 pkg_resources.extern.packaging 有 version.parse,这对于比较大于或小于的不同版本很有用,但需求规范可能非常复杂,并且有像 ~= 这样的运算符不是标准的数学运算符.

setuptools 文档有这个例子:

PickyThing<1.6,>1.9,!=1.9.6,<2.0a0,==2.4c1

有没有现成的方法来做这个检查,或者我自己做一个简单的方法?

编辑: ~= 尤其困难,尤其是当规范作为变量输入时。 * 在版本要求中也很难弄清楚,因为

 version.parse("20.0.*") == version.parse("20.0.1") # False
 version.parse("20.0.*") < version.parse("20.0.0")  # True 
 version.parse("20.0.*") < version.parse("20.1.1")  # True 
 version.parse("20.0.*") >= version.parse("20.0.0") # False

【问题讨论】:

    标签: python pip python-packaging requirements.txt


    【解决方案1】:

    使用 setuptools 解析说明符集,然后使用 in 检查成员资格:

    >>> from pkg_resources import Requirement
    >>> req = Requirement.parse("pip~=20.0.0")
    >>> pin = "pip==20.0.2"
    >>> name, version = pin.split("==")
    >>> name == req.name and version in req.specifier
    True
    

    发布后工作。必须明确选择加入预发布版本。

    >>> "20.0.0post1" in req.specifier
    True
    >>> req.specifier.contains("20.0.1b3")
    False
    >>> req.specifier.contains("20.0.1b3", prereleases=True)
    True
    

    【讨论】:

    • 哦,可能是这个!我注意到它不能像 version.parse 那样处理预发布版本/发布后版本,所以我可能只将它用于 * 和 ~=。非常感谢!
    • @FutureGadget 发布后:它确实处理它们。无需更改。预发布:它确实处理它们,但默认情况下不允许使用它们(因为默认情况下 pip 不会使用它们)。如果你想包含预发布,你可以使用req.specifier.contains(v, prereleases=True)
    • 为了完整起见,可能值得明确说明:1. 导入包pkg_resources 是分发包setuptools 的一部分。 2.pkg_resouces(以及setuptools)中出售的底层库是packaging
    【解决方案2】:

    也许是packaging

    from packaging import version
    
    version.parse("20.0.2") > version.parse("19.0.0")   # True
    version.parse("20.0.2") <= version.parse("20.1")    # True
    version.parse("20.0.2") >= version.parse("20.0.0")  # True
    version.parse("20.0.2") > version.parse("20.0.2")   # False
    

    【讨论】:

    • 是的,这就是我提到的 pkg_resources.extern.packaging 版本 - 它适用于大多数比较,如 、!= 等,但它不处理 ~=。最好有内置的特定方法,因为我不确定所有可能的需求规范是什么......
    • 通过一些解析,您应该(理论上)能够模拟~=,如PEP440 中所述。
    • 我刚刚添加了一个小编辑 - 需求规范中的 * 字符很难与之比较。 version.parse 有点奇怪:version.parse("20.0.*") != version.parse("20.0.1") # True version.parse("20.0.*") &lt; version.parse("20.0.0") # True version.parse("20.0.*") &gt;= version.parse("20.0.0") # False 不幸的是它是 PEP440 定义的一部分 ~=。
    • 这很快就会变得笨拙。对于"PickyThing&lt;1.6,&gt;1.9,!=1.9.6,&lt;2.0a0,==2.4c1" 这样的示例,您将如何使用此方法?
    【解决方案3】:

    我会推荐packaging,它可以像下面这样使用:

    >>> import packaging.requirements
    >>> import packaging.version
    >>> packaging.version.parse('20.0.2') in packaging.requirements.Requirement('pip>=19.0.0').specifier
    True
    >>> packaging.version.parse('20.0.2') in packaging.requirements.Requirement('pip~=20.0').specifier
    True
    >>> packaging.requirements.Requirement('pip==20.0.*').specifier.contains('20.0.2')
    True
    >>> packaging.requirements.Requirement('pip==20.0.*').specifier.contains('21')
    False
    >>> packaging.requirements.Requirement('PickyThing<1.6,>1.9,!=1.9.6,<2.0a0,==2.4c1').specifier
    <SpecifierSet('!=1.9.6,<1.6,<2.0a0,==2.4c1,>1.9')>
    

    【讨论】:

    • 这实际上与我的答案相同:pippkg_resources 供应商包装的 SpecifierSet 用于此功能。不同之处在于 setuptools 通常应该已经在站点包中可用,因为它是随 ensurepip 和 venv 一起安装的,但 packaging 必须单独安装才能像这样作为顶级导入使用。如果你单独安装它,你可能会得到一个更新的版本,所以你不能保证得到 pip/setuptools 实际使用的相同代码(我猜是好还是坏取决于用例) .
    • @wim 绝对,我赞成你的答案(以及另一个)。我猜只是一种稍微不同的方法。
    猜你喜欢
    • 2011-11-22
    • 2019-12-03
    • 1970-01-01
    • 2015-12-17
    • 1970-01-01
    • 2021-08-15
    • 2013-04-02
    • 2021-08-28
    • 1970-01-01
    相关资源
    最近更新 更多