11#!/usr/bin/env python3
22# -*- coding: utf-8 -*-
3+ import importlib
34import inspect
45import os
6+ import warnings
57
68import rtoml
79
8- from fastapi import FastAPI
10+ from fastapi import APIRouter
911
1012from backend .core .path_conf import PLUGIN_DIR
11- from backend .utils .import_parse import import_module_cached
1213
1314
1415def get_plugins () -> list [str ]:
@@ -31,59 +32,88 @@ def get_plugin_models() -> list:
3132 plugins = get_plugins ()
3233 for plugin in plugins :
3334 module_path = f'backend.plugin.{ plugin } .model'
34- module = import_module_cached (module_path )
35+ module = importlib . import_module (module_path )
3536 for name , obj in inspect .getmembers (module ):
3637 if inspect .isclass (obj ):
3738 classes .append (obj )
3839 return classes
3940
4041
41- def register_plugin_router ( app : FastAPI ) :
42+ def plugin_router_inject ( router : APIRouter ) -> APIRouter :
4243 """
4344 插件路由注入
4445
45- :param app:
46- :return:
46+ :param router: 源总路由器
47+ :return: 注入插件路由后的总路由器
4748 """
4849 plugins = get_plugins ()
4950 for plugin in plugins :
5051 toml_path = os .path .join (PLUGIN_DIR , plugin , 'plugin.toml' )
5152 if not os .path .exists (toml_path ):
52- raise FileNotFoundError ('插件缺少 plugin.toml 配置文件' )
53+ raise FileNotFoundError (f'插件 { plugin } 缺少 plugin .toml 配置文件,请检查插件是否合法 ' )
5354
54- toml_data = rtoml .load (toml_path )
55- app_name = toml_data .get ('app' , '' )
56- api_module = toml_data .get ('api' , {}).get ('module' , '' )
57- prefix = toml_data .get ('api' , {}).get ('prefix' , '' )
55+ # 解析 plugin.toml
56+ with open (toml_path , 'r' , encoding = 'utf-8' ) as f :
57+ data = rtoml .load (f )
58+ app_name = data .get ('app' , '' )
59+ prefix = data .get ('api' , {}).get ('prefix' , '' )
60+ tags = data .get ('api' , {}).get ('tags' , [])
5861
59- if app_name :
60- if '.' in api_module :
61- module_path = f'backend.app.{ app_name } .api.{ api_module } '
62- else :
63- module_path = f'backend.app.{ app_name } .api'
64- else :
65- module_path = 'backend.app'
62+ # 插件中 API 路由文件的路径
63+ plugin_api_path = os .path .join (PLUGIN_DIR , plugin , 'api' )
64+ if not os .path .exists (plugin_api_path ):
65+ raise FileNotFoundError (f'插件 { plugin } 缺少 api 目录,请检查插件文件是否完整' )
6666
67- try :
68- module = import_module_cached (module_path )
69- except ImportError as e :
70- raise ImportError (f'导入模块 { module_path } 失败:{ e } ' ) from e
67+ # 路由注入
68+ if app_name :
69+ # 非独立应用:将插件中的路由注入到源程序对应模块的路由中
70+ for root , _ , api_files in os .walk (plugin_api_path ):
71+ for file in api_files :
72+ if file .endswith ('.py' ) and file != '__init__.py' :
73+ api_files_path = os .path .join (root , file )
74+
75+ # 获取插件路由模块
76+ path_to_module_str = os .path .relpath (api_files_path , PLUGIN_DIR ).replace (os .sep , '.' )[:- 3 ]
77+ module_path = f'backend.plugin.{ path_to_module_str } '
78+ try :
79+ module = importlib .import_module (module_path )
80+ except ImportError as e :
81+ raise ImportError (f'导入模块 { module_path } 失败:{ e } ' ) from e
82+ plugin_router = getattr (module , 'router' , None )
83+ if not plugin_router :
84+ warnings .warn (
85+ f'目标模块 { module_path } 中没有有效的 router,请检查插件文件是否完整' ,
86+ FutureWarning ,
87+ )
88+ continue
89+
90+ # 获取源程序路由模块
91+ relative_path = os .path .relpath (root , plugin_api_path )
92+ target_module_path = f'backend.app.{ app_name } .api.{ relative_path .replace (os .sep , "." )} '
93+ try :
94+ target_module = importlib .import_module (target_module_path )
95+ except ImportError as e :
96+ raise ImportError (f'导入目标模块 { target_module_path } 失败:{ e } ' ) from e
97+ target_router = getattr (target_module , 'router' , None )
98+ if not target_router or not isinstance (target_router , APIRouter ):
99+ raise AttributeError (f'目标模块 { module_path } 中没有有效的 router,请检查插件文件是否完整' )
100+
101+ # 将插件路由注入到目标 router 中
102+ target_router .include_router (
103+ router = plugin_router ,
104+ prefix = prefix ,
105+ tags = tags if type (tags ) is list else [tags ],
106+ )
71107 else :
72- if app_name and '.' in api_module :
73- # 从 backend.app.xxx.api.vx.xxx.__init__.py 文件中获取路由
74- router = getattr (module , 'router' , None )
75- else :
76- if 'api' in module_path :
77- # 从 backend.app.xxx.api 下的 router.py 文件中获取路由
78- router = getattr (module , api_module .split ('.' )[0 ], None )
79- else :
80- # 从 backend.app 下的 router.py 文件中获取路由
81- router = getattr (module , 'router' , None )
82-
83- if not router :
84- raise ImportError (f'模块 { module_path } 中不存在路由' )
85-
86- if app_name :
87- app .include_router (router , prefix = f'/{ prefix } ' )
88- else :
89- app .include_router (router )
108+ # 独立应用:将插件中的路由直接注入到 app 中
109+ module_path = f'backend.plugin.{ plugin } .api.router'
110+ try :
111+ target_module = importlib .import_module (module_path )
112+ except ImportError as e :
113+ raise ImportError (f'导入目标模块 { module_path } 失败:{ e } ' ) from e
114+ target_router = getattr (target_module , 'router' , None )
115+ if not target_router or not isinstance (target_router , APIRouter ):
116+ raise AttributeError (f'目标模块 { module_path } 中没有有效的 router,请检查插件文件是否完整' )
117+ router .include_router (target_router )
118+
119+ return router
0 commit comments