diff --git a/nonebot/__init__.py b/nonebot/__init__.py index 85d74aa7..a0d3bbfb 100644 --- a/nonebot/__init__.py +++ b/nonebot/__init__.py @@ -40,10 +40,12 @@ FrontMatter: import importlib from typing import Any, Dict, Type, Optional +import loguru + +from nonebot.log import logger from nonebot.adapters import Bot from nonebot.utils import escape_tag from nonebot.config import Env, Config -from nonebot.log import logger, default_filter from nonebot.drivers import Driver, ReverseDriver, combine_driver try: @@ -206,6 +208,15 @@ def _resolve_combine_expr(obj_str: str) -> Type[Driver]: return combine_driver(DriverClass, *mixins) +def _log_patcher(record: "loguru.Record"): + record["name"] = ( + plugin.name + if (module_name := record["name"]) + and (plugin := get_plugin_by_module_name(module_name)) + else (module_name and module_name.split(".")[0]) + ) + + def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None: """初始化 NoneBot 以及 全局 {ref}`nonebot.drivers.Driver` 对象。 @@ -232,7 +243,9 @@ def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None: _env_file=_env_file or f".env.{env.environment}", ) - default_filter.level = config.log_level + logger.configure( + extra={"nonebot_log_level": config.log_level}, patcher=_log_patcher + ) logger.opt(colors=True).info( f"Current Env: {escape_tag(env.environment)}" ) diff --git a/nonebot/config.py b/nonebot/config.py index c998fd7a..f0bb374f 100644 --- a/nonebot/config.py +++ b/nonebot/config.py @@ -25,7 +25,6 @@ from pydantic.env_settings import ( ) from nonebot.log import logger -from nonebot.utils import escape_tag class CustomEnvSettings(EnvSettingsSource): @@ -85,13 +84,15 @@ class CustomEnvSettings(EnvSettingsSource): if env_file_vars: for env_name in env_file_vars.keys(): env_val = env_vars[env_name] - try: - if env_val: - env_val = settings.__config__.json_loads(env_val.strip()) - except ValueError as e: - logger.opt(colors=True, exception=e).trace( - f"Error while parsing JSON for {escape_tag(env_name)}. Assumed as string." - ) + if env_val and (val_striped := env_val.strip()): + try: + env_val = settings.__config__.json_loads(val_striped) + except ValueError as e: + logger.trace( + "Error while parsing JSON for " + f"{env_name!r}={val_striped!r}. " + "Assumed as string." + ) d[env_name] = env_val diff --git a/nonebot/dependencies/__init__.py b/nonebot/dependencies/__init__.py index 656566fb..417af13a 100644 --- a/nonebot/dependencies/__init__.py +++ b/nonebot/dependencies/__init__.py @@ -30,6 +30,7 @@ from pydantic.fields import Required, FieldInfo, Undefined, ModelField from nonebot.log import logger from nonebot.typing import _DependentCallable +from nonebot.exception import SkippedException from nonebot.utils import run_sync, is_coroutine_callable from .utils import check_field_type, get_typed_signature @@ -85,7 +86,15 @@ class Dependent(Generic[R]): parameterless: Tuple[Param] = field(default_factory=tuple) def __repr__(self) -> str: - return f"" + if inspect.isfunction(self.call) or inspect.isclass(self.call): + call_str = self.call.__name__ + else: + call_str = repr(self.call) + return ( + f"Dependent(call={call_str}" + + (f", parameterless={self.parameterless}" if self.parameterless else "") + + ")" + ) async def __call__(self, **kwargs: Any) -> R: # do pre-check @@ -178,19 +187,22 @@ class Dependent(Generic[R]): else cls.parse_parameterless(tuple(parameterless), allow_types) ) - logger.trace( - f"Parsed dependent with call={call}, " - f"params={params}, " - f"parameterless={parameterless_params}" - ) - return cls(call, params, parameterless_params) async def check(self, **params: Any) -> None: - await asyncio.gather(*(param._check(**params) for param in self.parameterless)) - await asyncio.gather( - *(cast(Param, param.field_info)._check(**params) for param in self.params) - ) + try: + await asyncio.gather( + *(param._check(**params) for param in self.parameterless) + ) + await asyncio.gather( + *( + cast(Param, param.field_info)._check(**params) + for param in self.params + ) + ) + except SkippedException as e: + logger.trace(f"{self} skipped due to {e}") + raise async def _solve_field(self, field: ModelField, params: Dict[str, Any]) -> Any: value = await cast(Param, field.field_info)._solve(**params) diff --git a/nonebot/drivers/aiohttp.py b/nonebot/drivers/aiohttp.py index 6d686e05..b17901fa 100644 --- a/nonebot/drivers/aiohttp.py +++ b/nonebot/drivers/aiohttp.py @@ -27,7 +27,7 @@ from nonebot.drivers import HTTPVersion, ForwardMixin, ForwardDriver, combine_dr try: import aiohttp -except ImportError: +except ImportError: # pragma: no cover raise ImportError( "Please install aiohttp first to use this driver. `pip install nonebot2[aiohttp]`" ) from None diff --git a/nonebot/drivers/httpx.py b/nonebot/drivers/httpx.py index fa167b6b..755ea0ec 100644 --- a/nonebot/drivers/httpx.py +++ b/nonebot/drivers/httpx.py @@ -31,7 +31,7 @@ from nonebot.drivers import ( try: import httpx -except ImportError: +except ImportError: # pragma: no cover raise ImportError( "Please install httpx by using `pip install nonebot2[httpx]`" ) from None diff --git a/nonebot/drivers/quart.py b/nonebot/drivers/quart.py index 52881fdd..bb02522b 100644 --- a/nonebot/drivers/quart.py +++ b/nonebot/drivers/quart.py @@ -37,7 +37,7 @@ try: from quart import Quart, Request, Response from quart.datastructures import FileStorage from quart import Websocket as QuartWebSocket -except ImportError: +except ImportError: # pragma: no cover raise ImportError( "Please install Quart by using `pip install nonebot2[quart]`" ) from None diff --git a/nonebot/drivers/websockets.py b/nonebot/drivers/websockets.py index 9b78c829..1b4302f0 100644 --- a/nonebot/drivers/websockets.py +++ b/nonebot/drivers/websockets.py @@ -30,10 +30,10 @@ from nonebot.drivers import ForwardMixin, ForwardDriver, combine_driver try: from websockets.exceptions import ConnectionClosed from websockets.legacy.client import Connect, WebSocketClientProtocol -except ImportError: +except ImportError: # pragma: no cover raise ImportError( "Please install websockets by using `pip install nonebot2[websockets]`" - ) + ) from None logger = logging.Logger("websockets.client", "INFO") logger.addHandler(LoguruHandler()) diff --git a/nonebot/exception.py b/nonebot/exception.py index 1160fc5d..348a6fbe 100644 --- a/nonebot/exception.py +++ b/nonebot/exception.py @@ -46,10 +46,14 @@ class ParserExit(NoneBotException): self.status = status self.message = message - def __repr__(self): - return f"" + def __repr__(self) -> str: + return ( + f"ParserExit(status={self.status}" + + (f", message={self.message!r}" if self.message else "") + + ")" + ) - def __str__(self): + def __str__(self) -> str: return self.__repr__() @@ -68,10 +72,10 @@ class IgnoredException(ProcessException): def __init__(self, reason: Any): self.reason: Any = reason - def __repr__(self): - return f"" + def __repr__(self) -> str: + return f"IgnoredException(reason={self.reason!r})" - def __str__(self): + def __str__(self) -> str: return self.__repr__() @@ -99,11 +103,14 @@ class TypeMisMatch(SkippedException): self.param: ModelField = param self.value: Any = value - def __repr__(self): - return f"" + def __repr__(self) -> str: + return ( + f"TypeMisMatch(param={self.param.name}, " + f"type={self.param._type_display()}, value={self.value!r}>" + ) - def __str__(self): - self.__repr__() + def __str__(self) -> str: + return self.__repr__() class MockApiException(ProcessException): @@ -116,10 +123,10 @@ class MockApiException(ProcessException): def __init__(self, result: Any): self.result = result - def __repr__(self): - return f"" + def __repr__(self) -> str: + return f"MockApiException(result={self.result!r})" - def __str__(self): + def __str__(self) -> str: return self.__repr__() @@ -195,7 +202,8 @@ class AdapterException(NoneBotException): adapter_name: 标识 adapter """ - def __init__(self, adapter_name: str) -> None: + def __init__(self, adapter_name: str, *args: object) -> None: + super().__init__(*args) self.adapter_name: str = adapter_name @@ -231,4 +239,11 @@ class WebSocketClosed(DriverException): self.reason = reason def __repr__(self) -> str: - return f"" + return ( + f"WebSocketClosed(code={self.code}" + + (f", reason={self.reason!r}" if self.reason else "") + + ")" + ) + + def __str__(self) -> str: + return self.__repr__() diff --git a/nonebot/internal/adapter/adapter.py b/nonebot/internal/adapter/adapter.py index 9f6c5e65..298ab143 100644 --- a/nonebot/internal/adapter/adapter.py +++ b/nonebot/internal/adapter/adapter.py @@ -33,6 +33,9 @@ class Adapter(abc.ABC): self.bots: Dict[str, Bot] = {} """本协议适配器已建立连接的 {ref}`nonebot.adapters.Bot` 实例""" + def __repr__(self) -> str: + return f"Adapter(name={self.get_name()!r})" + @classmethod @abc.abstractmethod def get_name(cls) -> str: diff --git a/nonebot/internal/adapter/bot.py b/nonebot/internal/adapter/bot.py index 5bedecb9..cf1df182 100644 --- a/nonebot/internal/adapter/bot.py +++ b/nonebot/internal/adapter/bot.py @@ -13,10 +13,9 @@ if TYPE_CHECKING: from .adapter import Adapter from .message import Message, MessageSegment - -class _ApiCall(Protocol): - async def __call__(self, **kwargs: Any) -> Any: - ... + class _ApiCall(Protocol): + async def __call__(self, **kwargs: Any) -> Any: + ... class Bot(abc.ABC): @@ -40,7 +39,10 @@ class Bot(abc.ABC): self.self_id: str = self_id """机器人 ID""" - def __getattr__(self, name: str) -> _ApiCall: + def __repr__(self) -> str: + return f"Bot(type={self.type!r}, self_id={self.self_id!r})" + + def __getattr__(self, name: str) -> "_ApiCall": return partial(self.call_api, name) @property diff --git a/nonebot/internal/adapter/template.py b/nonebot/internal/adapter/template.py index 39855903..b67c71b8 100644 --- a/nonebot/internal/adapter/template.py +++ b/nonebot/internal/adapter/template.py @@ -56,6 +56,9 @@ class MessageTemplate(Formatter, Generic[TF]): self.factory: Type[TF] = factory self.format_specs: Dict[str, FormatSpecFunc] = {} + def __repr__(self) -> str: + return f"MessageTemplate({self.template!r}, factory={self.factory!r})" + def add_format_spec( self, spec: FormatSpecFunc_T, name: Optional[str] = None ) -> FormatSpecFunc_T: diff --git a/nonebot/internal/driver/driver.py b/nonebot/internal/driver/driver.py index d0846aed..0040dd10 100644 --- a/nonebot/internal/driver/driver.py +++ b/nonebot/internal/driver/driver.py @@ -40,12 +40,18 @@ class Driver(abc.ABC): """环境名称""" self.config: Config = config """全局配置对象""" - self._clients: Dict[str, "Bot"] = {} + self._bots: Dict[str, "Bot"] = {} + + def __repr__(self) -> str: + return ( + f"Driver(type={self.type!r}, " + f"adapters={len(self._adapters)}, bots={len(self._bots)})" + ) @property def bots(self) -> Dict[str, "Bot"]: """获取当前所有已连接的 Bot""" - return self._clients + return self._bots def register_adapter(self, adapter: Type["Adapter"], **kwargs) -> None: """注册一个协议适配器 @@ -124,9 +130,9 @@ class Driver(abc.ABC): def _bot_connect(self, bot: "Bot") -> None: """在连接成功后,调用该函数来注册 bot 对象""" - if bot.self_id in self._clients: + if bot.self_id in self._bots: raise RuntimeError(f"Duplicate bot connection with id {bot.self_id}") - self._clients[bot.self_id] = bot + self._bots[bot.self_id] = bot async def _run_hook(bot: "Bot") -> None: coros = list( @@ -148,8 +154,8 @@ class Driver(abc.ABC): def _bot_disconnect(self, bot: "Bot") -> None: """在连接断开后,调用该函数来注销 bot 对象""" - if bot.self_id in self._clients: - del self._clients[bot.self_id] + if bot.self_id in self._bots: + del self._bots[bot.self_id] async def _run_hook(bot: "Bot") -> None: coros = list( diff --git a/nonebot/internal/driver/model.py b/nonebot/internal/driver/model.py index de13b81a..f4bb9ae1 100644 --- a/nonebot/internal/driver/model.py +++ b/nonebot/internal/driver/model.py @@ -131,9 +131,7 @@ class Request: self.files.append((name, file_info)) # type: ignore def __repr__(self) -> str: - class_name = self.__class__.__name__ - url = str(self.url) - return f"<{class_name}({self.method!r}, {url!r})>" + return f"{self.__class__.__name__}(method={self.method!r}, url='{self.url!s}')" class Response: @@ -161,12 +159,18 @@ class Response: # request self.request: Optional[Request] = request + def __repr__(self) -> str: + return f"{self.__class__.__name__}(status_code={self.status_code!r})" + class WebSocket(abc.ABC): def __init__(self, *, request: Request): # request self.request: Request = request + def __repr__(self) -> str: + return f"{self.__class__.__name__}('{self.request.url!s}')" + @property @abc.abstractmethod def closed(self) -> bool: @@ -320,17 +324,14 @@ class Cookies(MutableMapping): return len(self.jar) def __iter__(self) -> Iterator[Cookie]: - return (cookie for cookie in self.jar) + return iter(self.jar) def __repr__(self) -> str: cookies_repr = ", ".join( - [ - f"" - for cookie in self.jar - ] + f"Cookie({cookie.name}={cookie.value} for {cookie.domain})" + for cookie in self.jar ) - - return f"" + return f"{self.__class__.__name__}({cookies_repr})" @dataclass diff --git a/nonebot/internal/matcher.py b/nonebot/internal/matcher.py index 525d22e8..11eda889 100644 --- a/nonebot/internal/matcher.py +++ b/nonebot/internal/matcher.py @@ -36,7 +36,6 @@ from nonebot.typing import ( T_PermissionUpdater, ) from nonebot.exception import ( - TypeMisMatch, PausedException, StopPropagation, SkippedException, @@ -73,29 +72,16 @@ current_handler: ContextVar[Dependent] = ContextVar("current_handler") class MatcherMeta(type): if TYPE_CHECKING: - module: Optional[str] - plugin_name: Optional[str] module_name: Optional[str] - module_prefix: Optional[str] type: str - rule: Rule - permission: Permission - handlers: List[T_Handler] - priority: int - block: bool - temp: bool - expire_time: Optional[datetime] def __repr__(self) -> str: return ( - f"" + f"Matcher(type={self.type!r}" + + (f", module={self.module_name}" if self.module_name else "") + + ")" ) - def __str__(self) -> str: - return repr(self) - class Matcher(metaclass=MatcherMeta): """事件响应器类""" @@ -150,8 +136,9 @@ class Matcher(metaclass=MatcherMeta): def __repr__(self) -> str: return ( - f"" + f"Matcher(type={self.type!r}" + + (f", module={self.module_name}" if self.module_name else "") + + ")" ) @classmethod @@ -683,8 +670,8 @@ class Matcher(metaclass=MatcherMeta): dependency_cache: Optional[T_DependencyCache] = None, ): logger.trace( - f"Matcher {self} run with incoming args: " - f"bot={bot}, event={event}, state={state}" + f"{self} run with incoming args: " + f"bot={bot}, event={event!r}, state={state!r}" ) b_t = current_bot.set(bot) e_t = current_event.set(event) @@ -706,17 +693,12 @@ class Matcher(metaclass=MatcherMeta): stack=stack, dependency_cache=dependency_cache, ) - except TypeMisMatch as e: - logger.debug( - f"Handler {handler} param {e.param.name} value {e.value} " - f"mismatch type {e.param._type_display()}, skipped" - ) - except SkippedException as e: + except SkippedException: logger.debug(f"Handler {handler} skipped") except StopPropagation: self.block = True finally: - logger.info(f"Matcher {self} running complete") + logger.info(f"{self} running complete") current_bot.reset(b_t) current_event.reset(e_t) current_matcher.reset(m_t) diff --git a/nonebot/internal/params.py b/nonebot/internal/params.py index a4782725..341333d1 100644 --- a/nonebot/internal/params.py +++ b/nonebot/internal/params.py @@ -36,7 +36,7 @@ class DependsInner: def __repr__(self) -> str: dep = get_name(self.dependency) cache = "" if self.use_cache else ", use_cache=False" - return f"{self.__class__.__name__}({dep}{cache})" + return f"DependsInner({dep}{cache})" def Depends( @@ -71,6 +71,9 @@ def Depends( class DependParam(Param): """子依赖参数""" + def __repr__(self) -> str: + return f"Depends({self.extra['dependent']})" + @classmethod def _check_param( cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] @@ -153,6 +156,17 @@ class DependParam(Param): class BotParam(Param): """{ref}`nonebot.adapters.Bot` 参数""" + def __repr__(self) -> str: + return ( + "BotParam(" + + ( + repr(cast(ModelField, checker).type_) + if (checker := self.extra.get("checker")) + else "" + ) + + ")" + ) + @classmethod def _check_param( cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] @@ -179,13 +193,24 @@ class BotParam(Param): return bot async def _check(self, bot: "Bot", **kwargs: Any) -> None: - if checker := self.extra.get("checker", None): + if checker := self.extra.get("checker"): check_field_type(checker, bot) class EventParam(Param): """{ref}`nonebot.adapters.Event` 参数""" + def __repr__(self) -> str: + return ( + "EventParam(" + + ( + repr(cast(ModelField, checker).type_) + if (checker := self.extra.get("checker")) + else "" + ) + + ")" + ) + @classmethod def _check_param( cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] @@ -216,20 +241,17 @@ class EventParam(Param): check_field_type(checker, event) -class StateInner(T_State): - ... - - class StateParam(Param): """事件处理状态参数""" + def __repr__(self) -> str: + return "StateParam()" + @classmethod def _check_param( cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] ) -> Optional["StateParam"]: - if isinstance(param.default, StateInner): - return cls(Required) - elif param.default == param.empty: + if param.default == param.empty: if param.annotation is T_State: return cls(Required) elif param.annotation == param.empty and param.name == "state": @@ -242,6 +264,9 @@ class StateParam(Param): class MatcherParam(Param): """事件响应器实例参数""" + def __repr__(self) -> str: + return "MatcherParam()" + @classmethod def _check_param( cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] @@ -264,6 +289,9 @@ class ArgInner: self.key = key self.type = type + def __repr__(self) -> str: + return f"ArgInner(key={self.key!r}, type={self.type!r})" + def Arg(key: Optional[str] = None) -> Any: """`got` 的 Arg 参数消息""" @@ -283,6 +311,9 @@ def ArgPlainText(key: Optional[str] = None) -> str: class ArgParam(Param): """`got` 的 Arg 参数""" + def __repr__(self) -> str: + return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})" + @classmethod def _check_param( cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] @@ -307,6 +338,9 @@ class ArgParam(Param): class ExceptionParam(Param): """`run_postprocessor` 的异常参数""" + def __repr__(self) -> str: + return "ExceptionParam()" + @classmethod def _check_param( cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] @@ -323,6 +357,9 @@ class ExceptionParam(Param): class DefaultParam(Param): """默认值参数""" + def __repr__(self) -> str: + return f"DefaultParam(default={self.default!r})" + @classmethod def _check_param( cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] diff --git a/nonebot/internal/permission.py b/nonebot/internal/permission.py index 0ea7f4f4..d844ffdd 100644 --- a/nonebot/internal/permission.py +++ b/nonebot/internal/permission.py @@ -47,6 +47,9 @@ class Permission: } """存储 `PermissionChecker`""" + def __repr__(self) -> str: + return f"Permission({', '.join(repr(checker) for checker in self.checkers)})" + async def __call__( self, bot: Bot, @@ -121,6 +124,13 @@ class User: self.users = users self.perm = perm + def __repr__(self) -> str: + return ( + f"User(users={self.users}" + + (f", permission={self.perm})" if self.perm else "") + + ")" + ) + async def __call__(self, bot: Bot, event: Event) -> bool: return bool( event.get_session_id() in self.users diff --git a/nonebot/internal/rule.py b/nonebot/internal/rule.py index b5e2f097..a35640e3 100644 --- a/nonebot/internal/rule.py +++ b/nonebot/internal/rule.py @@ -47,6 +47,9 @@ class Rule: } """存储 `RuleChecker`""" + def __repr__(self) -> str: + return f"Rule({', '.join(repr(checker) for checker in self.checkers)})" + async def __call__( self, bot: Bot, diff --git a/nonebot/log.py b/nonebot/log.py index 71395b4e..26336bff 100644 --- a/nonebot/log.py +++ b/nonebot/log.py @@ -14,16 +14,14 @@ FrontMatter: import sys import logging -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING import loguru if TYPE_CHECKING: # avoid sphinx autodoc resolve annotation failed # because loguru module do not have `Logger` class actually - from loguru import Logger - - from nonebot.plugin import Plugin + from loguru import Logger, Record # logger = logging.getLogger("nonebot") logger: "Logger" = loguru.logger @@ -47,26 +45,10 @@ logger: "Logger" = loguru.logger # logger.addHandler(default_handler) -class Filter: - def __init__(self) -> None: - self.level: Union[int, str] = "INFO" - - def __call__(self, record): - module_name: str = record["name"] - # TODO: get plugin name instead of module name - # module = sys.modules.get(module_name) - # if module and hasattr(module, "__plugin__"): - # plugin: "Plugin" = getattr(module, "__plugin__") - # module_name = plugin.module_name - record["name"] = module_name.split(".")[0] - levelno = ( - logger.level(self.level).no if isinstance(self.level, str) else self.level - ) - return record["level"].no >= levelno - - class LoguruHandler(logging.Handler): # pragma: no cover - def emit(self, record): + """logging 与 loguru 之间的桥梁,将 logging 的日志转发到 loguru。""" + + def emit(self, record: logging.LogRecord): try: level = logger.level(record.levelname).name except ValueError: @@ -82,9 +64,13 @@ class LoguruHandler(logging.Handler): # pragma: no cover ) -logger.remove() -default_filter: Filter = Filter() -"""默认日志等级过滤器""" +def default_filter(record: "Record"): + """默认的日志过滤器,根据 `config.log_level` 配置改变日志等级。""" + log_level = record["extra"].get("nonebot_log_level", "INFO") + levelno = logger.level(log_level).no if isinstance(log_level, str) else log_level + return record["level"].no >= levelno + + default_format: str = ( "{time:MM-DD HH:mm:ss} " "[{level}] " @@ -93,6 +79,8 @@ default_format: str = ( "{message}" ) """默认日志格式""" + +logger.remove() logger_id = logger.add( sys.stdout, level=0, @@ -101,4 +89,4 @@ logger_id = logger.add( format=default_format, ) -__autodoc__ = {"Filter": False, "LoguruHandler": False} +__autodoc__ = {"logger_id": False} diff --git a/nonebot/message.py b/nonebot/message.py index b3639caf..0a77a17c 100644 --- a/nonebot/message.py +++ b/nonebot/message.py @@ -51,14 +51,14 @@ _event_postprocessors: Set[Dependent[Any]] = set() _run_preprocessors: Set[Dependent[Any]] = set() _run_postprocessors: Set[Dependent[Any]] = set() -EVENT_PCS_PARAMS = [ +EVENT_PCS_PARAMS = ( DependParam, BotParam, EventParam, StateParam, DefaultParam, -] -RUN_PREPCS_PARAMS = [ +) +RUN_PREPCS_PARAMS = ( DependParam, BotParam, EventParam, @@ -66,8 +66,8 @@ RUN_PREPCS_PARAMS = [ ArgParam, MatcherParam, DefaultParam, -] -RUN_POSTPCS_PARAMS = [ +) +RUN_POSTPCS_PARAMS = ( DependParam, ExceptionParam, BotParam, @@ -76,7 +76,7 @@ RUN_POSTPCS_PARAMS = [ ArgParam, MatcherParam, DefaultParam, -] +) def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor: @@ -170,9 +170,7 @@ async def _run_matcher( try: await asyncio.gather(*coros) except IgnoredException: - logger.opt(colors=True).info( - f"Matcher {matcher} running is cancelled" - ) + logger.opt(colors=True).info(f"{matcher} running is cancelled") return except Exception as e: logger.opt(colors=True, exception=e).error( @@ -184,11 +182,11 @@ async def _run_matcher( exception = None try: - logger.debug(f"Running matcher {matcher}") + logger.debug(f"Running {matcher}") await matcher.run(bot, event, state, stack, dependency_cache) except Exception as e: logger.opt(colors=True, exception=e).error( - f"Running matcher {matcher} failed." + f"Running {matcher} failed." ) exception = e @@ -233,7 +231,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None: ``` """ show_log = True - log_msg = f"{escape_tag(bot.type.upper())} {escape_tag(bot.self_id)} | " + log_msg = f"{escape_tag(bot.type)} {escape_tag(bot.self_id)} | " try: log_msg += event.get_log_string() except NoLogException: diff --git a/nonebot/permission.py b/nonebot/permission.py index 1373fbb6..46a1f3de 100644 --- a/nonebot/permission.py +++ b/nonebot/permission.py @@ -20,6 +20,9 @@ class Message: __slots__ = () + def __repr__(self) -> str: + return "Message()" + async def __call__(self, type: str = EventType()) -> bool: return type == "message" @@ -29,6 +32,9 @@ class Notice: __slots__ = () + def __repr__(self) -> str: + return "Notice()" + async def __call__(self, type: str = EventType()) -> bool: return type == "notice" @@ -38,6 +44,9 @@ class Request: __slots__ = () + def __repr__(self) -> str: + return "Request()" + async def __call__(self, type: str = EventType()) -> bool: return type == "request" @@ -47,6 +56,9 @@ class MetaEvent: __slots__ = () + def __repr__(self) -> str: + return "MetaEvent()" + async def __call__(self, type: str = EventType()) -> bool: return type == "meta_event" @@ -78,6 +90,9 @@ class SuperUser: __slots__ = () + def __repr__(self) -> str: + return "Superuser()" + async def __call__(self, bot: Bot, event: Event) -> bool: return event.get_type() == "message" and ( f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{event.get_user_id()}" diff --git a/nonebot/plugin/__init__.py b/nonebot/plugin/__init__.py index 48d18bc8..b110eb0f 100644 --- a/nonebot/plugin/__init__.py +++ b/nonebot/plugin/__init__.py @@ -27,6 +27,7 @@ - `load_builtin_plugin` => {ref}``load_builtin_plugin` ` - `load_builtin_plugins` => {ref}``load_builtin_plugins` ` - `require` => {ref}``require` ` +- `PluginMetadata` => {ref}``PluginMetadata` ` FrontMatter: sidebar_position: 0 @@ -85,13 +86,12 @@ def get_plugin_by_module_name(module_name: str) -> Optional["Plugin"]: 参数: module_name: 模块名,即 {ref}`nonebot.plugin.plugin.Plugin.module_name`。 """ - splits = module_name.split(".") loaded = {plugin.module_name: plugin for plugin in _plugins.values()} - while splits: - name = ".".join(splits) - if name in loaded: - return loaded[name] - splits.pop() + has_parent = True + while has_parent: + if module_name in loaded: + return loaded[module_name] + module_name, *has_parent = module_name.rsplit(".", 1) def get_loaded_plugins() -> Set["Plugin"]: diff --git a/nonebot/plugin/load.py b/nonebot/plugin/load.py index c51ba1e0..e2d269b3 100644 --- a/nonebot/plugin/load.py +++ b/nonebot/plugin/load.py @@ -159,11 +159,10 @@ def require(name: str) -> ModuleType: """ plugin = get_plugin(_module_name_to_plugin_name(name)) if not plugin: - manager = _find_manager_by_name(name) - if manager: + if manager := _find_manager_by_name(name): plugin = manager.load_plugin(name) else: plugin = load_plugin(name) - if not plugin: - raise RuntimeError(f'Cannot load plugin "{name}"!') + if not plugin: + raise RuntimeError(f'Cannot load plugin "{name}"!') return plugin.module diff --git a/nonebot/plugin/manager.py b/nonebot/plugin/manager.py index 43f76845..5611b954 100644 --- a/nonebot/plugin/manager.py +++ b/nonebot/plugin/manager.py @@ -51,6 +51,9 @@ class PluginManager: self._searched_plugin_names: Dict[str, Path] = {} self.prepare_plugins() + def __repr__(self) -> str: + return f"PluginManager(plugins={self.plugins}, search_path={self.search_path})" + @property def third_party_plugins(self) -> Set[str]: """返回所有独立插件名称。""" diff --git a/nonebot/plugin/on.py b/nonebot/plugin/on.py index 62b24c9e..6915d0ad 100644 --- a/nonebot/plugin/on.py +++ b/nonebot/plugin/on.py @@ -32,9 +32,8 @@ from .manager import _current_plugin_chain def _store_matcher(matcher: Type[Matcher]) -> None: - plugins = _current_plugin_chain.get() # only store the matcher defined in the plugin - if plugins: + if plugins := _current_plugin_chain.get(): plugins[-1].matcher.add(matcher) @@ -370,7 +369,7 @@ def on_command( state: 默认 state """ - commands = set([cmd]) | (aliases or set()) + commands = {cmd} | (aliases or set()) block = kwargs.pop("block", False) return on_message( command(*commands) & rule, block=block, **kwargs, _depth=_depth + 1 @@ -405,7 +404,7 @@ def on_shell_command( state: 默认 state """ - commands = set([cmd]) | (aliases or set()) + commands = {cmd} | (aliases or set()) return on_message( shell_command(*commands, parser=parser) & rule, **kwargs, @@ -486,6 +485,9 @@ class CommandGroup: self.base_kwargs: Dict[str, Any] = kwargs """其他传递给 `on_command` 的参数默认值""" + def __repr__(self) -> str: + return f"CommandGroup(cmd={self.basecmd})" + def command(self, cmd: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]: """注册一个新的命令。新参数将会覆盖命令组默认值 @@ -544,6 +546,9 @@ class MatcherGroup: self.base_kwargs: Dict[str, Any] = kwargs """其他传递给 `on` 的参数默认值""" + def __repr__(self) -> str: + return f"MatcherGroup(matchers={len(self.matchers)})" + def on(self, **kwargs) -> Type[Matcher]: """注册一个基础事件响应器,可自定义类型。 diff --git a/nonebot/plugins/echo.py b/nonebot/plugins/echo.py index 8eb1968c..65b6c654 100644 --- a/nonebot/plugins/echo.py +++ b/nonebot/plugins/echo.py @@ -7,5 +7,5 @@ echo = on_command("echo", to_me()) @echo.handle() -async def echo_escape(message: Message = CommandArg()): +async def handle_echo(message: Message = CommandArg()): await echo.send(message=message) diff --git a/nonebot/plugins/single_session.py b/nonebot/plugins/single_session.py index 9253bd02..1afb478e 100644 --- a/nonebot/plugins/single_session.py +++ b/nonebot/plugins/single_session.py @@ -15,8 +15,7 @@ async def matcher_mutex(event: Event) -> AsyncGenerator[bool, None]: yield result else: current_event_id = id(event) - event_id = _running_matcher.get(session_id, None) - if event_id: + if event_id := _running_matcher.get(session_id, None): result = event_id != current_event_id else: _running_matcher[session_id] = current_event_id diff --git a/nonebot/rule.py b/nonebot/rule.py index f3e83a03..febb455a 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -131,7 +131,7 @@ class StartswithRule: self.ignorecase = ignorecase def __repr__(self) -> str: - return f"StartswithRule(msg={self.msg}, ignorecase={self.ignorecase})" + return f"Startswith(msg={self.msg}, ignorecase={self.ignorecase})" def __eq__(self, other: object) -> bool: return ( @@ -185,7 +185,7 @@ class EndswithRule: self.ignorecase = ignorecase def __repr__(self) -> str: - return f"EndswithRule(msg={self.msg}, ignorecase={self.ignorecase})" + return f"Endswith(msg={self.msg}, ignorecase={self.ignorecase})" def __eq__(self, other: object) -> bool: return ( @@ -239,7 +239,7 @@ class FullmatchRule: self.ignorecase = ignorecase def __repr__(self) -> str: - return f"FullmatchRule(msg={self.msg}, ignorecase={self.ignorecase})" + return f"Fullmatch(msg={self.msg}, ignorecase={self.ignorecase})" def __eq__(self, other: object) -> bool: return ( @@ -286,7 +286,7 @@ class KeywordsRule: self.keywords = keywords def __repr__(self) -> str: - return f"KeywordsRule(keywords={self.keywords})" + return f"Keywords(keywords={self.keywords})" def __eq__(self, other: object) -> bool: return isinstance(other, KeywordsRule) and frozenset( @@ -327,7 +327,7 @@ class CommandRule: self.cmds = tuple(cmds) def __repr__(self) -> str: - return f"CommandRule(cmds={self.cmds})" + return f"Command(cmds={self.cmds})" def __eq__(self, other: object) -> bool: return isinstance(other, CommandRule) and frozenset(self.cmds) == frozenset( @@ -454,7 +454,7 @@ class ShellCommandRule: self.parser = parser def __repr__(self) -> str: - return f"ShellCommandRule(cmds={self.cmds}, parser={self.parser})" + return f"ShellCommand(cmds={self.cmds}, parser={self.parser})" def __eq__(self, other: object) -> bool: return ( @@ -571,7 +571,7 @@ class RegexRule: self.flags = flags def __repr__(self) -> str: - return f"RegexRule(regex={self.regex!r}, flags={self.flags})" + return f"Regex(regex={self.regex!r}, flags={self.flags})" def __eq__(self, other: object) -> bool: return ( @@ -630,7 +630,7 @@ class ToMeRule: __slots__ = () def __repr__(self) -> str: - return "ToMeRule()" + return "ToMe()" def __eq__(self, other: object) -> bool: return isinstance(other, ToMeRule) @@ -657,7 +657,7 @@ class IsTypeRule: self.types = types def __repr__(self) -> str: - return f"IsTypeRule(types={tuple(type.__name__ for type in self.types)})" + return f"IsType(types={tuple(type.__name__ for type in self.types)})" def __eq__(self, other: object) -> bool: return isinstance(other, IsTypeRule) and self.types == other.types diff --git a/tests/.coveragerc b/tests/.coveragerc index 4bf3ebe2..0d96f6b3 100644 --- a/tests/.coveragerc +++ b/tests/.coveragerc @@ -1,6 +1,7 @@ [report] exclude_lines = def __repr__ + def __str__ pragma: no cover if TYPE_CHECKING: @(abc\.)?abstractmethod diff --git a/tests/test_init.py b/tests/test_init.py index f137e9d7..e6c7fae1 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -61,7 +61,7 @@ async def test_get(monkeypatch: pytest.MonkeyPatch, nonebug_clear): with pytest.raises(ValueError): get_bot() - monkeypatch.setattr(driver, "_clients", {"test": "test"}) + monkeypatch.setattr(driver, "_bots", {"test": "test"}) assert get_bot() == "test" assert get_bot("test") == "test" assert get_bots() == {"test": "test"}