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、稳定版、后发布版,以及完全不满足范围的版本。针对稳定版专用入口,确认预发布样本确实被拒绝,而正常稳定版本仍可通过;针对允许回退的入口,再比较“候选中有稳定版”和“只有预发布版”两组结果。
如果变化最终影响了锁文件或发布许可,按项目原流程审查新结果,不要仅为让旧断言变绿而退回库版本。修的是调用方缺失的政策,不能据此放宽实际安装器的来源与兼容检查。


