Skip to content

Commit a461f78

Browse files
authored
Optimize the installation of plugin dependencies (#700)
* Optimize the installation of plugin dependencies * Remove enumerate * Update main params
1 parent c306432 commit a461f78

File tree

7 files changed

+72
-55
lines changed

7 files changed

+72
-55
lines changed

Dockerfile

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,6 @@ COPY . /fba
3636

3737
COPY --from=builder /usr/local /usr/local
3838

39-
# Install plugin dependencies
40-
WORKDIR /fba
41-
ENV PYTHONPATH=/fba
42-
RUN python3 backend/scripts/init_plugin.py
43-
4439
# === FastAPI server image ===
4540
FROM base_server AS fastapi_server
4641

backend/app/admin/service/plugin_service.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,9 @@ async def install(*, type: PluginType, file: UploadFile | None = None, repo_url:
5656
if not file:
5757
raise errors.RequestError(msg='ZIP 压缩包不能为空')
5858
return await install_zip_plugin(file)
59-
elif type == PluginType.git:
60-
if not repo_url:
61-
raise errors.RequestError(msg='Git 仓库地址不能为空')
62-
return await install_git_plugin(repo_url)
59+
if not repo_url:
60+
raise errors.RequestError(msg='Git 仓库地址不能为空')
61+
return await install_git_plugin(repo_url)
6362

6463
@staticmethod
6564
async def uninstall(*, plugin: str):

backend/cli.py

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,29 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3+
import os
4+
35
from dataclasses import dataclass
46
from typing import Annotated
57

68
import cappa
79
import uvicorn
810

911
from rich.panel import Panel
10-
from rich.progress import (
11-
Progress,
12-
SpinnerColumn,
13-
TextColumn,
14-
TimeElapsedColumn,
15-
)
1612
from rich.text import Text
1713

1814
from backend import console, get_version
1915
from backend.common.exception.errors import BaseExceptionMixin
2016
from backend.core.conf import settings
21-
from backend.plugin.tools import get_plugins, install_requirements
2217
from backend.utils._await import run_await
2318
from backend.utils.file_ops import install_git_plugin, install_zip_plugin
2419

2520

2621
def run(host: str, port: int, reload: bool, workers: int | None) -> None:
27-
console.print(Text('检测插件依赖...', style='bold cyan'))
28-
29-
plugins = get_plugins()
30-
31-
with Progress(
32-
SpinnerColumn(finished_text='[bold green]插件依赖安装完成[/]'),
33-
TextColumn('[green]{task.completed}/{task.total}[/]'),
34-
TimeElapsedColumn(),
35-
console=console,
36-
) as progress:
37-
task = progress.add_task('安装插件依赖...', total=len(plugins))
38-
for i, plugin in enumerate(plugins):
39-
install_requirements(plugin)
40-
progress.advance(task)
41-
4222
url = f'http://{host}:{port}'
4323
docs_url = url + settings.FASTAPI_DOCS_URL
4424
redoc_url = url + settings.FASTAPI_REDOC_URL
4525
openapi_url = url + settings.FASTAPI_OPENAPI_URL
4626

47-
console.print(Text('启动 fba 服务...', style='bold magenta'))
48-
4927
panel_content = Text()
5028
panel_content.append(f'📝 Swagger 文档: {docs_url}\n', style='blue')
5129
panel_content.append(f'📚 Redoc 文档: {redoc_url}\n', style='yellow')
@@ -56,7 +34,14 @@ def run(host: str, port: int, reload: bool, workers: int | None) -> None:
5634
)
5735

5836
console.print(Panel(panel_content, title='fba 服务信息', border_style='purple', padding=(1, 2)))
59-
uvicorn.run(app='backend.main:app', host=host, port=port, reload=not reload, workers=workers)
37+
uvicorn.run(
38+
app='backend.main:app',
39+
host=host,
40+
port=port,
41+
reload=not reload,
42+
reload_excludes=[os.path.abspath('../.venv' if 'backend' in os.getcwd() else '.venv')],
43+
workers=workers,
44+
)
6045

6146

6247
def install_plugin(path: str, repo_url: str) -> None:

backend/main.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3+
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
4+
from rich.text import Text
5+
6+
from backend import console
37
from backend.core.registrar import register_app
8+
from backend.plugin.tools import get_plugins, install_requirements
9+
from backend.utils.timezone import timezone
10+
11+
_print_log_style = f'{timezone.to_str(timezone.now(), "%Y-%m-%d %H:%M:%S.%M0")} | fba | - | '
12+
console.print(Text(f'{_print_log_style}检测插件依赖...', style='bold cyan'))
13+
14+
_plugins = get_plugins()
15+
16+
with Progress(
17+
SpinnerColumn(finished_text=f'[bold green]{_print_log_style}插件准备就绪[/]'),
18+
TextColumn('[bold green]{task.completed}/{task.total}[/]'),
19+
TimeElapsedColumn(),
20+
console=console,
21+
) as progress:
22+
task = progress.add_task('安装插件依赖...', total=len(_plugins))
23+
for plugin in _plugins:
24+
install_requirements(plugin)
25+
progress.advance(task)
26+
27+
console.print(Text(f'{_print_log_style}启动服务...', style='bold magenta'))
428

529
app = register_app()

backend/plugin/tools.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
import warnings
99

1010
from functools import lru_cache
11+
from importlib.metadata import PackageNotFoundError, distribution
1112
from typing import Any
1213

1314
import rtoml
1415

1516
from fastapi import APIRouter, Depends, Request
17+
from packaging.requirements import Requirement
1618
from starlette.concurrency import run_in_threadpool
1719

1820
from backend.common.enums import StatusType
@@ -33,6 +35,10 @@ class PluginInjectError(Exception):
3335
"""插件注入错误"""
3436

3537

38+
class PluginInstallError(Exception):
39+
"""插件安装错误"""
40+
41+
3642
@lru_cache
3743
def get_plugins() -> list[str]:
3844
"""获取插件列表"""
@@ -262,7 +268,24 @@ def install_requirements(plugin: str | None) -> None:
262268

263269
for plugin in plugins:
264270
requirements_file = os.path.join(PLUGIN_DIR, plugin, 'requirements.txt')
271+
missing_dependencies = False
265272
if os.path.exists(requirements_file):
273+
with open(requirements_file, 'r', encoding='utf-8') as f:
274+
for line in f:
275+
line = line.strip()
276+
if not line or line.startswith('#'):
277+
continue
278+
try:
279+
req = Requirement(line)
280+
dependency = req.name.lower()
281+
except Exception as e:
282+
raise PluginInstallError(f'插件 {plugin} 依赖 {line} 格式错误: {str(e)}') from e
283+
try:
284+
distribution(dependency)
285+
except PackageNotFoundError:
286+
missing_dependencies = True
287+
288+
if missing_dependencies:
266289
try:
267290
ensurepip_install = [sys.executable, '-m', 'ensurepip', '--upgrade']
268291
pip_install = [sys.executable, '-m', 'pip', 'install', '-r', requirements_file]
@@ -271,7 +294,7 @@ def install_requirements(plugin: str | None) -> None:
271294
subprocess.check_call(ensurepip_install, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
272295
subprocess.check_call(pip_install, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
273296
except subprocess.CalledProcessError as e:
274-
raise PluginInjectError(f'插件 {plugin} 依赖安装失败:{e.stderr}') from e
297+
raise PluginInstallError(f'插件 {plugin} 依赖安装失败:{e}') from e
275298

276299

277300
def uninstall_requirements(plugin: str) -> None:
@@ -285,9 +308,9 @@ def uninstall_requirements(plugin: str) -> None:
285308
if os.path.exists(requirements_file):
286309
try:
287310
pip_uninstall = [sys.executable, '-m', 'pip', 'uninstall', '-r', requirements_file, '-y']
288-
subprocess.check_call(pip_uninstall)
311+
subprocess.check_call(pip_uninstall, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
289312
except subprocess.CalledProcessError as e:
290-
raise PluginInjectError(f'插件 {plugin} 依赖卸载失败:{e.stderr}') from e
313+
raise PluginInstallError(f'插件 {plugin} 依赖卸载失败:{e}') from e
291314

292315

293316
async def install_requirements_async(plugin: str | None = None) -> None:

backend/run.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3+
import os
4+
35
import uvicorn
46

57
if __name__ == '__main__':
68
# 为什么独立此启动文件:https://stackoverflow.com/questions/64003384
79
# 如果你喜欢在 IDE 中进行 DEBUG,可在 IDE 中直接右键启动此文件
810
# 如果你喜欢通过 print 方式进行调试,建议使用 fastapi cli 方式启动服务
911
try:
10-
config = uvicorn.Config(app='backend.main:app', reload=True)
11-
server = uvicorn.Server(config)
12-
server.run()
12+
uvicorn.run(
13+
app='backend.main:app',
14+
host='127.0.0.1',
15+
port=8000,
16+
reload=True,
17+
reload_excludes=[os.path.abspath('../.venv')],
18+
)
1319
except Exception as e:
1420
raise e

backend/scripts/init_plugin.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)