mirror of
				https://github.com/nonebot/nonebot2.git
				synced 2025-10-30 22:46:40 +00:00 
			
		
		
		
	⚗️ change import hook
This commit is contained in:
		| @@ -4,7 +4,6 @@ | ||||
| 
 | ||||
| 为 NoneBot 插件开发提供便携的定义函数。 | ||||
| """ | ||||
| 
 | ||||
| import re | ||||
| import sys | ||||
| import pkgutil | ||||
| @@ -21,14 +20,17 @@ from nonebot.permission import Permission | ||||
| from nonebot.typing import T_State, T_StateFactory, T_Handler, T_RuleChecker | ||||
| from nonebot.rule import Rule, startswith, endswith, keyword, command, shell_command, ArgumentParser, regex | ||||
| 
 | ||||
| from .manager import PluginManager | ||||
| 
 | ||||
| if TYPE_CHECKING: | ||||
|     from nonebot.adapters import Bot, Event, MessageSegment | ||||
|     from nonebot.adapters import Bot, Event | ||||
| 
 | ||||
| plugins: Dict[str, "Plugin"] = {} | ||||
| """ | ||||
| :类型: ``Dict[str, Plugin]`` | ||||
| :说明: 已加载的插件 | ||||
| """ | ||||
| PLUGIN_NAMESPACE = "nonebot.loaded_plugins" | ||||
| 
 | ||||
| _export: ContextVar["Export"] = ContextVar("_export") | ||||
| _tmp_matchers: ContextVar[Set[Type[Matcher]]] = ContextVar("_tmp_matchers") | ||||
| @@ -950,7 +952,7 @@ def load_plugin(module_path: str) -> Optional[Plugin]: | ||||
|     """ | ||||
|     :说明: | ||||
| 
 | ||||
|       使用 ``importlib`` 加载单个插件,可以是本地插件或是通过 ``pip`` 安装的插件。 | ||||
|       使用 ``PluginManager`` 加载单个插件,可以是本地插件或是通过 ``pip`` 安装的插件。 | ||||
| 
 | ||||
|     :参数: | ||||
| 
 | ||||
| @@ -1006,42 +1008,38 @@ def load_plugins(*plugin_dir: str) -> Set[Plugin]: | ||||
|       - ``Set[Plugin]`` | ||||
|     """ | ||||
| 
 | ||||
|     def _load_plugin(module_info) -> Optional[Plugin]: | ||||
|         _tmp_matchers.set(set()) | ||||
|         _export.set(Export()) | ||||
|         name = module_info.name | ||||
|         if name.startswith("_"): | ||||
|     def _load_plugin(plugin_name: str) -> Optional[Plugin]: | ||||
|         if plugin_name.startswith("_"): | ||||
|             return None | ||||
| 
 | ||||
|         spec = module_info.module_finder.find_spec(name, None) | ||||
|         if not spec: | ||||
|             logger.warning( | ||||
|                 f"Module {name} cannot be loaded! Check module name first.") | ||||
|         elif spec.name in plugins: | ||||
|             return None | ||||
|         elif spec.name in sys.modules: | ||||
|             logger.warning( | ||||
|                 f"Module {spec.name} has been loaded by other plugin! Ignored") | ||||
|         _tmp_matchers.set(set()) | ||||
|         _export.set(Export()) | ||||
| 
 | ||||
|         if plugin_name in plugins: | ||||
|             return None | ||||
| 
 | ||||
|         try: | ||||
|             module = _load(spec) | ||||
|             module = manager.load_plugin(plugin_name) | ||||
| 
 | ||||
|             for m in _tmp_matchers.get(): | ||||
|                 m.module = name | ||||
|             plugin = Plugin(name, module, _tmp_matchers.get(), _export.get()) | ||||
|             plugins[name] = plugin | ||||
|             logger.opt(colors=True).info(f'Succeeded to import "<y>{name}</y>"') | ||||
|                 m.module = plugin_name | ||||
|             plugin = Plugin(plugin_name, module, _tmp_matchers.get(), | ||||
|                             _export.get()) | ||||
|             plugins[plugin_name] = plugin | ||||
|             logger.opt( | ||||
|                 colors=True).info(f'Succeeded to import "<y>{plugin_name}</y>"') | ||||
|             return plugin | ||||
|         except Exception as e: | ||||
|             logger.opt(colors=True, exception=e).error( | ||||
|                 f'<r><bg #f8bbd0>Failed to import "{name}"</bg #f8bbd0></r>') | ||||
|                 f'<r><bg #f8bbd0>Failed to import "{plugin_name}"</bg #f8bbd0></r>' | ||||
|             ) | ||||
|             return None | ||||
| 
 | ||||
|     loaded_plugins = set() | ||||
|     for module_info in pkgutil.iter_modules(plugin_dir): | ||||
|     manager = PluginManager(PLUGIN_NAMESPACE, search_path=plugin_dir) | ||||
|     for plugin_name in manager.list_plugins(): | ||||
|         context: Context = copy_context() | ||||
|         result = context.run(_load_plugin, module_info) | ||||
|         result = context.run(_load_plugin, plugin_name) | ||||
|         if result: | ||||
|             loaded_plugins.add(result) | ||||
|     return loaded_plugins | ||||
							
								
								
									
										177
									
								
								nonebot/plugin/manager.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								nonebot/plugin/manager.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | ||||
| import sys | ||||
| import uuid | ||||
| import pkgutil | ||||
| import importlib | ||||
| from hashlib import md5 | ||||
| from types import ModuleType | ||||
| from collections import Counter | ||||
| from importlib.abc import MetaPathFinder | ||||
| from importlib.machinery import PathFinder | ||||
| from typing import Set, List, Optional, Iterable | ||||
|  | ||||
| _internal_space = ModuleType(__name__ + "._internal") | ||||
| _internal_space.__path__ = []  # type: ignore | ||||
| sys.modules[_internal_space.__name__] = _internal_space | ||||
|  | ||||
| _manager_stack: List["PluginManager"] = [] | ||||
|  | ||||
|  | ||||
| class _NamespaceModule(ModuleType): | ||||
|     """Simple namespace module to store plugins.""" | ||||
|  | ||||
|     @property | ||||
|     def __path__(self): | ||||
|         return [] | ||||
|  | ||||
|     def __getattr__(self, name: str): | ||||
|         try: | ||||
|             return super().__getattr__(name)  # type: ignore | ||||
|         except AttributeError: | ||||
|             if name.startswith("__"): | ||||
|                 raise | ||||
|             raise RuntimeError("Plugin manager not activated!") | ||||
|  | ||||
|  | ||||
| class _InternalModule(ModuleType): | ||||
|     """Internal module for each plugin manager.""" | ||||
|  | ||||
|     def __init__(self, plugin_manager: "PluginManager"): | ||||
|         super().__init__( | ||||
|             f"{_internal_space.__name__}.{plugin_manager.internal_id}") | ||||
|         self.__plugin_manager__ = plugin_manager | ||||
|  | ||||
|     @property | ||||
|     def __path__(self) -> List[str]: | ||||
|         return list(self.__plugin_manager__.search_path) | ||||
|  | ||||
|  | ||||
| class PluginManager: | ||||
|  | ||||
|     def __init__(self, | ||||
|                  namespace: Optional[str] = None, | ||||
|                  plugins: Optional[Iterable[str]] = None, | ||||
|                  search_path: Optional[Iterable[str]] = None, | ||||
|                  *, | ||||
|                  id: Optional[str] = None): | ||||
|         self.namespace: Optional[str] = namespace | ||||
|         self.namespace_module: Optional[ModuleType] = self._setup_namespace( | ||||
|             namespace) | ||||
|  | ||||
|         self.id: str = id or str(uuid.uuid4()) | ||||
|         self.internal_id: str = md5( | ||||
|             ((self.namespace or "") + self.id).encode()).hexdigest() | ||||
|         self.internal_module = self._setup_internal_module(self.internal_id) | ||||
|  | ||||
|         # simple plugin not in search path | ||||
|         self.plugins: Set[str] = set(plugins or []) | ||||
|         self.search_path: Set[str] = set(search_path or []) | ||||
|         # ensure can be loaded | ||||
|         self.list_plugins() | ||||
|  | ||||
|     def _setup_namespace(self, | ||||
|                          namespace: Optional[str] = None | ||||
|                         ) -> Optional[ModuleType]: | ||||
|         if not namespace: | ||||
|             return None | ||||
|  | ||||
|         try: | ||||
|             module = importlib.import_module(namespace) | ||||
|         except ImportError: | ||||
|             module = _NamespaceModule(namespace) | ||||
|             if "." in namespace: | ||||
|                 parent = importlib.import_module(namespace.rsplit(".", 1)[0]) | ||||
|                 setattr(parent, namespace.rsplit(".", 1)[1], module) | ||||
|  | ||||
|         sys.modules[namespace] = module | ||||
|         return module | ||||
|  | ||||
|     def _setup_internal_module(self, internal_id: str) -> ModuleType: | ||||
|         if hasattr(_internal_space, internal_id): | ||||
|             raise RuntimeError("Plugin manager already exists!") | ||||
|         module = _InternalModule(self) | ||||
|         sys.modules[module.__name__] = module | ||||
|         setattr(_internal_space, internal_id, module) | ||||
|         return module | ||||
|  | ||||
|     def __enter__(self): | ||||
|         if self in _manager_stack: | ||||
|             raise RuntimeError("Plugin manager already activated!") | ||||
|         _manager_stack.append(self) | ||||
|         return self | ||||
|  | ||||
|     def __exit__(self, exc_type, exc_value, traceback): | ||||
|         try: | ||||
|             _manager_stack.pop() | ||||
|         except IndexError: | ||||
|             pass | ||||
|  | ||||
|     def search_plugins(self) -> List[str]: | ||||
|         return [ | ||||
|             module_info.name | ||||
|             for module_info in pkgutil.iter_modules(self.search_path) | ||||
|         ] | ||||
|  | ||||
|     def list_plugins(self) -> Set[str]: | ||||
|         _pre_managers: List[PluginManager] | ||||
|         if self in _manager_stack: | ||||
|             _pre_managers = _manager_stack[:_manager_stack.index(self)] | ||||
|         else: | ||||
|             _pre_managers = _manager_stack[:] | ||||
|  | ||||
|         _search_path: Set[str] = set() | ||||
|         for manager in _pre_managers: | ||||
|             _search_path |= manager.search_path | ||||
|         if _search_path & self.search_path: | ||||
|             raise RuntimeError("Duplicate plugin search path!") | ||||
|  | ||||
|         _search_plugins = self.search_plugins() | ||||
|         c = Counter([*_search_plugins, *self.plugins]) | ||||
|         conflict = [name for name, num in c.items() if num > 1] | ||||
|         if conflict: | ||||
|             raise RuntimeError( | ||||
|                 f"More than one plugin named {' / '.join(conflict)}!") | ||||
|         return set(_search_plugins) | self.plugins | ||||
|  | ||||
|     def load_plugin(self, name) -> ModuleType: | ||||
|         if name in self.plugins: | ||||
|             return importlib.import_module(name) | ||||
|  | ||||
|         if "." in name: | ||||
|             raise ValueError("Plugin name cannot contain '.'") | ||||
|         with self: | ||||
|             return importlib.import_module(f"{self.namespace}.{name}") | ||||
|  | ||||
|     def load_all_plugins(self) -> List[ModuleType]: | ||||
|         return [self.load_plugin(name) for name in self.list_plugins()] | ||||
|  | ||||
|     def _rewrite_module_name(self, module_name) -> Optional[str]: | ||||
|         if module_name == self.namespace: | ||||
|             return self.internal_module.__name__ | ||||
|         elif module_name.startswith(self.namespace + "."): | ||||
|             path = module_name.split(".") | ||||
|             length = self.namespace.count(".") + 1 | ||||
|             return f"{self.internal_module.__name__}.{'.'.join(path[length:])}" | ||||
|         elif module_name in self.search_plugins(): | ||||
|             return f"{self.internal_module.__name__}.{module_name}" | ||||
|         return None | ||||
|  | ||||
|  | ||||
| class PluginFinder(MetaPathFinder): | ||||
|  | ||||
|     def find_spec(self, fullname: str, path, target): | ||||
|         if _manager_stack: | ||||
|             index = -1 | ||||
|             while -index <= len(_manager_stack): | ||||
|                 manager = _manager_stack[index] | ||||
|                 newname = manager._rewrite_module_name(fullname) | ||||
|                 if newname: | ||||
|                     spec = PathFinder.find_spec(newname, | ||||
|                                                 list(manager.search_path), | ||||
|                                                 target) | ||||
|                     if spec: | ||||
|                         return spec | ||||
|                 index -= 1 | ||||
|         return None | ||||
|  | ||||
|  | ||||
| sys.meta_path.insert(0, PluginFinder()) | ||||
		Reference in New Issue
	
	Block a user