DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
RackNerd;$21.99/年 · 1GB KVM
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

packaging 26 的 contains 接受了 dev 版本,预发布政策要显式写出

packaging 26 改变单候选 contains 的预发布默认判断。区分严格稳定版、集合中的回退选择与 installed 参数,不把范围匹配当完整依赖求解。

自写依赖检查器升级 packaging 后,>=7 居然接受了 8.0.0dev0,原来用于拦预发布版的判断失效了。如果代码只调用 specifier.contains(version),先把“满足版本范围”和“允许预发布版”分开看。

packaging 26.0 改变了默认行为,26.3 的正式接口也保留这一规则:prereleases=None 时,单个候选没有其他版本可供比较,可能按预发布回退规则被接受。这个变化不是所有版本号都突然满足范围,也不等于安装器一定会选它。

单个候选不代表完整候选集合

例如 Requirement("click>=7").specifier.contains("8.0.0dev0"),检查的是这一项能否符合约束。调用里没有传入可选择的稳定版列表。不能一边逐项调用 contains(),一边假定库知道仓库里还有稳定版本。

如果业务规定“只接受稳定版”,明确关闭预发布:

from packaging.requirements import Requirement

requirement = Requirement("click>=7")
allowed = requirement.specifier.contains(
    "8.0.0dev0",
    prereleases=False,
)

这段判断应拒绝示例中的开发版。prereleases=False 是业务政策,不需要通过把版本字符串里的 dev、a、b 或 rc 剪掉来实现。剪掉后会变成另一个版本,比较结果失去原意。

原流程若有意允许已经安装的预发布版,还要核对 installed=True。这个参数会影响判断,不能在保留它的同时,认为新增一个 prereleases=False 就必然实施了“任何预发布都拒绝”的政策。

需要比较整组版本时,用集合过滤表达选择范围

若政策是“存在符合条件的稳定版时不用预发布版,没有时才考虑”,应把候选一起交给 filter():

from packaging.specifiers import SpecifierSet

constraint = SpecifierSet(">=7")
available = ["7.2.0", "8.0.0dev0"]
matching = list(constraint.filter(available, prereleases=None))

这里的 available 只是已取得候选的示例,不会访问包仓库。实际调用方仍须先按平台、Python、发行产物、来源与其他依赖规则建立候选集合;这个方法不负责完整依赖求解,也不会自动选择最高版本。

显式设置在 SpecifierSet 上的预发布政策也会影响默认行为。检查初始化参数及方法调用参数,不能只改某个末端调用而漏掉上层已经写死的政策。

留住会改变决策的样本

升级前后对照开发版、alpha、beta、rc、稳定版、后发布版,以及完全不满足范围的版本。针对稳定版专用入口,确认预发布样本确实被拒绝,而正常稳定版本仍可通过;针对允许回退的入口,再比较“候选中有稳定版”和“只有预发布版”两组结果。

如果变化最终影响了锁文件或发布许可,按项目原流程审查新结果,不要仅为让旧断言变绿而退回库版本。修的是调用方缺失的政策,不能据此放宽实际安装器的来源与兼容检查。