mirror of
https://github.com/fastapi-practices/fastapi_best_architecture.git
synced 2026-03-13 09:31:31 +08:00
* 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
107 lines
3.8 KiB
Python
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)
|