| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  | import enum | 
					
						
							| 
									
										
										
										
											2024-06-20 15:16:21 +08:00
										 |  |  | import importlib.util | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  | import json | 
					
						
							|  |  |  | import logging | 
					
						
							|  |  |  | import os | 
					
						
							| 
									
										
										
										
											2024-09-12 15:50:49 +08:00
										 |  |  | from pathlib import Path | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  | from typing import Any, Optional | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from pydantic import BaseModel | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-05 00:13:04 +08:00
										 |  |  | from core.helper.position_helper import sort_to_dict_by_position_map | 
					
						
							| 
									
										
										
										
											2024-03-13 20:29:38 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | class ExtensionModule(enum.Enum): | 
					
						
							| 
									
										
										
										
											2024-09-10 17:00:20 +08:00
										 |  |  |     MODERATION = "moderation" | 
					
						
							|  |  |  |     EXTERNAL_DATA_TOOL = "external_data_tool" | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ModuleExtension(BaseModel): | 
					
						
							| 
									
										
										
										
											2024-06-14 01:05:37 +08:00
										 |  |  |     extension_class: Any = None | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  |     name: str | 
					
						
							|  |  |  |     label: Optional[dict] = None | 
					
						
							|  |  |  |     form_schema: Optional[list] = None | 
					
						
							|  |  |  |     builtin: bool = True | 
					
						
							|  |  |  |     position: Optional[int] = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Extensible: | 
					
						
							|  |  |  |     module: ExtensionModule | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     name: str | 
					
						
							|  |  |  |     tenant_id: str | 
					
						
							|  |  |  |     config: Optional[dict] = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, tenant_id: str, config: Optional[dict] = None) -> None: | 
					
						
							|  |  |  |         self.tenant_id = tenant_id | 
					
						
							|  |  |  |         self.config = config | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def scan_extensions(cls): | 
					
						
							| 
									
										
										
										
											2024-12-24 18:38:51 +08:00
										 |  |  |         extensions = [] | 
					
						
							|  |  |  |         position_map: dict[str, int] = {} | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # get the path of the current class | 
					
						
							| 
									
										
										
										
											2024-09-10 17:00:20 +08:00
										 |  |  |         current_path = os.path.abspath(cls.__module__.replace(".", os.path.sep) + ".py") | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  |         current_dir_path = os.path.dirname(current_path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # traverse subdirectories | 
					
						
							|  |  |  |         for subdir_name in os.listdir(current_dir_path): | 
					
						
							| 
									
										
										
										
											2024-09-10 17:00:20 +08:00
										 |  |  |             if subdir_name.startswith("__"): | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             subdir_path = os.path.join(current_dir_path, subdir_name) | 
					
						
							|  |  |  |             extension_name = subdir_name | 
					
						
							|  |  |  |             if os.path.isdir(subdir_path): | 
					
						
							|  |  |  |                 file_names = os.listdir(subdir_path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 # is builtin extension, builtin extension | 
					
						
							|  |  |  |                 # in the front-end page and business logic, there are special treatments. | 
					
						
							|  |  |  |                 builtin = False | 
					
						
							| 
									
										
										
										
											2024-12-24 18:38:51 +08:00
										 |  |  |                 # default position is 0 can not be None for sort_to_dict_by_position_map | 
					
						
							|  |  |  |                 position = 0 | 
					
						
							| 
									
										
										
										
											2024-09-10 17:00:20 +08:00
										 |  |  |                 if "__builtin__" in file_names: | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  |                     builtin = True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 17:00:20 +08:00
										 |  |  |                     builtin_file_path = os.path.join(subdir_path, "__builtin__") | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  |                     if os.path.exists(builtin_file_path): | 
					
						
							| 
									
										
										
										
											2024-09-12 15:50:49 +08:00
										 |  |  |                         position = int(Path(builtin_file_path).read_text(encoding="utf-8").strip()) | 
					
						
							| 
									
										
										
										
											2024-09-04 08:41:12 +08:00
										 |  |  |                     position_map[extension_name] = position | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 17:00:20 +08:00
										 |  |  |                 if (extension_name + ".py") not in file_names: | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  |                     logging.warning(f"Missing {extension_name}.py file in {subdir_path}, Skip.") | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 # Dynamic loading {subdir_name}.py file and find the subclass of Extensible | 
					
						
							| 
									
										
										
										
											2024-09-10 17:00:20 +08:00
										 |  |  |                 py_path = os.path.join(subdir_path, extension_name + ".py") | 
					
						
							| 
									
										
										
										
											2024-03-29 22:07:34 +09:00
										 |  |  |                 spec = importlib.util.spec_from_file_location(extension_name, py_path) | 
					
						
							| 
									
										
										
										
											2024-06-20 15:16:21 +08:00
										 |  |  |                 if not spec or not spec.loader: | 
					
						
							|  |  |  |                     raise Exception(f"Failed to load module {extension_name} from {py_path}") | 
					
						
							| 
									
										
										
										
											2024-03-29 22:07:34 +09:00
										 |  |  |                 mod = importlib.util.module_from_spec(spec) | 
					
						
							|  |  |  |                 spec.loader.exec_module(mod) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 extension_class = None | 
					
						
							|  |  |  |                 for name, obj in vars(mod).items(): | 
					
						
							|  |  |  |                     if isinstance(obj, type) and issubclass(obj, cls) and obj != cls: | 
					
						
							|  |  |  |                         extension_class = obj | 
					
						
							|  |  |  |                         break | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if not extension_class: | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  |                     logging.warning(f"Missing subclass of {cls.__name__} in {py_path}, Skip.") | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-24 18:38:51 +08:00
										 |  |  |                 json_data: dict[str, Any] = {} | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  |                 if not builtin: | 
					
						
							| 
									
										
										
										
											2024-09-10 17:00:20 +08:00
										 |  |  |                     if "schema.json" not in file_names: | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  |                         logging.warning(f"Missing schema.json file in {subdir_path}, Skip.") | 
					
						
							|  |  |  |                         continue | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 17:00:20 +08:00
										 |  |  |                     json_path = os.path.join(subdir_path, "schema.json") | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  |                     json_data = {} | 
					
						
							|  |  |  |                     if os.path.exists(json_path): | 
					
						
							| 
									
										
										
										
											2024-09-10 17:00:20 +08:00
										 |  |  |                         with open(json_path, encoding="utf-8") as f: | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  |                             json_data = json.load(f) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 17:00:20 +08:00
										 |  |  |                 extensions.append( | 
					
						
							|  |  |  |                     ModuleExtension( | 
					
						
							|  |  |  |                         extension_class=extension_class, | 
					
						
							|  |  |  |                         name=extension_name, | 
					
						
							|  |  |  |                         label=json_data.get("label"), | 
					
						
							|  |  |  |                         form_schema=json_data.get("form_schema"), | 
					
						
							|  |  |  |                         builtin=builtin, | 
					
						
							|  |  |  |                         position=position, | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         sorted_extensions = sort_to_dict_by_position_map( | 
					
						
							|  |  |  |             position_map=position_map, data=extensions, name_func=lambda x: x.name | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-11-06 19:36:16 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return sorted_extensions |