@@ -286,7 +286,62 @@ def build_final_router() -> APIRouter:
286286 return main_router
287287
288288
289- def install_requirements (plugin : str | None ) -> None :
289+ def _ensure_pip_available () -> bool :
290+ """确保 pip 在虚拟环境中可用"""
291+ try :
292+ result = subprocess .run ([sys .executable , '-m' , 'pip' , '--version' ], capture_output = True , text = True )
293+ if result .returncode == 0 :
294+ return True
295+ except (subprocess .TimeoutExpired , subprocess .SubprocessError , FileNotFoundError ):
296+ pass
297+
298+ # 尝试使用 ensurepip
299+ try :
300+ subprocess .check_call (
301+ [sys .executable , '-m' , 'ensurepip' , '--default-pip' ],
302+ stdout = subprocess .DEVNULL ,
303+ stderr = subprocess .DEVNULL ,
304+ )
305+ result = subprocess .run ([sys .executable , '-m' , 'pip' , '--version' ], capture_output = True , text = True )
306+ if result .returncode == 0 :
307+ return True
308+ except (subprocess .CalledProcessError , subprocess .TimeoutExpired , subprocess .SubprocessError , FileNotFoundError ):
309+ pass
310+
311+ # 尝试下载并安装
312+ try :
313+ import os
314+ import tempfile
315+
316+ import httpx
317+
318+ try :
319+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.py' , delete = False ) as f :
320+ with httpx .Client (timeout = 3 ) as client :
321+ get_pip_url = 'https://bootstrap.pypa.io/get-pip.py'
322+ response = client .get (get_pip_url )
323+ response .raise_for_status ()
324+ f .write (response .text )
325+ temp_file = f .name
326+ except Exception : # noqa: ignore
327+ return False
328+
329+ try :
330+ subprocess .check_call ([sys .executable , temp_file ], stdout = subprocess .DEVNULL , stderr = subprocess .DEVNULL )
331+ result = subprocess .run ([sys .executable , '-m' , 'pip' , '--version' ], capture_output = True , text = True )
332+ return result .returncode == 0
333+ finally :
334+ try :
335+ os .unlink (temp_file )
336+ except OSError :
337+ pass
338+ except Exception : # noqa: ignore
339+ pass
340+
341+ return False
342+
343+
344+ def install_requirements (plugin : str | None ) -> None : # noqa: C901
290345 """
291346 安装插件依赖
292347
@@ -316,12 +371,30 @@ def install_requirements(plugin: str | None) -> None:
316371
317372 if missing_dependencies :
318373 try :
319- ensurepip_install = [sys .executable , '-m' , 'ensurepip' , '--upgrade' ]
374+ if not _ensure_pip_available ():
375+ raise PluginInstallError (f'pip 安装失败,无法继续安装插件 { plugin } 依赖' )
376+
320377 pip_install = [sys .executable , '-m' , 'pip' , 'install' , '-r' , requirements_file ]
321378 if settings .PLUGIN_PIP_CHINA :
322379 pip_install .extend (['-i' , settings .PLUGIN_PIP_INDEX_URL ])
323- subprocess .check_call (ensurepip_install , stdout = subprocess .DEVNULL , stderr = subprocess .DEVNULL )
324- subprocess .check_call (pip_install , stdout = subprocess .DEVNULL , stderr = subprocess .DEVNULL )
380+
381+ max_retries = settings .PLUGIN_PIP_MAX_RETRY
382+ for attempt in range (max_retries ):
383+ try :
384+ subprocess .check_call (
385+ pip_install ,
386+ stdout = subprocess .DEVNULL ,
387+ stderr = subprocess .DEVNULL ,
388+ )
389+ break
390+ except subprocess .TimeoutExpired :
391+ if attempt == max_retries - 1 :
392+ raise PluginInstallError (f'插件 { plugin } 依赖安装超时' )
393+ continue
394+ except subprocess .CalledProcessError as e :
395+ if attempt == max_retries - 1 :
396+ raise PluginInstallError (f'插件 { plugin } 依赖安装失败:{ e } ' ) from e
397+ continue
325398 except subprocess .CalledProcessError as e :
326399 raise PluginInstallError (f'插件 { plugin } 依赖安装失败:{ e } ' ) from e
327400
0 commit comments