def is_kwarg_annotation_correct( parameter_name: str, parameter_type: Any, supposed_type: Any, common_prefix: str = "", ): wrong_type_flag = True if get_origin(supposed_type) is Union: for type_ in get_args(supposed_type): if type_ == parameter_type: wrong_type_flag = False else: if type(supposed_type) == str: # 避免循环导入,有时会使用字符串形式的类型 # <class 'pepperbot.core.message.chain.MessageChain'> pattern = re.search(r"<class '(.*)'>", str(parameter_type)) if not pattern: raise InitializationError(common_prefix + f"无法确认参数{parameter_name}的类型") str_parameter_type = pattern.groups()[0].split(".")[-1] if supposed_type == str_parameter_type: wrong_type_flag = False else: if supposed_type == parameter_type: wrong_type_flag = False if wrong_type_flag: raise InitializationError( common_prefix + f"的参数{parameter_name}的类型应该为{supposed_type},而不是{parameter_type}" )
def is_handler_method_args_valid( class_handler: Any, method: Callable, method_name: str ): if "onebot" in method_name: mapping = ONEBOTV11_KWARGS_MAPPING event_name = method_name.replace("onebot_", "") elif "keaimao" in method_name: mapping = KEAIMAO_KWARGS_MAPPING event_name = method_name.replace("keaimao_", "") else: mapping = UNIVERSAL_KWARGS_MAPPING event_name = method_name kwarg_name_type_mapping, usable_kwargs_hint = gen_usable_kwargs_hint( mapping, event_name ) usable_kwarg_names = kwarg_name_type_mapping.keys() source_file_name = inspect.getsourcefile(class_handler) class_handler_name = class_handler.__name__ common_prefix = ( f"\n{source_file_name}文件中的类响应器{class_handler_name}的" + f"{method_name}事件" ) all_args, var_args, var_kwargs = inspect.getargs(method.__code__) if var_args or var_kwargs: raise InitializationError( common_prefix + "不需要提供*或者**参数,PepperBot会自动根据声明的参数以及类型注入" + usable_kwargs_hint ) for arg_name in all_args[1:]: # 第一个是self if arg_name not in usable_kwarg_names: raise InitializationError( common_prefix + f"上不存在参数{arg_name}" + usable_kwargs_hint ) if arg_name not in method.__annotations__.keys(): raise InitializationError( common_prefix + f"的参数{arg_name}未提供类型注解,其类型为{kwarg_name_type_mapping[arg_name]}" + usable_kwargs_hint ) # 经过上两步验证,此时的参数,一定是有效的,而且有类型注解 for arg_name, arg_type in method.__annotations__.items(): supposed_arg_type = kwarg_name_type_mapping[arg_name] is_kwarg_annotation_correct( arg_name, arg_type, supposed_arg_type, common_prefix )
def register_adapter( self, bot_protocol: T_BotProtocol, receive_protocol: T_WebProtocol, backend_protocol: T_WebProtocol, backend_port: int, backend_host="127.0.0.1", receive_uri: str = "", ): uri: str request_handler: Optional[Callable] = None if bot_protocol not in ALL_AVAILABLE_BOT_PROTOCOLS: raise InitializationError(f"尚不支持的机器人协议 {bot_protocol}") if receive_uri: uri = receive_uri else: if receive_protocol == "http": request_handler = lambda request: http_receiver(request, bot_protocol) uri = DEFAULT_URI[bot_protocol] + "/http" elif receive_protocol == "websocket": request_handler = lambda request, ws: websocket_receiver( request, ws, bot_protocol ) uri = DEFAULT_URI[bot_protocol] + "/ws" else: raise InitializationError(f"未知通信协议 {receive_protocol}") create_web_routes( sanic_app, receive_protocol=receive_protocol, uri=uri, request_handler=request_handler, ) api_caller: ApiCaller if backend_protocol == "http": api_caller = ApiCaller( bot_protocol=bot_protocol, backend_protocol="http", backend_port=backend_port, backend_host=backend_host, ) elif backend_protocol == "websocket": raise InitializationError(f"backend_protocol尚不支持ws") else: raise InitializationError(f"未知通信协议 {backend_protocol}") api_callers[bot_protocol] = api_caller
def is_command_method_return_valid( method: Callable, method_name: str, method_names: List[str], common_prefix: str ): """ 通过ast,保证方法返回值要么为None,要么是同一class的其它方法名 同时不能是catch, timeout, exit,不应该由指令作者主动触发;initial和finish可以 即使有了静态检测,运行时检测方法返回值也是需要的 """ for info in get_return_identifiers(method): ids = info["ids"] wrong = False if not ids: # 不返回 continue if len(ids) > 1: # 不能返回多个值 wrong = True identifier = ids[0] if identifier not in method_names: # 要么返回下一步的方法名,要么不返回 wrong = True if identifier in LIFECYCLE_NO_PROGRAMMIC: wrong = True if wrong: raise InitializationError( common_prefix + f"方法 {method_name} 的返回值可以为除catch, timeout, exit以外的生命周期," "即initial和finish,或者直接返回None(不返回)以结束会话" )
def is_handler_method_name_valid(method_name: str): if not ( method_name in ALL_META_EVENTS or method_name in ALL_COMMON_EVENTS or method_name in ALL_GROUP_EVENTS or method_name in ALL_PRIVATE_EVENTS ): raise InitializationError(f"无效的事件名 {method_name}")
def cache_class_command(class_command: Callable, command_name: str): # 多个group handler,相同command的处理(解析所有指令和groupId,重新生成缓存) # 同一个commandClass,就实例化一次 # if lifecycle_name not in get_own_methods(): class_command_instance = class_command() command_method_mapping = {} for method in get_own_methods(class_command_instance): method_name = method.__name__ patterns: List[Tuple[str, T_PatternArg]] = [] compressed_patterns = [] if method_name not in LIFECYCLE_WITHOUT_PATTERNS: signature = inspect.signature(method) for arg_name, p in signature.parameters.items(): if p.default == "PatternArg": annotation = p.annotation if annotation not in runtime_pattern_arg_types: raise InitializationError(f"仅支持str, bool, int, float和所有消息类型") # 未具体指定类型(int, float, bool)的Text按照str处理 patterns.append( (arg_name, annotation if annotation != Text else str) ) # debug(patterns) compressed_patterns = merge_text_of_patterns(patterns) # debug(compressed_patterns) command_method_mapping[method_name] = CommandMethodCache( method=method, patterns=patterns, compressed_patterns=compressed_patterns, ) class_command_mapping[command_name] = ClassCommandCache( class_instance=class_command_instance, command_method_mapping=command_method_mapping, ) command_config: CommandConfig = getattr(class_command, COMMAND_CONFIG) class_command_config_mapping[command_name]["default"] = command_config
def decorator(handler: Callable): if not isclass(handler): raise InitializationError("register装饰器只能注册class,不能注册function") # 收集register中的BotRoute,apply_routes中应用 register_routes.append( BotRoute( handler=handler, commands=commands, groups=groups, friends=friends, )) return handler
def is_valid_class_command(class_command: Any): source_file_name = inspect.getsourcefile(class_command) class_command_name = class_command.__name__ common_prefix = f"\n{source_file_name} 文件中的指令 {class_command_name} 的" methods = list(get_own_methods(class_command)) method_names = [method.__name__ for method in methods] if not "initial" in method_names: raise InitializationError("指令必须定义initial方法作为入口") for method in methods: method_name = method.__name__ is_command_method_args_valid(method, method_name, common_prefix) is_command_method_return_valid(method, method_name, method_names, common_prefix) return True
def is_command_method_args_valid( method: Callable, method_name: str, common_prefix: str ): common_prefix += f"{method_name}方法" signature = inspect.signature(method) for arg_name, p in signature.parameters.items(): # self if arg_name == "self": continue # no *, ** if p.kind == p.VAR_POSITIONAL or p.kind == p.VAR_KEYWORD: raise InitializationError( common_prefix + "不需要提供*或者**参数,PepperBot会自动根据声明的参数以及类型注入" ) ## has type hint kwarg_name_type_mapping, usable_kwargs_hint = gen_usable_kwargs_hint( COMMAND_DEFAULT_KWARGS, method_name ) if p.annotation == p.empty: raise InitializationError( common_prefix + f"指令的方法 {method_name} 的参数 {arg_name} 未提供类型注解" + usable_kwargs_hint if arg_name in kwarg_name_type_mapping.keys() else "" ) else: # kwargs == no default value if p.default != p.empty and p.default != "PatternArg": raise InitializationError( common_prefix + f"不应为 {arg_name} 除pattern外的参数设置默认值" ) # type hint correct if p.default != "PatternArg": supposed_type = kwarg_name_type_mapping.get(arg_name) if not supposed_type: raise InitializationError( common_prefix + f"{arg_name} 无对应的类型,请确认该参数是否为有效参数" ) is_kwarg_annotation_correct( arg_name, p.annotation, supposed_type, common_prefix ) # pattern args if p.default == "PatternArg": # no pattern in lifecycle hooks if method_name in LIFECYCLE_WITHOUT_PATTERNS: raise InitializationError(common_prefix + f"这些生命周期不应支持pattern") if p.annotation not in runtime_pattern_arg_types: raise InitializationError( common_prefix + f"仅支持str, bool, int, float和所有消息类型" )
def parse_routes(routes: Iterable[BotRoute]): for route in routes: commands = [] if route.commands: commands = route.commands command_names: Set[str] = set() for command in commands: command_name = command.__name__ if not is_valid_class_command(command): raise InitializationError( f"路由 {route} 中的指令 {command_name} 不符合要求") if command_name not in class_command_mapping.keys(): cache_class_command(command, command_name) command_names.add(command_name) handler_names: Set[str] = set() if route.handlers: for handler in route.handlers: handler_name = handler.__name__ if not is_valid_class_handler(handler): raise InitializationError( f"路由 {route} 中的消息响应器 {handler_name} 不符合要求") if handler_name not in class_handler_mapping.keys(): cache_class_handler(handler, handler_name) handler_names.add(handler_name) # ------------------------------------------------------------------------------- # pydantic会校验不合法的情况,所以不用else if route.groups == "*": for protocol in ALL_AVAILABLE_BOT_PROTOCOLS: route_mapping.global_commands[protocol][ "group"] |= command_names route_mapping.global_handlers[protocol][ "group"] |= handler_names elif route.groups and type(route.groups) == dict: for protocol, group_ids in route.groups.items(): if group_ids == "*": route_mapping.global_handlers[protocol][ "group"] |= handler_names else: for group_id in group_ids: group_id = str(group_id) route_mapping.mapping[protocol]["group"][group_id][ "commands"] |= command_names route_mapping.mapping[protocol]["group"][group_id][ "class_handlers"] |= handler_names elif callable(route.groups): validator = route.groups if not is_valid_route_validator(validator): raise InitializationError(f"路由{route}中的groups的validator不符合要求") validator_name = validator.__name__ if validator_name not in route_validator_mapping.keys(): cache_route_validator(validator, validator_name) route_mapping.mapping["validators"]["group"][validator_name][ "commands"] |= command_names route_mapping.mapping["validators"]["group"][validator_name][ "class_handlers"] |= handler_names # ------------------------------------------------------------------------------- if route.friends == "*": for protocol in ALL_AVAILABLE_BOT_PROTOCOLS: route_mapping.global_commands[protocol][ "private"] |= command_names route_mapping.global_handlers[protocol][ "private"] |= handler_names elif route.friends and type(route.friends) == dict: for protocol, private_ids in route.friends.items(): if private_ids == "*": route_mapping.global_handlers[protocol][ "private"] |= handler_names else: for private_id in private_ids: private_id = str(private_id) route_mapping.mapping[protocol]["private"][private_id][ "commands"] |= command_names route_mapping.mapping[protocol]["private"][private_id][ "class_handlers"] |= handler_names elif callable(route.friends): validator = route.friends if not is_valid_route_validator(validator): raise InitializationError(f"路由{route}中的friends的validator不符合要求") validator_name = validator.__name__ if validator_name not in route_validator_mapping.keys(): cache_route_validator(validator, validator_name) route_mapping.mapping["validators"]["private"][validator_name][ "commands"] |= command_names route_mapping.mapping["validators"]["private"][validator_name][ "class_handlers"] |= handler_names