22import sys
33import subprocess
44import venv
5+ import time
6+ import urllib .request
57from pathlib import Path
8+ from concurrent .futures import ThreadPoolExecutor , as_completed
69
710# ================= 配置 =================
811VENV_DIR_NAME = ".venv"
912REQUIREMENTS_FILE = "requirements.txt"
10- # 这里的包名必须是 import 时使用的名字 (例如 "tomli" 而不是 "tomli>=2.0")
11- REQUIRED_IMPORTS = ["rich" , "tomli" ]
12- REQUIRED_PACKAGES = ["rich" , "tomli" ] # 如果需要安装,传给 pip 的包名
13+ REQUIRED_IMPORTS = ["rich" , "tomli" ]
14+ REQUIRED_PACKAGES = ["rich" , "tomli" ]
15+
16+ # 定义待测速的 PyPI 源列表
17+ PYPI_MIRRORS = {
18+ "Official" : "https://pypi.org/simple" ,
19+ "Tsinghua" : "https://pypi.tuna.tsinghua.edu.cn/simple" ,
20+ "Aliyun" : "https://mirrors.aliyun.com/pypi/simple" ,
21+ "Tencent" : "https://mirrors.cloud.tencent.com/pypi/simple" ,
22+ }
1323# =======================================
1424
25+
1526def get_venv_paths (root_dir : Path ):
1627 """获取虚拟环境的路径"""
1728 venv_dir = root_dir / VENV_DIR_NAME
@@ -23,62 +34,126 @@ def get_venv_paths(root_dir: Path):
2334 pip_executable = venv_dir / "bin" / "pip"
2435 return venv_dir , python_executable , pip_executable
2536
37+
2638def check_venv_integrity (python_executable : Path ) -> bool :
27- """
28- 检查 venv 里的 Python 是否已经安装了必要的包。
29- 通过调用 venv python 执行尝试导入的脚本来验证。
30- """
39+ """检查 venv 里的 Python 是否已经安装了必要的包。"""
3140 if not python_executable .exists ():
3241 return False
33-
34- # 构建检查脚本: "import rich; import tomli;"
42+
3543 check_script = "; " .join ([f"import { pkg } " for pkg in REQUIRED_IMPORTS ])
36-
3744 try :
38- # 使用 -c 执行导入检查,stdout/stderr 扔掉,只看返回码
3945 subprocess .run (
4046 [str (python_executable ), "-c" , check_script ],
4147 stdout = subprocess .DEVNULL ,
4248 stderr = subprocess .DEVNULL ,
43- check = True
49+ check = True ,
4450 )
4551 return True
4652 except subprocess .CalledProcessError :
4753 return False
4854
55+
4956def create_venv_if_missing (venv_dir : Path ):
5057 if not venv_dir .exists ():
5158 print (f"[Bootstrap] Creating virtual environment at: { venv_dir } ..." , flush = True )
5259 venv .create (venv_dir , with_pip = True )
5360
61+
62+ def test_mirror_latency (name , url , timeout = 2 ):
63+ """测试单个镜像源的延迟"""
64+ start_time = time .time ()
65+ try :
66+ # 使用 HEAD 请求或者极小的 GET 请求来测试连接
67+ # 标准库 urllib 发送请求
68+ req = urllib .request .Request (url , method = "HEAD" )
69+ with urllib .request .urlopen (req , timeout = timeout ) as response :
70+ if response .status == 200 :
71+ latency = (time .time () - start_time ) * 1000 # ms
72+ return name , url , latency
73+ except Exception :
74+ pass
75+ return name , url , float ("inf" )
76+
77+
78+ def get_fastest_mirror ():
79+ """并发测试所有源,返回速度最快的源 URL"""
80+ print ("[Bootstrap] Selecting the fastest PyPI mirror..." , flush = True )
81+
82+ fastest_url = None
83+ min_latency = float ("inf" )
84+ best_name = "Unknown"
85+
86+ # 使用线程池并发测速,避免串行等待
87+ with ThreadPoolExecutor (max_workers = len (PYPI_MIRRORS )) as executor :
88+ futures = [
89+ executor .submit (test_mirror_latency , name , url )
90+ for name , url in PYPI_MIRRORS .items ()
91+ ]
92+
93+ for future in as_completed (futures ):
94+ name , url , latency = future .result ()
95+ if latency < float ("inf" ):
96+ # print(f" - {name}: {latency:.2f} ms") # 调试时可打开
97+ if latency < min_latency :
98+ min_latency = latency
99+ fastest_url = url
100+ best_name = name
101+
102+ if fastest_url :
103+ print (f"[Bootstrap] Selected { best_name } (Latency: { min_latency :.0f} ms)" , flush = True )
104+ return fastest_url
105+ else :
106+ print ("[Bootstrap] Warning: All mirrors check failed. Fallback to Official." , flush = True )
107+ return PYPI_MIRRORS ["Official" ]
108+
109+
54110def install_dependencies (root_dir : Path , pip_executable : Path ):
55111 req_path = root_dir / REQUIREMENTS_FILE
56- base_cmd = [str (pip_executable ), "install" , "--disable-pip-version-check" , "-i" , "https://pypi.tuna.tsinghua.edu.cn/simple" ]
57112
58- print (f"[Bootstrap] Installing dependencies..." , flush = True )
113+ # === 获取最快源 ===
114+ mirror_url = get_fastest_mirror ()
115+ # ================
116+
117+ base_cmd = [
118+ str (pip_executable ),
119+ "install" ,
120+ "--disable-pip-version-check" ,
121+ "-i" ,
122+ mirror_url , # 使用动态获取的源
123+ ]
124+
125+ # 如果选中的不是官方源,通常需要信任该 host,避免 pip 报 SSL 警告
126+ if "pypi.org" not in mirror_url :
127+ from urllib .parse import urlparse
128+ hostname = urlparse (mirror_url ).hostname
129+ base_cmd .extend (["--trusted-host" , hostname ])
130+
131+ print ("[Bootstrap] Installing dependencies..." , flush = True )
59132 try :
60133 if req_path .exists ():
61134 subprocess .run (base_cmd + ["-r" , str (req_path )], check = True )
62135 else :
63136 subprocess .run (base_cmd + REQUIRED_PACKAGES , check = True )
64137 except subprocess .CalledProcessError :
65- print (f "[Bootstrap] Error: Failed to install dependencies." , file = sys .stderr )
138+ print ("[Bootstrap] Error: Failed to install dependencies." , file = sys .stderr )
66139 sys .exit (1 )
67140
141+
68142def restart_in_venv (python_executable : Path ):
69143 """重启当前脚本到 venv 环境"""
70144 env = os .environ .copy ()
71145 env ["GRADER_BOOTSTRAPPED" ] = "1"
72146 args = [str (python_executable )] + sys .argv
73-
147+
74148 if sys .platform == "win32" :
75149 subprocess .run (args , env = env , check = True )
76150 sys .exit (0 )
77151 else :
78152 os .execv (str (python_executable ), args )
79153
154+
80155def initialize ():
81- # 1. Fast Path: 如果当前环境(无论是否venv)已经能用,直接通过
156+ # 1. Fast Path
82157 try :
83158 for pkg in REQUIRED_IMPORTS :
84159 __import__ (pkg )
@@ -90,21 +165,23 @@ def initialize():
90165 venv_dir , venv_python , venv_pip = get_venv_paths (root_dir )
91166 is_in_venv = os .environ .get ("GRADER_BOOTSTRAPPED" ) == "1"
92167
93- # 2. 如果已经在 venv 里但还是缺包,说明环境坏了,强制修复
168+ # 2. 修复损坏环境
94169 if is_in_venv :
95170 print ("[Bootstrap] In venv but dependencies missing. Repairing..." , flush = True )
96171 install_dependencies (root_dir , venv_pip )
97172 return
98173
99- # 3. 如果在系统环境 (用户没激活 venv 直接跑脚本)
100- # 逻辑:先检查 venv 是否存在且完好,如果是,直接重启进去,不要跑 install
101-
174+ # 3. 创建与首次安装
102175 create_venv_if_missing (venv_dir )
103176
104- # === 关键优化点 ===
105- # 只有当 venv 里的 python 无法导入指定包时,才运行 pip install
106177 if not check_venv_integrity (venv_python ):
107178 install_dependencies (root_dir , venv_pip )
108- # =================
109-
179+
110180 restart_in_venv (venv_python )
181+
182+ # 示例用法,实际使用时这里可能是 main()
183+ if __name__ == "__main__" :
184+ initialize ()
185+ # 下面写你的正常业务逻辑
186+ import rich
187+ print ("[Success] Environment is ready!" )
0 commit comments