python脚本打包成exe后,对其引用的日历库实时更新-动态化加载模块

文章目录

python脚本打包成exe后实现日历库实时更新-动态化加载模块

一、背景

开发了一个测试报告生成工具,集成了日历库chinese_calendar,用来保证报告执行日期和结束日期在正常工作日时间。但该日历库会在每年年末,官方公布来年的法定节假日日期后进行更新,如未能更新致最新版本,会导致生成报告时日期处理异常。例如:1.11.0版本的日历库只能对2026年之前的日期进行处理,对2027年的日期进行处理时,会报NotImplementedError异常。

因此目前的工具需要在官方日历库更新后,手动更新日历库的版本重新打包后才能生成来年的报告。若作者更新不及时,有可能造成较多用户无法使用工具的情况,有一定风险。

为了排除上述风险,实现日历库的实时动态更新,制定如下方案:

  1. 日历库的引用:将日历库从python环境中摘出来放在本地,作为一个本地包来引用
  2. 日历库的更新方式:从第三方库地址下载日历库压缩包,与本地当前使用版本进行对比,如果高于当前使用版本,则下载最新包,解压后覆盖现有包
  3. 何时进行更新:基于日历库未更新对来年的日期进行处理时会报特殊异常的情况,在工具进行日期判定时若捕获到该异常,则对日历库进行更新

二、动态更新日历库的实现

1、如何加载本地包

经过多次尝试,在需要打包成exe的场景下,使用import ,__import__等方法,均无法实现动态更新,因为在打包时他会将当前import的版本打包进exe中,而不会动态引用指定路径下的包,最终使用importlib库指定包路径的方式实现。代码如下

python 复制代码
# hot_loader_module.py
import importlib
import importlib.util
import sys
import os


def load_external_module(module_name, module_path):
    """动态加载本地指定路径的模块"""
    if not os.path.exists(module_path):
        raise FileNotFoundError(f"模块不存在,请检查是否存在 {module_path}!")
    spec = importlib.util.spec_from_file_location(module_name, module_path)
    module = importlib.util.module_from_spec(spec)
    sys.modules[module_name] = module
    spec.loader.exec_module(module)
    return module

2、日历库的更新

日历库更新,从官网获取最新版本号与当前使用版本对比,若当前版本不是最新,则下载最新包并解压覆盖当前包

python 复制代码
# check_version.py
"""
由于明年的法定节假日只会于今年年末官方公布,
chinesecalendar库只会存当年以前的节假日情况,并在官方公司数据后进行更新
所以每年都需要对这个库进行一次更新操作
"""

import urllib
import re
from packaging.version import parse as parse_version
import os
import requests
import shutil
import tarfile
from hot_loader_module import load_external_module


def update_cc(module_name, module_path):
    """更新日历库"""
    # 第三方库镜像网址主页
    url = r'http://mirrors.aliyun.com/pypi/'

    # 日历库地址
    cc_url = r'http://mirrors.aliyun.com/pypi/simple/chinesecalendar/'

    # 代理
    proxies = {'http': 'http://ip:port'}

    # 从python第三方库地址获取最新包的地址地址和版本号
    response = requests.get(cc_url, proxies=proxies, timeout=10)
    data = response.text
    # 正则匹配所有包的链接地址,和版本号
    ver_list = re.findall(r'(?<=\.\./\.\./)(.*(?<=chinesecalendar-)([\d\.]*)\..*tar.*)(?=#)', data)
    # 按版本升序排序
    ver_list.sort(key=lambda x: parse_version(x[1]))
    # 获取最新的版本号
    last_verion = ver_list[-1][1]
    # 获取最新的版本号的包名
    last_file_name = os.path.basename(ver_list[-1][0])  # chinesecalendar-1.11.0.tar.gz
    # 获取解压后的目录名称
    last_whl_name = re.sub(r'\.tar|\.gz', '', last_file_name)  # chinesecalendar-1.11.0
    # 获取最新的版本的下载地址链接
    last_url = urllib.parse.urljoin(url, ver_list[-1][0])
    print(last_url)

    # 暂时存放第三方库的目录
    target_dir = '.\packages'
    # 包解压后的目录
    whl_dir = fr'.\{target_dir}\chinesecalendar'

    def download_and_extract():
        """下载并解压包,"""
        # 放包的文件夹,如果没有,就创建一个
        if not os.path.exists(target_dir):
            os.mkdir(target_dir)
        # 删除packages目录下旧的包
        if os.path.exists(whl_dir):
            shutil.rmtree(whl_dir)
        # 去官网下载包
        file = requests.get(last_url, proxies=proxies, stream=True, timeout=10)
        # 官方下载的包,指定他的存放地址,保存在本地
        file_path = fr'.\{target_dir}\{last_file_name}'
        with open(file_path, 'wb') as f:
            f.write(file.content)

        # 官司网下载的包均为压缩格式,需要解压
        if file_path.endswith('.tar.gz') or file_path.endswith('tar'):
            with tarfile.open(file_path, 'r:*') as f:
                f.extractall(target_dir)
        else:
            raise Exception('压缩包格式当前不支持,请联系作者!')
        # 正常解压会带着版本号,为了让主程序能正常引用,对其名进行重合名,保证引用路径始终不变
        shutil.move(fr'.\{target_dir}\{last_whl_name}', fr'.\{target_dir}\chinesecalendar')
        print("最新日历库更新完毕!")

    # 加载当前包,获取其版本号
    cc = load_external_module(module_name, module_path)
    use_version = cc.__version__
    if parse_version(last_verion) > parse_version(use_version):
        # 最新版本号>当前使用版本号时
        print('chinesecalendar库当前不是最新版本!需要更新')
        print('更新执行中...请稍等')
        download_and_extract()
    else:
        print('chinesecalendar库已是最新版本')


if __name__ == '__main__':
    module_name = 'chinese_calendar'
    module_path = r'.\packages\chinesecalendar\chinese_calendar\__init__.py'
    update_cc(module_name, module_path)
    # cc.is_workday(datetime.datetime.strptime('2026-12-12', '%Y-%m-%d'))

3、异常捕获后进行更新

python 复制代码
from hot_loader_module import load_external_module


module_name = 'chinese_calendar'
module_path = r'.\packages\chinesecalendar\chinese_calendar\__init__.py'
cc = load_external_module(module_name, module_path)

def work_day(date):
    """判断是否是工作日"""
    global cc
    try:
        is_work_day = cc.is_workday(datetime.datetime.strptime(date, '%Y-%m-%d'))
        return is_work_day
    except NotImplementedError as msg:
		# 报该异常时,则是日历库配置不足,无法处理当前给定的日期
        logger.info(r'chinesecalendar库当前版本%s,不是最新版本!需要更新,请稍等。。。' % cc.__version__)
        showinfo(r'提示', '源数据日期超出当前日历库配置,检查更新,点击确认后开始更新!')
		
		# 执行更新日历库操作
        check_version.update_cc(module_name, module_path)
		
		# 重新加载库,检查最新版本号,判断是否更新成功
        cc = load_external_module(module_name, module_path)
		
        logger.info(r'日历库更新完毕,最新版本%s,点击确定后重启工具再使用!' % cc.__version__)
        showinfo(r'提示', '日历库更新完毕,最新版本%s,点击确定后重启工具再使用!' % cc.__version__)
        
        # 关闭工具,用户重新打开生效
        root.destroy()

三、最终实现效果

  • 使用旧版本处理未来日期异常时

  • 提示更新完毕后,点击确定工具自动关闭后,再双击打开工具,生成报告,可以正常生成报告了

相关推荐
Wpa.wk2 小时前
接口测试-Postman接口测试流程小练习2
测试工具·postman
kobe_OKOK_2 小时前
快递鸟对接发快递后端设计系统
python·django
阿蔹2 小时前
UI测试自动化-Web-Python-Selenium-2-元素操作、浏览器操作
前端·python·selenium·ui·自动化
Tipriest_2 小时前
配置用户pip源与查看当前的pip的源的办法
linux·人工智能·python·pip
雪域迷影3 小时前
使用Python库获取网页时报HTTP 403错误(禁止访问)的解决办法
开发语言·python·http·beautifulsoup·urllib
吃茄子的猫3 小时前
python中global全局变量
python
Flash.kkl3 小时前
Python基础语法
开发语言·python
veminhe3 小时前
Python(二) 容器类型与对应操作行为
python
人工干智能3 小时前
调用client.beta.threads.runs.create后交由OpenAI云服务器端的处理
服务器·python·llm