|
| 1 | +"""依赖环境自动安装模块。 |
| 2 | +
|
| 3 | +本模块负责检测项目版本变化,并在必要时自动安装或更新 Python 依赖包。 |
| 4 | +通过读取 interface.json 中的版本号与本地配置进行比对, |
| 5 | +决定是否需要执行 pip install 操作。 |
| 6 | +""" |
| 7 | + |
| 8 | +import os |
| 9 | +import sys |
| 10 | +import json |
| 11 | +import subprocess |
| 12 | +from pathlib import Path |
| 13 | + |
| 14 | +# ==================== 路径初始化 ==================== |
| 15 | + |
| 16 | +current_file_path = os.path.abspath(__file__) |
| 17 | +current_dir = os.path.dirname(current_file_path) |
| 18 | +parent_dir = os.path.dirname(current_dir) |
| 19 | +os.chdir(parent_dir) |
| 20 | + |
| 21 | +if current_dir not in sys.path: |
| 22 | + sys.path.insert(0, current_dir) |
| 23 | + |
| 24 | + |
| 25 | +# ==================== 全局路径常量 ==================== |
| 26 | + |
| 27 | +PIP_CONFIG_PATH = Path("./config/mddl/pip_config.json") |
| 28 | + |
| 29 | + |
| 30 | +# ==================== 配置文件读写 ==================== |
| 31 | + |
| 32 | + |
| 33 | +def _read_interface_version() -> str: |
| 34 | + """读取 interface.json 中的版本号。 |
| 35 | +
|
| 36 | + Returns: |
| 37 | + 版本号字符串,若文件不存在或读取失败则返回 "unknown" |
| 38 | + """ |
| 39 | + interface_path = Path("./interface.json") |
| 40 | + if not interface_path.exists(): |
| 41 | + return "unknown" |
| 42 | + |
| 43 | + try: |
| 44 | + with open(interface_path, "r", encoding="utf-8") as f: |
| 45 | + interface_data = json.load(f) |
| 46 | + return interface_data.get("version", "unknown") |
| 47 | + except Exception: |
| 48 | + return "unknown" |
| 49 | + |
| 50 | + |
| 51 | +def _read_pip_config() -> dict: |
| 52 | + """读取 pip 安装配置文件。 |
| 53 | +
|
| 54 | + 若配置文件不存在,则自动创建并使用默认配置。 |
| 55 | + 默认配置包括: |
| 56 | + - enable_pip_install:是否启用自动安装,默认 True |
| 57 | + - last_version:上次安装时的版本号,默认 "unknown" |
| 58 | + - mirrors:pip 镜像源地址列表,按顺序尝试,失败后用默认源兜底 |
| 59 | +
|
| 60 | + Returns: |
| 61 | + 包含 pip 配置的字典 |
| 62 | + """ |
| 63 | + config_dir = Path("./config/mddl") |
| 64 | + config_dir.mkdir(parents=True, exist_ok=True) |
| 65 | + |
| 66 | + default_config = { |
| 67 | + "enable_pip_install": True, |
| 68 | + "last_version": "unknown", |
| 69 | + "mirrors": [ |
| 70 | + "https://pypi.tuna.tsinghua.edu.cn/simple", |
| 71 | + "https://mirrors.ustc.edu.cn/pypi/simple", |
| 72 | + "https://mirrors.aliyun.com/pypi/simple", |
| 73 | + ], |
| 74 | + } |
| 75 | + |
| 76 | + if not PIP_CONFIG_PATH.exists(): |
| 77 | + with open(PIP_CONFIG_PATH, "w", encoding="utf-8") as f: |
| 78 | + json.dump(default_config, f, indent=4) |
| 79 | + return default_config |
| 80 | + |
| 81 | + try: |
| 82 | + with open(PIP_CONFIG_PATH, "r", encoding="utf-8") as f: |
| 83 | + return json.load(f) |
| 84 | + except Exception: |
| 85 | + return default_config |
| 86 | + |
| 87 | + |
| 88 | +def _update_pip_config(version) -> bool: |
| 89 | + """更新 pip 配置文件中的版本号。 |
| 90 | +
|
| 91 | + Args: |
| 92 | + version:要更新的版本号 |
| 93 | +
|
| 94 | + Returns: |
| 95 | + 更新成功返回 True,失败返回 False |
| 96 | + """ |
| 97 | + try: |
| 98 | + config = _read_pip_config() |
| 99 | + config["last_version"] = version |
| 100 | + |
| 101 | + with open(PIP_CONFIG_PATH, "w", encoding="utf-8") as f: |
| 102 | + json.dump(config, f, indent=4) |
| 103 | + return True |
| 104 | + except Exception: |
| 105 | + return False |
| 106 | + |
| 107 | + |
| 108 | +# ==================== 依赖安装 ==================== |
| 109 | + |
| 110 | + |
| 111 | +def _install_requirements(req_file=None, mirrors=None) -> bool: |
| 112 | + """安装 requirements.txt 中列出的依赖包。 |
| 113 | +
|
| 114 | + Args: |
| 115 | + req_file:依赖文件路径,默认为 "requirements.txt" |
| 116 | + mirrors:pip 镜像源地址列表,按顺序尝试,最后用默认源兜底 |
| 117 | +
|
| 118 | + Returns: |
| 119 | + 安装成功返回 True,失败或文件不存在返回 False |
| 120 | + """ |
| 121 | + req_path = Path(req_file) if req_file else Path("requirements.txt") |
| 122 | + if not req_path.exists(): |
| 123 | + return False |
| 124 | + |
| 125 | + # 准备镜像源列表,最后添加默认源作为兜底 |
| 126 | + mirror_list = [] |
| 127 | + if mirrors: |
| 128 | + if isinstance(mirrors, list): |
| 129 | + mirror_list.extend(mirrors) |
| 130 | + else: |
| 131 | + mirror_list.append(mirrors) |
| 132 | + |
| 133 | + # 默认源兜底(PyPI 官方源) |
| 134 | + mirror_list.append(None) |
| 135 | + |
| 136 | + # 逐个尝试镜像源 |
| 137 | + for idx, mirror in enumerate(mirror_list): |
| 138 | + try: |
| 139 | + if mirror: |
| 140 | + print( |
| 141 | + f"正在使用镜像源安装或更新环境... ({idx + 1}/{len(mirror_list)}): {mirror}" |
| 142 | + ) |
| 143 | + else: |
| 144 | + print(f"正在使用默认源安装或更新环境... ({idx + 1}/{len(mirror_list)})") |
| 145 | + |
| 146 | + cmd = [ |
| 147 | + sys.executable, |
| 148 | + "-m", |
| 149 | + "pip", |
| 150 | + "install", |
| 151 | + "-r", |
| 152 | + str(req_path), |
| 153 | + "--no-warn-script-location", |
| 154 | + ] |
| 155 | + |
| 156 | + if mirror: |
| 157 | + cmd.extend(["-i", mirror]) |
| 158 | + |
| 159 | + subprocess.check_call( |
| 160 | + cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL |
| 161 | + ) |
| 162 | + print("安装完成!") |
| 163 | + return True |
| 164 | + except Exception as e: |
| 165 | + if idx < len(mirror_list) - 1: |
| 166 | + print(f"当前镜像源失败,尝试下一个...") |
| 167 | + continue |
| 168 | + else: |
| 169 | + print("环境加载失败,请检查网络与环境后重新尝试!") |
| 170 | + return False |
| 171 | + |
| 172 | + return False |
| 173 | + |
| 174 | + |
| 175 | +# ==================== 主入口函数 ==================== |
| 176 | + |
| 177 | + |
| 178 | +def check_and_install_dependencies(): |
| 179 | + """检查并自动安装或更新项目依赖。 |
| 180 | +
|
| 181 | + 该函数会: |
| 182 | + 1、读取当前项目版本号(从 interface.json) |
| 183 | + 2、读取上次安装时的版本号(从 pip_config.json) |
| 184 | + 3、若版本号不一致或当前版本为 unknown,则执行依赖安装 |
| 185 | + 4、安装成功后更新配置文件中的版本号 |
| 186 | +
|
| 187 | + 注意: |
| 188 | + - 可通过修改 config/pip_config.json 中的 enable_pip_install 字段禁用自动安装 |
| 189 | + - 可通过修改 mirrors 字段自定义 pip 镜像源列表,支持多个镜像源,按顺序尝试 |
| 190 | + - 所有镜像源失败后会自动使用 PyPI 官方默认源兜底 |
| 191 | + """ |
| 192 | + pip_config = _read_pip_config() |
| 193 | + current_version = _read_interface_version() |
| 194 | + last_version = pip_config.get("last_version", "unknown") |
| 195 | + enable_pip_install = pip_config.get("enable_pip_install", True) |
| 196 | + mirrors = pip_config.get("mirrors", None) |
| 197 | + |
| 198 | + # 版本不一致时触发依赖安装 |
| 199 | + if enable_pip_install and ( |
| 200 | + current_version != last_version or current_version == "unknown" |
| 201 | + ): |
| 202 | + if _install_requirements(mirrors=mirrors): |
| 203 | + _update_pip_config(current_version) |
0 commit comments