Files
fastapi_best_architecture/backend/plugin/requirements.py
Wu Clan 6f1c27786d Add multi level caching and optimize caching (#1054)
* Add multi level caching and optimize caching

* optimize current caching

* improved decorators

* improved local

* add pub/sub

* fix serialize

* Improve pub and sub

* Add comment

* Fix lru_cache maxsize

* Fix lint

* Fix config warmup
2026-02-02 17:18:49 +08:00

107 lines
3.8 KiB
Python

import os
import subprocess
import sys
from importlib.metadata import PackageNotFoundError, distribution
from packaging.requirements import Requirement
from starlette.concurrency import run_in_threadpool
from backend.core.conf import settings
from backend.core.path_conf import PLUGIN_DIR
from backend.plugin.core import get_plugins
from backend.plugin.errors import PluginInstallError
def _is_in_virtualenv() -> bool:
"""检测当前是否在虚拟环境中运行"""
return hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)
def install_requirements(plugin: str | None) -> None: # noqa: C901
"""
安装插件依赖
:param plugin: 指定插件名,否则检查所有插件
:return:
"""
plugins = [plugin] if plugin else get_plugins()
for plugin in plugins:
requirements_file = PLUGIN_DIR / plugin / 'requirements.txt'
missing_dependencies = False
if os.path.exists(requirements_file):
with open(requirements_file, encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
try:
req = Requirement(line)
dependency = req.name.lower()
except Exception as e:
raise PluginInstallError(f'插件 {plugin} 依赖 {line} 格式错误: {e!s}') from e
try:
distribution(dependency)
except PackageNotFoundError:
missing_dependencies = True
if missing_dependencies:
pip_install = ['uv', 'pip', 'install', '-r', requirements_file]
if not _is_in_virtualenv():
pip_install.append('--system')
if settings.PLUGIN_PIP_CHINA:
pip_install.extend(['-i', settings.PLUGIN_PIP_INDEX_URL])
max_retries = settings.PLUGIN_PIP_MAX_RETRY
for attempt in range(max_retries):
try:
subprocess.check_call(pip_install)
break
except subprocess.TimeoutExpired:
if attempt == max_retries - 1:
raise PluginInstallError(f'插件 {plugin} 依赖安装超时')
continue
except subprocess.CalledProcessError as e:
if attempt == max_retries - 1:
raise PluginInstallError(f'插件 {plugin} 依赖安装失败:{e}') from e
continue
def uninstall_requirements(plugin: str) -> None:
"""
卸载插件依赖
:param plugin: 插件名称
:return:
"""
requirements_file = PLUGIN_DIR / plugin / 'requirements.txt'
if os.path.exists(requirements_file):
try:
pip_uninstall = ['uv', 'pip', 'uninstall', '-r', str(requirements_file), '-y']
if not _is_in_virtualenv():
pip_uninstall.append('--system')
subprocess.check_call(pip_uninstall, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError as e:
raise PluginInstallError(f'插件 {plugin} 依赖卸载失败:{e}') from e
async def install_requirements_async(plugin: str | None = None) -> None:
"""
异步安装插件依赖
由于 Windows 平台限制,无法实现完美的全异步方案,详情:
https://stackoverflow.com/questions/44633458/why-am-i-getting-notimplementederror-with-async-and-await-on-windows
"""
await run_in_threadpool(install_requirements, plugin)
async def uninstall_requirements_async(plugin: str) -> None:
"""
异步卸载插件依赖
:param plugin: 插件名称
:return:
"""
await run_in_threadpool(uninstall_requirements, plugin)