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"}