#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
#
# Copyright 2018 黎慧剑
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""
库导入工具
@module import_tool
@file import_tool.py
"""
import sys
import os
from types import ModuleType
# 根据当前文件路径将包路径纳入, 在非安装的情况下可以引用到
sys.path.append(os.path.abspath(os.path.join(
os.path.dirname(__file__), os.path.pardir, os.path.pardir)))
from HiveNetCore.utils.file_tool import FileTool
__MOUDLE__ = 'import_tool' # 模块名
__DESCRIPT__ = u'库导入工具' # 模块描述
__VERSION__ = '0.1.0' # 版本
__AUTHOR__ = u'黎慧剑' # 作者
__PUBLISH__ = '2018.08.29' # 发布日期
[文档]class DynamicLibManager(object):
"""
动态库加载管理类
"""
[文档] def __init__(self, base_lib_path: str) -> None:
"""
构造函数
@param {str} base_lib_path - 默认加载动态模块所在目录
"""
# 默认支持插件所在目录
self.base_lib_path = os.path.abspath(base_lib_path)
# 已加载的模块字典, key为模块名, value为模块对象
self.modules = dict()
# 已经初始化的产品实例对象缓存, key为'real_module_name.real_class_name.cache_id', value为实例对象
self.instances = dict()
# 模块检索字典, key为检索名, value为模块名
self.modules_mapping = dict()
#############################
# 公共函数
#############################
[文档] def load(self, path: str = None, module_name: str = None, as_name: str = None, class_mapping: dict = None):
"""
装载指定模块模块
@param {str} path=None - 文件路径或加载模块的指定搜索路径
@param {str} module_name=None - 要加载的模块名, 注意模块名必须全局唯一
@param {str} as_name=None - 获取模块对象的检索名
@param {dict} class_mapping=None - 类名映射字典, key为类别名, value为真实的类名
注: 类名映射是设置在as_name下, 因此如果要使用该参数, 请设置as_name(例如设置为和模块名一致)
@returns {tuple} - 返回 (module_name, module_obj)
@example path和module_name组合使用的例子
1、path为完整文件路径, module_name不设置, 则导入模块名为文件名(不含.py)
例如: path='/test/xxx.py', module_name=None
2、path为文件所在目录, module_name设置为文件名(不含.py), 按文件名自动获取文件进行导入
例如: path='/test', module_name='xxx'
3、path为包所在目录, module_name设置为包含后续包路径的模块名, 需注意每个包路径下要有__init__.py文件
例如: path='./test', module_name='aaa.bbb.xxx'
4、path设置为None, module_name设置为要搜索的模块名, 会自动从python搜索路径(sys.path)中查找模块
"""
# 标准化模块名和路径
_path = path
_module_name = module_name
if _path is not None and _module_name is None:
_module_name = FileTool.get_file_name_no_ext(_path)
_path = FileTool.get_file_path(_path)
# 如果已导入, 则无需重新导入
_module_obj = self.modules.get(_module_name, None)
if _module_obj is None:
_module_obj = ImportTool.import_module(
_module_name, extend_path=_path, is_force=True
)
# 添加到搜索字典中
self.modules[_module_name] = _module_obj
# 设置检索字典
if as_name is not None:
self.modules_mapping[as_name] = {
'module_name': _module_name, 'class_mapping': {} if class_mapping is None else class_mapping
}
return _module_name, _module_obj
[文档] def set_as_name(self, module_name: str, as_name: str, class_mapping: dict = None, as_name_first: bool = True):
"""
为模块获取设置检索名
@param {str} module_name - 模块名
@param {str} as_name - 模块对象的检索名
@param {dict} class_mapping=None - 类名映射字典, key为类别名, value为真实的类名
@param {bool} as_name_first=True - 优先按检索名查找模块
@throws {ModuleNotFoundError} - 当模块名找不到模块时抛出异常
"""
_module_name, _class_mapping = self._get_module_search_info(
module_name, as_name_first=as_name_first
)
# 没有找到模块, 抛出异常
if _module_name is None:
raise ModuleNotFoundError('module [%s] not found' % module_name)
# 添加配置或覆盖掉原配置
self.modules_mapping[as_name] = {
'module_name': _module_name, 'class_mapping': {} if class_mapping is None else class_mapping
}
[文档] def remove_as_name(self, as_name: str):
"""
删除指定模块检索别名
@param {str} as_name - 要删除的别名
"""
self.modules_mapping.pop(as_name, None)
[文档] def get_module(self, module_name: str, as_name_first: bool = True) -> ModuleType:
"""
获取指定模块对象
@param {str} module_name - 模块名或检索名
@param {bool} as_name_first=True - 优先按检索名查找模块
@returns {ModuleType} - 返回模块对象
@throws {ModuleNotFoundError} - 当模块名找不到模块时抛出异常
"""
_module_name, _class_mapping = self._get_module_search_info(
module_name, as_name_first=as_name_first
)
# 没有找到模块, 抛出异常
if _module_name is None:
raise ModuleNotFoundError('module [%s] not found' % module_name)
return self.modules[_module_name]
[文档] def get_class(self, module_name: str, class_name: str, as_name_first: bool = True):
"""
获取指定模块的类对象(或成员函数)
@param {str} module_name - 模块名或检索名
@param {str} class_name - 类名
@param {bool} as_name_first=True - 优先按检索名查找模块
@returns {Any} - 查找到的类对象
@throws {ModuleNotFoundError} - 当模块名找不到模块时抛出异常
"""
_module_name, _class_mapping = self._get_module_search_info(
module_name, as_name_first=as_name_first
)
# 没有找到模块, 抛出异常
if _module_name is None:
raise ModuleNotFoundError('module [%s] not found' % module_name)
# 获取真实类名
_mapping_class_name = _class_mapping.get(class_name, None)
_class_name = class_name if _mapping_class_name is None else _mapping_class_name
return ImportTool.get_member_from_module(
self.modules[_module_name], _class_name
)
[文档] def init_class(self, module_name: str, class_name: str, init_args: list = None,
init_kwargs: dict = None, stand_alone: bool = False,
cache_id: str = None, as_name_first: bool = True):
"""
初始化类实例
@param {str} module_name - 模块名或检索名
@param {str} class_name - 类名
@param {list} init_args=None - 类实例的初始化固定参数, 以*args方式传入
@param {dict} init_kwargs=None - 类实例的初始化kv参数, 以*kwargs方式传入
@param {bool} stand_alone=False - 是否生成新的独立实例(不缓存)
@param {str} cache_id=None - 缓存的唯一检索id
@param {bool} as_name_first=True - 优先按检索名查找模块
@returns {object} - 返回初始化后的实例对象
@throws {ModuleNotFoundError} - 当模块名找不到模块时抛出异常
"""
# 获取真实模块和类名
_real_module_name, _class_mapping = self._get_module_search_info(
module_name, as_name_first=as_name_first
)
_real_class_name = _class_mapping.get(class_name, class_name)
if stand_alone:
# 创新新的独立实例
_class_obj = self.get_class(_real_module_name, _real_class_name, as_name_first=False)
return self._init_class(_class_obj, init_args=init_args, init_kwargs=init_kwargs)
else:
# 共享实例
_cache_id = '' if cache_id is None else cache_id
_instance_key = '%s.%s.%s' % (_real_module_name, _real_class_name, _cache_id)
_instance = self.instances.get(_instance_key, None)
if _instance is None:
_class_obj = self.get_class(_real_module_name, _real_class_name, as_name_first=False)
_instance = self._init_class(_class_obj, init_args=init_args, init_kwargs=init_kwargs)
self.instances[_instance_key] = _instance
# 返回实例
return _instance
[文档] def get_instance(self, module_name: str, class_name: str, cache_id: str = None, as_name_first: bool = True):
"""
获取缓存的类实例对象
@param {str} module_name - 模块名或检索名
@param {str} class_name - 类名
@param {str} cache_id=None - 缓存的唯一检索id
@param {bool} as_name_first=True - 优先按检索名查找模块
@returns {object} - 返回实例对象
@throws {ModuleNotFoundError} - 当模块名找不到模块时抛出异常
"""
_real_module_name, _class_mapping = self._get_module_search_info(
module_name, as_name_first=as_name_first
)
_real_class_name = _class_mapping.get(class_name, class_name)
_cache_id = '' if cache_id is None else cache_id
_instance_key = '%s.%s.%s' % (_real_module_name, _real_class_name, _cache_id)
_instance = self.instances.get(_instance_key, None)
if _instance is None:
raise ModuleNotFoundError('class [%s] instance is not found' % _instance_key)
return _instance
[文档] def load_by_config(self, lib_config: dict, self_lib_path: str = None, force_self_lib: bool = False):
"""
装载动态库
@param {dict} lib_config - 动态库加载配置
is_self_lib {bool} - 是否私有库, 默认为False(直接管理类初始化的默认路径查找库文件)
path {str} - 文件路径或加载模块的指定搜索路径, 该参数可以设置为None或不设置
module_name {str} - 指定要加载的模块名, 如果path包含完整文件名可以不设置
class {str} - 指定要获取的类名
function {str} - 指定要获取的函数名
instantiation {bool} - 是否要初始化类, 默认为False
stand_alone {bool} - 是否生成新的独立实例(不缓存), 默认为False
cache_id {str} - 缓存的唯一检索id, 可以设置为None
注: 可以通过cache_id的不同控制一个类可以有多个实例的情况
init_args {list} - 类实例的初始化固定参数, 以*args方式传入
init_kwargs {dict} - 类实例的初始化kv参数, 以*kwargs方式传入
@param {str} self_lib_path=None - 查找私有库的基础路径
@param {bool} force_self_lib=False - 是否强制指定为私有哭(不再判断lib_config中的is_self_lib参数)
@returns {str|object} - 根据不同情况返回不同的结果
class和function均不设置: 返回模块名
设置了class, 未设置function: 返回class类或class实例对象(instantiation为True的情况)
设置了function: 返回指定的函数对象(如果class未指定, 返回的是模块中定义的函数)
"""
# 根据是否自有插件设置不同的搜索目录
if force_self_lib:
_is_self_lib = True
else:
_is_self_lib = lib_config.get('is_self_lib', False)
if _is_self_lib is None:
_is_self_lib = False
_base_lib_path = self_lib_path if _is_self_lib and self_lib_path is not None else self.base_lib_path
_file_path = None if lib_config.get('path', None) is None else os.path.abspath(
os.path.join(_base_lib_path, lib_config['path'])
)
_module_name, _module_obj = self.load(
path=_file_path, module_name=lib_config.get('module_name', None)
)
_class = lib_config.get('class', None)
_function = lib_config.get('function', None)
if _class is None:
if _function is None:
return _module_name
else:
# 返回模块定义的函数实例
return ImportTool.get_member_from_module(_module_obj, _function)
# 确定产品是否需实例化
if lib_config.get('instantiation', False):
# 需要初始化类
_class_obj = self.init_class(
_module_name, _class, init_args=lib_config.get('init_args', None),
init_kwargs=lib_config.get('init_kwargs', None),
stand_alone=lib_config.get('stand_alone', False),
cache_id=lib_config.get('cache_id', None),
as_name_first=True
)
else:
_class_obj = self.get_class(_module_name, _class)
# 最后的返回处理, 判断返回的是类还是函数
if _function is None:
return _class_obj
else:
return getattr(_class_obj, _function)
#############################
# 内部函数
#############################
def _get_module_search_info(self, module_name: str, as_name_first: bool = True) -> tuple:
"""
获取模块的检索信息
@param {str} module_name - 模块名或检索名
@param {bool} as_name_first=True - 优先按检索名查找模块
@returns {tuple} - 返回真实模块名和类映射字典(real_module_name, class_mapping)
"""
_module_name = module_name
_class_mapping = {}
if as_name_first:
# 优先通过检索名获取
_mapping_index = self.modules_mapping.get(module_name, None)
if _mapping_index is not None:
_module_name = _mapping_index['module_name']
_class_mapping = _mapping_index['class_mapping']
else:
if self.modules.get(module_name, None) is None:
_module_name = None
else:
if self.modules.get(module_name, None) is None:
_module_name = None
return _module_name, _class_mapping
def _init_class(self, class_obj, init_args: list = None, init_kwargs: dict = None):
"""
实例化类
@param {object} class_obj - 类对象
@param {list} init_args=None - 类实例的初始化固定参数, 以*args方式传入
@param {dict} init_kwargs=None - 类实例的初始化kv参数, 以*kwargs方式传入
@returns {object} - 返回初始化的实例对象
"""
if init_args is None and init_kwargs is None:
return class_obj()
elif init_args is None:
return class_obj(**init_kwargs)
elif init_kwargs is None:
return class_obj(*init_args)
else:
return class_obj(*init_args, **init_kwargs)
if __name__ == '__main__':
# 当程序自己独立运行时执行的操作
# 打印版本信息
print(('模块名: %s - %s\n'
'作者: %s\n'
'发布日期: %s\n'
'版本: %s' % (__MOUDLE__, __DESCRIPT__, __AUTHOR__, __PUBLISH__, __VERSION__)))