语义化版本与其在Python中的使用

2023-04-13 16:27:18 浏览数 (1)

今天在公司处理了一个线上问题,涉及到在 Python 中处理语义化版本(Semantic Versioning),值得作为一个主题记录一下。

背景

公司的互动营销模块允许用户(淘宝店铺)创建无限互动营销活动,淘宝买家可以在手机淘宝的活动页面参与对应的互动营销活动。 互动营销活动在手机淘宝上的载体是商家应用(一种形式的小程序),整体流程如下:

  1. 服务商开发并发布商家应用模板;
  2. 订购了服务的用户实例化商家应用模板为商家应用;
  3. 用户在 B 端 Web 页面创建互动营销活动,并将活动页面链接(商家应用 url)发布到手机淘宝店铺首页等位置。

其中第二步实例化商家应用模板时需要指定模板版本,模板版本是在商家应用模板提交给淘宝开放平台审核时由开发商填写的,需要符合语义化版本规范。商家应用模板是在不断迭代的,模板版本号也在不断的增长。

起初模板版本号是硬编码到代码中的,造成的影响就是每次模板版本升级的时候,后端服务都要重新部署。在商家应用模板迭代频繁的时候,几乎大部分后端代码上线的唯一改动只有修改商家应用模板版本号。

如此频繁的上线一定程度上也会影响到服务的稳定性,并且在每次大促期间(六一八、双十一、双十二等)淘宝开放平台都会禁止服务商随意进行线上服务部署。

为了解决模板版本号硬编码的问题,我们在管理页面提供了一个入口允许管理员手动修改模板版本号,并且添加了模板版本号只能增加的限制。

代码语言:javascript复制
if new_version < version:
    raise ValueError('模板版本号只能增加')

检查版本号的逻辑大致实现如上述代码。这个实现在一般情况下还是没有问题的,比如版本号从0.1.0升级到0.1.1或者版本号从1.0.9升级到1.1.0。不过当子版本号不是一位整数时,问题就出现了: 例如将版本号从1.0.9升级到1.0.10,在语义化版本规范中,1.0.10是比1.0.9版本更高的,然而在python的字符串比较(按位比较)中,1.0.9反而是大于1.0.10的。今天线上出现的问题就类似于此。

对于这个问题,我们首先要对语义化版本有更深刻的理解。

语义化版本

(语义化版本)[https://semver.org/lang/zh-CN/]是一种软件版本号的规范,主要的规定如下。

版本格式:主版本号.次版本号.修订号,版本号递增规则如下: 主版本号:当你做了不兼容的 API 修改, 次版本号:当你做了向下兼容的功能性新增, 修订号:当你做了向下兼容的问题修正。 先行版本号及版本编译元数据可以加到“主版本号.次版本号.修订号”的后面,作为延伸。

语义化版本的应用十分广泛,诸如 Python(3.8.6)、Mysql(5.7.31)、React (17.0.2)、Chrome( 89.0.774.57)等流行的编程语言、数据库软件、框架和应用软件都采用了语义化版本。

在 Python 中处理并比较语义化版本

我们已经知道了语义化版本是由.分隔的,一个很直接的方案是分段比较每一段版本的大小。

代码语言:javascript复制
def compare(version1: str, version2: str) -> int:
    versions1 = [int(s) for s in version1.split('.')]
    versions2 = [int(s) for s in version2.split('.')]
    if versions1 > versions2:
        return 1
    if versions1 == versions2:
        return 0
    return -1

compare('1.0.9', '1.0.10') # -1
compare('10.0.0', '9.9.99') # 1
compare('1.2.3', '1.2.3') # 0

上述方案看起来工作正常,然而还是有不足之处:在语义化版本规范中,修订号(第三段版本)后面还是可以加一些编译元数据的,例如0.1.1.105.3.2.a11.1.3pre0也是符合语义化版本规范的,但是用上述compare实现进行比较的时候就会报错。

使用packaging库处理语义化版本

对语义化版本的处理实际上是一个很常见的需求(至少所有的包办理工具都需要处理语义化版本,如 pip、npm 等)。packaging是一个常用的 Python 库(它是pip的间接依赖,所以一般不需要手动安装packaging库),其中的packaging.version模块提供了处理语义化版本的支持。

代码语言:javascript复制
from packaging import version
ver = version.parse('1.2.3')
ver.major,ver.minor,ver.micro # 1, 2, 3
version.parse('1.0.9') < version.parse('1.0.10') # True

使用packaging.version模块可以方便地进行语义化版本的解析和比较。我也将修改商家模板版本接口的业务逻辑改为了使用packaging.version模块用于验证新版本的合法性。

总结

本文大致介绍了语义化版本及其在 Python 中的处理方式。同时也算是一个 Case Study,日后再遇到类似的较为通用的问题时,可以优先考虑一下有没有现成的标准库或者第三方库(一般会考虑到更多边界条件并且有着完善的单元测试)。

0 人点赞