🔇 Feature: 调整日志输出格式与等级 (#1233)

This commit is contained in:
Ju4tCode 2022-09-09 11:52:57 +08:00 committed by GitHub
parent 179d7105c9
commit 8c42490a7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 261 additions and 165 deletions

View File

@ -40,10 +40,12 @@ FrontMatter:
import importlib import importlib
from typing import Any, Dict, Type, Optional from typing import Any, Dict, Type, Optional
import loguru
from nonebot.log import logger
from nonebot.adapters import Bot from nonebot.adapters import Bot
from nonebot.utils import escape_tag from nonebot.utils import escape_tag
from nonebot.config import Env, Config from nonebot.config import Env, Config
from nonebot.log import logger, default_filter
from nonebot.drivers import Driver, ReverseDriver, combine_driver from nonebot.drivers import Driver, ReverseDriver, combine_driver
try: try:
@ -206,6 +208,15 @@ def _resolve_combine_expr(obj_str: str) -> Type[Driver]:
return combine_driver(DriverClass, *mixins) 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: def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None:
"""初始化 NoneBot 以及 全局 {ref}`nonebot.drivers.Driver` 对象。 """初始化 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}", _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( logger.opt(colors=True).info(
f"Current <y><b>Env: {escape_tag(env.environment)}</b></y>" f"Current <y><b>Env: {escape_tag(env.environment)}</b></y>"
) )

View File

@ -25,7 +25,6 @@ from pydantic.env_settings import (
) )
from nonebot.log import logger from nonebot.log import logger
from nonebot.utils import escape_tag
class CustomEnvSettings(EnvSettingsSource): class CustomEnvSettings(EnvSettingsSource):
@ -85,12 +84,14 @@ class CustomEnvSettings(EnvSettingsSource):
if env_file_vars: if env_file_vars:
for env_name in env_file_vars.keys(): for env_name in env_file_vars.keys():
env_val = env_vars[env_name] env_val = env_vars[env_name]
if env_val and (val_striped := env_val.strip()):
try: try:
if env_val: env_val = settings.__config__.json_loads(val_striped)
env_val = settings.__config__.json_loads(env_val.strip())
except ValueError as e: except ValueError as e:
logger.opt(colors=True, exception=e).trace( logger.trace(
f"Error while parsing JSON for {escape_tag(env_name)}. Assumed as string." "Error while parsing JSON for "
f"{env_name!r}={val_striped!r}. "
"Assumed as string."
) )
d[env_name] = env_val d[env_name] = env_val

View File

@ -30,6 +30,7 @@ from pydantic.fields import Required, FieldInfo, Undefined, ModelField
from nonebot.log import logger from nonebot.log import logger
from nonebot.typing import _DependentCallable from nonebot.typing import _DependentCallable
from nonebot.exception import SkippedException
from nonebot.utils import run_sync, is_coroutine_callable from nonebot.utils import run_sync, is_coroutine_callable
from .utils import check_field_type, get_typed_signature from .utils import check_field_type, get_typed_signature
@ -85,7 +86,15 @@ class Dependent(Generic[R]):
parameterless: Tuple[Param] = field(default_factory=tuple) parameterless: Tuple[Param] = field(default_factory=tuple)
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<Dependent call={self.call}>" 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: async def __call__(self, **kwargs: Any) -> R:
# do pre-check # do pre-check
@ -178,19 +187,22 @@ class Dependent(Generic[R]):
else cls.parse_parameterless(tuple(parameterless), allow_types) 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) return cls(call, params, parameterless_params)
async def check(self, **params: Any) -> None: async def check(self, **params: Any) -> None:
await asyncio.gather(*(param._check(**params) for param in self.parameterless)) try:
await asyncio.gather( await asyncio.gather(
*(cast(Param, param.field_info)._check(**params) for param in self.params) *(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: async def _solve_field(self, field: ModelField, params: Dict[str, Any]) -> Any:
value = await cast(Param, field.field_info)._solve(**params) value = await cast(Param, field.field_info)._solve(**params)

View File

@ -27,7 +27,7 @@ from nonebot.drivers import HTTPVersion, ForwardMixin, ForwardDriver, combine_dr
try: try:
import aiohttp import aiohttp
except ImportError: except ImportError: # pragma: no cover
raise ImportError( raise ImportError(
"Please install aiohttp first to use this driver. `pip install nonebot2[aiohttp]`" "Please install aiohttp first to use this driver. `pip install nonebot2[aiohttp]`"
) from None ) from None

View File

@ -31,7 +31,7 @@ from nonebot.drivers import (
try: try:
import httpx import httpx
except ImportError: except ImportError: # pragma: no cover
raise ImportError( raise ImportError(
"Please install httpx by using `pip install nonebot2[httpx]`" "Please install httpx by using `pip install nonebot2[httpx]`"
) from None ) from None

View File

@ -37,7 +37,7 @@ try:
from quart import Quart, Request, Response from quart import Quart, Request, Response
from quart.datastructures import FileStorage from quart.datastructures import FileStorage
from quart import Websocket as QuartWebSocket from quart import Websocket as QuartWebSocket
except ImportError: except ImportError: # pragma: no cover
raise ImportError( raise ImportError(
"Please install Quart by using `pip install nonebot2[quart]`" "Please install Quart by using `pip install nonebot2[quart]`"
) from None ) from None

View File

@ -30,10 +30,10 @@ from nonebot.drivers import ForwardMixin, ForwardDriver, combine_driver
try: try:
from websockets.exceptions import ConnectionClosed from websockets.exceptions import ConnectionClosed
from websockets.legacy.client import Connect, WebSocketClientProtocol from websockets.legacy.client import Connect, WebSocketClientProtocol
except ImportError: except ImportError: # pragma: no cover
raise ImportError( raise ImportError(
"Please install websockets by using `pip install nonebot2[websockets]`" "Please install websockets by using `pip install nonebot2[websockets]`"
) ) from None
logger = logging.Logger("websockets.client", "INFO") logger = logging.Logger("websockets.client", "INFO")
logger.addHandler(LoguruHandler()) logger.addHandler(LoguruHandler())

View File

@ -46,10 +46,14 @@ class ParserExit(NoneBotException):
self.status = status self.status = status
self.message = message self.message = message
def __repr__(self): def __repr__(self) -> str:
return f"<ParserExit status={self.status} message={self.message}>" 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__() return self.__repr__()
@ -68,10 +72,10 @@ class IgnoredException(ProcessException):
def __init__(self, reason: Any): def __init__(self, reason: Any):
self.reason: Any = reason self.reason: Any = reason
def __repr__(self): def __repr__(self) -> str:
return f"<IgnoredException, reason={self.reason}>" return f"IgnoredException(reason={self.reason!r})"
def __str__(self): def __str__(self) -> str:
return self.__repr__() return self.__repr__()
@ -99,11 +103,14 @@ class TypeMisMatch(SkippedException):
self.param: ModelField = param self.param: ModelField = param
self.value: Any = value self.value: Any = value
def __repr__(self): def __repr__(self) -> str:
return f"<TypeMisMatch, param={self.param}, value={self.value}>" return (
f"TypeMisMatch(param={self.param.name}, "
f"type={self.param._type_display()}, value={self.value!r}>"
)
def __str__(self): def __str__(self) -> str:
self.__repr__() return self.__repr__()
class MockApiException(ProcessException): class MockApiException(ProcessException):
@ -116,10 +123,10 @@ class MockApiException(ProcessException):
def __init__(self, result: Any): def __init__(self, result: Any):
self.result = result self.result = result
def __repr__(self): def __repr__(self) -> str:
return f"<ApiCancelledException, result={self.result}>" return f"MockApiException(result={self.result!r})"
def __str__(self): def __str__(self) -> str:
return self.__repr__() return self.__repr__()
@ -195,7 +202,8 @@ class AdapterException(NoneBotException):
adapter_name: 标识 adapter 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 self.adapter_name: str = adapter_name
@ -231,4 +239,11 @@ class WebSocketClosed(DriverException):
self.reason = reason self.reason = reason
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<WebSocketClosed code={self.code} reason={self.reason}>" return (
f"WebSocketClosed(code={self.code}"
+ (f", reason={self.reason!r}" if self.reason else "")
+ ")"
)
def __str__(self) -> str:
return self.__repr__()

View File

@ -33,6 +33,9 @@ class Adapter(abc.ABC):
self.bots: Dict[str, Bot] = {} self.bots: Dict[str, Bot] = {}
"""本协议适配器已建立连接的 {ref}`nonebot.adapters.Bot` 实例""" """本协议适配器已建立连接的 {ref}`nonebot.adapters.Bot` 实例"""
def __repr__(self) -> str:
return f"Adapter(name={self.get_name()!r})"
@classmethod @classmethod
@abc.abstractmethod @abc.abstractmethod
def get_name(cls) -> str: def get_name(cls) -> str:

View File

@ -13,8 +13,7 @@ if TYPE_CHECKING:
from .adapter import Adapter from .adapter import Adapter
from .message import Message, MessageSegment from .message import Message, MessageSegment
class _ApiCall(Protocol):
class _ApiCall(Protocol):
async def __call__(self, **kwargs: Any) -> Any: async def __call__(self, **kwargs: Any) -> Any:
... ...
@ -40,7 +39,10 @@ class Bot(abc.ABC):
self.self_id: str = self_id self.self_id: str = self_id
"""机器人 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) return partial(self.call_api, name)
@property @property

View File

@ -56,6 +56,9 @@ class MessageTemplate(Formatter, Generic[TF]):
self.factory: Type[TF] = factory self.factory: Type[TF] = factory
self.format_specs: Dict[str, FormatSpecFunc] = {} self.format_specs: Dict[str, FormatSpecFunc] = {}
def __repr__(self) -> str:
return f"MessageTemplate({self.template!r}, factory={self.factory!r})"
def add_format_spec( def add_format_spec(
self, spec: FormatSpecFunc_T, name: Optional[str] = None self, spec: FormatSpecFunc_T, name: Optional[str] = None
) -> FormatSpecFunc_T: ) -> FormatSpecFunc_T:

View File

@ -40,12 +40,18 @@ class Driver(abc.ABC):
"""环境名称""" """环境名称"""
self.config: Config = config 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 @property
def bots(self) -> Dict[str, "Bot"]: def bots(self) -> Dict[str, "Bot"]:
"""获取当前所有已连接的 Bot""" """获取当前所有已连接的 Bot"""
return self._clients return self._bots
def register_adapter(self, adapter: Type["Adapter"], **kwargs) -> None: def register_adapter(self, adapter: Type["Adapter"], **kwargs) -> None:
"""注册一个协议适配器 """注册一个协议适配器
@ -124,9 +130,9 @@ class Driver(abc.ABC):
def _bot_connect(self, bot: "Bot") -> None: def _bot_connect(self, bot: "Bot") -> None:
"""在连接成功后,调用该函数来注册 bot 对象""" """在连接成功后,调用该函数来注册 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}") 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: async def _run_hook(bot: "Bot") -> None:
coros = list( coros = list(
@ -148,8 +154,8 @@ class Driver(abc.ABC):
def _bot_disconnect(self, bot: "Bot") -> None: def _bot_disconnect(self, bot: "Bot") -> None:
"""在连接断开后,调用该函数来注销 bot 对象""" """在连接断开后,调用该函数来注销 bot 对象"""
if bot.self_id in self._clients: if bot.self_id in self._bots:
del self._clients[bot.self_id] del self._bots[bot.self_id]
async def _run_hook(bot: "Bot") -> None: async def _run_hook(bot: "Bot") -> None:
coros = list( coros = list(

View File

@ -131,9 +131,7 @@ class Request:
self.files.append((name, file_info)) # type: ignore self.files.append((name, file_info)) # type: ignore
def __repr__(self) -> str: def __repr__(self) -> str:
class_name = self.__class__.__name__ return f"{self.__class__.__name__}(method={self.method!r}, url='{self.url!s}')"
url = str(self.url)
return f"<{class_name}({self.method!r}, {url!r})>"
class Response: class Response:
@ -161,12 +159,18 @@ class Response:
# request # request
self.request: Optional[Request] = 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): class WebSocket(abc.ABC):
def __init__(self, *, request: Request): def __init__(self, *, request: Request):
# request # request
self.request: Request = request self.request: Request = request
def __repr__(self) -> str:
return f"{self.__class__.__name__}('{self.request.url!s}')"
@property @property
@abc.abstractmethod @abc.abstractmethod
def closed(self) -> bool: def closed(self) -> bool:
@ -320,17 +324,14 @@ class Cookies(MutableMapping):
return len(self.jar) return len(self.jar)
def __iter__(self) -> Iterator[Cookie]: def __iter__(self) -> Iterator[Cookie]:
return (cookie for cookie in self.jar) return iter(self.jar)
def __repr__(self) -> str: def __repr__(self) -> str:
cookies_repr = ", ".join( cookies_repr = ", ".join(
[ f"Cookie({cookie.name}={cookie.value} for {cookie.domain})"
f"<Cookie {cookie.name}={cookie.value} for {cookie.domain} />"
for cookie in self.jar for cookie in self.jar
]
) )
return f"{self.__class__.__name__}({cookies_repr})"
return f"<Cookies [{cookies_repr}]>"
@dataclass @dataclass

View File

@ -36,7 +36,6 @@ from nonebot.typing import (
T_PermissionUpdater, T_PermissionUpdater,
) )
from nonebot.exception import ( from nonebot.exception import (
TypeMisMatch,
PausedException, PausedException,
StopPropagation, StopPropagation,
SkippedException, SkippedException,
@ -73,29 +72,16 @@ current_handler: ContextVar[Dependent] = ContextVar("current_handler")
class MatcherMeta(type): class MatcherMeta(type):
if TYPE_CHECKING: if TYPE_CHECKING:
module: Optional[str]
plugin_name: Optional[str]
module_name: Optional[str] module_name: Optional[str]
module_prefix: Optional[str]
type: 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: def __repr__(self) -> str:
return ( return (
f"<Matcher from {self.module_name or 'unknown'}, " f"Matcher(type={self.type!r}"
f"type={self.type}, priority={self.priority}, " + (f", module={self.module_name}" if self.module_name else "")
f"temp={self.temp}>" + ")"
) )
def __str__(self) -> str:
return repr(self)
class Matcher(metaclass=MatcherMeta): class Matcher(metaclass=MatcherMeta):
"""事件响应器类""" """事件响应器类"""
@ -150,8 +136,9 @@ class Matcher(metaclass=MatcherMeta):
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
f"<Matcher from {self.module_name or 'unknown'}, type={self.type}, " f"Matcher(type={self.type!r}"
f"priority={self.priority}, temp={self.temp}>" + (f", module={self.module_name}" if self.module_name else "")
+ ")"
) )
@classmethod @classmethod
@ -683,8 +670,8 @@ class Matcher(metaclass=MatcherMeta):
dependency_cache: Optional[T_DependencyCache] = None, dependency_cache: Optional[T_DependencyCache] = None,
): ):
logger.trace( logger.trace(
f"Matcher {self} run with incoming args: " f"{self} run with incoming args: "
f"bot={bot}, event={event}, state={state}" f"bot={bot}, event={event!r}, state={state!r}"
) )
b_t = current_bot.set(bot) b_t = current_bot.set(bot)
e_t = current_event.set(event) e_t = current_event.set(event)
@ -706,17 +693,12 @@ class Matcher(metaclass=MatcherMeta):
stack=stack, stack=stack,
dependency_cache=dependency_cache, dependency_cache=dependency_cache,
) )
except TypeMisMatch as e: except SkippedException:
logger.debug(
f"Handler {handler} param {e.param.name} value {e.value} "
f"mismatch type {e.param._type_display()}, skipped"
)
except SkippedException as e:
logger.debug(f"Handler {handler} skipped") logger.debug(f"Handler {handler} skipped")
except StopPropagation: except StopPropagation:
self.block = True self.block = True
finally: finally:
logger.info(f"Matcher {self} running complete") logger.info(f"{self} running complete")
current_bot.reset(b_t) current_bot.reset(b_t)
current_event.reset(e_t) current_event.reset(e_t)
current_matcher.reset(m_t) current_matcher.reset(m_t)

View File

@ -36,7 +36,7 @@ class DependsInner:
def __repr__(self) -> str: def __repr__(self) -> str:
dep = get_name(self.dependency) dep = get_name(self.dependency)
cache = "" if self.use_cache else ", use_cache=False" cache = "" if self.use_cache else ", use_cache=False"
return f"{self.__class__.__name__}({dep}{cache})" return f"DependsInner({dep}{cache})"
def Depends( def Depends(
@ -71,6 +71,9 @@ def Depends(
class DependParam(Param): class DependParam(Param):
"""子依赖参数""" """子依赖参数"""
def __repr__(self) -> str:
return f"Depends({self.extra['dependent']})"
@classmethod @classmethod
def _check_param( def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
@ -153,6 +156,17 @@ class DependParam(Param):
class BotParam(Param): class BotParam(Param):
"""{ref}`nonebot.adapters.Bot` 参数""" """{ref}`nonebot.adapters.Bot` 参数"""
def __repr__(self) -> str:
return (
"BotParam("
+ (
repr(cast(ModelField, checker).type_)
if (checker := self.extra.get("checker"))
else ""
)
+ ")"
)
@classmethod @classmethod
def _check_param( def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
@ -179,13 +193,24 @@ class BotParam(Param):
return bot return bot
async def _check(self, bot: "Bot", **kwargs: Any) -> None: 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) check_field_type(checker, bot)
class EventParam(Param): class EventParam(Param):
"""{ref}`nonebot.adapters.Event` 参数""" """{ref}`nonebot.adapters.Event` 参数"""
def __repr__(self) -> str:
return (
"EventParam("
+ (
repr(cast(ModelField, checker).type_)
if (checker := self.extra.get("checker"))
else ""
)
+ ")"
)
@classmethod @classmethod
def _check_param( def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
@ -216,20 +241,17 @@ class EventParam(Param):
check_field_type(checker, event) check_field_type(checker, event)
class StateInner(T_State):
...
class StateParam(Param): class StateParam(Param):
"""事件处理状态参数""" """事件处理状态参数"""
def __repr__(self) -> str:
return "StateParam()"
@classmethod @classmethod
def _check_param( def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["StateParam"]: ) -> Optional["StateParam"]:
if isinstance(param.default, StateInner): if param.default == param.empty:
return cls(Required)
elif param.default == param.empty:
if param.annotation is T_State: if param.annotation is T_State:
return cls(Required) return cls(Required)
elif param.annotation == param.empty and param.name == "state": elif param.annotation == param.empty and param.name == "state":
@ -242,6 +264,9 @@ class StateParam(Param):
class MatcherParam(Param): class MatcherParam(Param):
"""事件响应器实例参数""" """事件响应器实例参数"""
def __repr__(self) -> str:
return "MatcherParam()"
@classmethod @classmethod
def _check_param( def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
@ -264,6 +289,9 @@ class ArgInner:
self.key = key self.key = key
self.type = type 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: def Arg(key: Optional[str] = None) -> Any:
"""`got` 的 Arg 参数消息""" """`got` 的 Arg 参数消息"""
@ -283,6 +311,9 @@ def ArgPlainText(key: Optional[str] = None) -> str:
class ArgParam(Param): class ArgParam(Param):
"""`got` 的 Arg 参数""" """`got` 的 Arg 参数"""
def __repr__(self) -> str:
return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})"
@classmethod @classmethod
def _check_param( def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
@ -307,6 +338,9 @@ class ArgParam(Param):
class ExceptionParam(Param): class ExceptionParam(Param):
"""`run_postprocessor` 的异常参数""" """`run_postprocessor` 的异常参数"""
def __repr__(self) -> str:
return "ExceptionParam()"
@classmethod @classmethod
def _check_param( def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
@ -323,6 +357,9 @@ class ExceptionParam(Param):
class DefaultParam(Param): class DefaultParam(Param):
"""默认值参数""" """默认值参数"""
def __repr__(self) -> str:
return f"DefaultParam(default={self.default!r})"
@classmethod @classmethod
def _check_param( def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]

View File

@ -47,6 +47,9 @@ class Permission:
} }
"""存储 `PermissionChecker`""" """存储 `PermissionChecker`"""
def __repr__(self) -> str:
return f"Permission({', '.join(repr(checker) for checker in self.checkers)})"
async def __call__( async def __call__(
self, self,
bot: Bot, bot: Bot,
@ -121,6 +124,13 @@ class User:
self.users = users self.users = users
self.perm = perm 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: async def __call__(self, bot: Bot, event: Event) -> bool:
return bool( return bool(
event.get_session_id() in self.users event.get_session_id() in self.users

View File

@ -47,6 +47,9 @@ class Rule:
} }
"""存储 `RuleChecker`""" """存储 `RuleChecker`"""
def __repr__(self) -> str:
return f"Rule({', '.join(repr(checker) for checker in self.checkers)})"
async def __call__( async def __call__(
self, self,
bot: Bot, bot: Bot,

View File

@ -14,16 +14,14 @@ FrontMatter:
import sys import sys
import logging import logging
from typing import TYPE_CHECKING, Union from typing import TYPE_CHECKING
import loguru import loguru
if TYPE_CHECKING: if TYPE_CHECKING:
# avoid sphinx autodoc resolve annotation failed # avoid sphinx autodoc resolve annotation failed
# because loguru module do not have `Logger` class actually # because loguru module do not have `Logger` class actually
from loguru import Logger from loguru import Logger, Record
from nonebot.plugin import Plugin
# logger = logging.getLogger("nonebot") # logger = logging.getLogger("nonebot")
logger: "Logger" = loguru.logger logger: "Logger" = loguru.logger
@ -47,26 +45,10 @@ logger: "Logger" = loguru.logger
# logger.addHandler(default_handler) # 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 class LoguruHandler(logging.Handler): # pragma: no cover
def emit(self, record): """logging 与 loguru 之间的桥梁,将 logging 的日志转发到 loguru。"""
def emit(self, record: logging.LogRecord):
try: try:
level = logger.level(record.levelname).name level = logger.level(record.levelname).name
except ValueError: except ValueError:
@ -82,9 +64,13 @@ class LoguruHandler(logging.Handler): # pragma: no cover
) )
logger.remove() def default_filter(record: "Record"):
default_filter: Filter = Filter() """默认的日志过滤器,根据 `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 = ( default_format: str = (
"<g>{time:MM-DD HH:mm:ss}</g> " "<g>{time:MM-DD HH:mm:ss}</g> "
"[<lvl>{level}</lvl>] " "[<lvl>{level}</lvl>] "
@ -93,6 +79,8 @@ default_format: str = (
"{message}" "{message}"
) )
"""默认日志格式""" """默认日志格式"""
logger.remove()
logger_id = logger.add( logger_id = logger.add(
sys.stdout, sys.stdout,
level=0, level=0,
@ -101,4 +89,4 @@ logger_id = logger.add(
format=default_format, format=default_format,
) )
__autodoc__ = {"Filter": False, "LoguruHandler": False} __autodoc__ = {"logger_id": False}

View File

@ -51,14 +51,14 @@ _event_postprocessors: Set[Dependent[Any]] = set()
_run_preprocessors: Set[Dependent[Any]] = set() _run_preprocessors: Set[Dependent[Any]] = set()
_run_postprocessors: Set[Dependent[Any]] = set() _run_postprocessors: Set[Dependent[Any]] = set()
EVENT_PCS_PARAMS = [ EVENT_PCS_PARAMS = (
DependParam, DependParam,
BotParam, BotParam,
EventParam, EventParam,
StateParam, StateParam,
DefaultParam, DefaultParam,
] )
RUN_PREPCS_PARAMS = [ RUN_PREPCS_PARAMS = (
DependParam, DependParam,
BotParam, BotParam,
EventParam, EventParam,
@ -66,8 +66,8 @@ RUN_PREPCS_PARAMS = [
ArgParam, ArgParam,
MatcherParam, MatcherParam,
DefaultParam, DefaultParam,
] )
RUN_POSTPCS_PARAMS = [ RUN_POSTPCS_PARAMS = (
DependParam, DependParam,
ExceptionParam, ExceptionParam,
BotParam, BotParam,
@ -76,7 +76,7 @@ RUN_POSTPCS_PARAMS = [
ArgParam, ArgParam,
MatcherParam, MatcherParam,
DefaultParam, DefaultParam,
] )
def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor: def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
@ -170,9 +170,7 @@ async def _run_matcher(
try: try:
await asyncio.gather(*coros) await asyncio.gather(*coros)
except IgnoredException: except IgnoredException:
logger.opt(colors=True).info( logger.opt(colors=True).info(f"{matcher} running is <b>cancelled</b>")
f"Matcher {matcher} running is <b>cancelled</b>"
)
return return
except Exception as e: except Exception as e:
logger.opt(colors=True, exception=e).error( logger.opt(colors=True, exception=e).error(
@ -184,11 +182,11 @@ async def _run_matcher(
exception = None exception = None
try: try:
logger.debug(f"Running matcher {matcher}") logger.debug(f"Running {matcher}")
await matcher.run(bot, event, state, stack, dependency_cache) await matcher.run(bot, event, state, stack, dependency_cache)
except Exception as e: except Exception as e:
logger.opt(colors=True, exception=e).error( logger.opt(colors=True, exception=e).error(
f"<r><bg #f8bbd0>Running matcher {matcher} failed.</bg #f8bbd0></r>" f"<r><bg #f8bbd0>Running {matcher} failed.</bg #f8bbd0></r>"
) )
exception = e exception = e
@ -233,7 +231,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
``` ```
""" """
show_log = True show_log = True
log_msg = f"<m>{escape_tag(bot.type.upper())} {escape_tag(bot.self_id)}</m> | " log_msg = f"<m>{escape_tag(bot.type)} {escape_tag(bot.self_id)}</m> | "
try: try:
log_msg += event.get_log_string() log_msg += event.get_log_string()
except NoLogException: except NoLogException:

View File

@ -20,6 +20,9 @@ class Message:
__slots__ = () __slots__ = ()
def __repr__(self) -> str:
return "Message()"
async def __call__(self, type: str = EventType()) -> bool: async def __call__(self, type: str = EventType()) -> bool:
return type == "message" return type == "message"
@ -29,6 +32,9 @@ class Notice:
__slots__ = () __slots__ = ()
def __repr__(self) -> str:
return "Notice()"
async def __call__(self, type: str = EventType()) -> bool: async def __call__(self, type: str = EventType()) -> bool:
return type == "notice" return type == "notice"
@ -38,6 +44,9 @@ class Request:
__slots__ = () __slots__ = ()
def __repr__(self) -> str:
return "Request()"
async def __call__(self, type: str = EventType()) -> bool: async def __call__(self, type: str = EventType()) -> bool:
return type == "request" return type == "request"
@ -47,6 +56,9 @@ class MetaEvent:
__slots__ = () __slots__ = ()
def __repr__(self) -> str:
return "MetaEvent()"
async def __call__(self, type: str = EventType()) -> bool: async def __call__(self, type: str = EventType()) -> bool:
return type == "meta_event" return type == "meta_event"
@ -78,6 +90,9 @@ class SuperUser:
__slots__ = () __slots__ = ()
def __repr__(self) -> str:
return "Superuser()"
async def __call__(self, bot: Bot, event: Event) -> bool: async def __call__(self, bot: Bot, event: Event) -> bool:
return event.get_type() == "message" and ( return event.get_type() == "message" and (
f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{event.get_user_id()}" f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{event.get_user_id()}"

View File

@ -27,6 +27,7 @@
- `load_builtin_plugin` => {ref}``load_builtin_plugin` <nonebot.plugin.load.load_builtin_plugin>` - `load_builtin_plugin` => {ref}``load_builtin_plugin` <nonebot.plugin.load.load_builtin_plugin>`
- `load_builtin_plugins` => {ref}``load_builtin_plugins` <nonebot.plugin.load.load_builtin_plugins>` - `load_builtin_plugins` => {ref}``load_builtin_plugins` <nonebot.plugin.load.load_builtin_plugins>`
- `require` => {ref}``require` <nonebot.plugin.load.require>` - `require` => {ref}``require` <nonebot.plugin.load.require>`
- `PluginMetadata` => {ref}``PluginMetadata` <nonebot.plugin.plugin.PluginMetadata>`
FrontMatter: FrontMatter:
sidebar_position: 0 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` module_name: 模块名 {ref}`nonebot.plugin.plugin.Plugin.module_name`
""" """
splits = module_name.split(".")
loaded = {plugin.module_name: plugin for plugin in _plugins.values()} loaded = {plugin.module_name: plugin for plugin in _plugins.values()}
while splits: has_parent = True
name = ".".join(splits) while has_parent:
if name in loaded: if module_name in loaded:
return loaded[name] return loaded[module_name]
splits.pop() module_name, *has_parent = module_name.rsplit(".", 1)
def get_loaded_plugins() -> Set["Plugin"]: def get_loaded_plugins() -> Set["Plugin"]:

View File

@ -159,8 +159,7 @@ def require(name: str) -> ModuleType:
""" """
plugin = get_plugin(_module_name_to_plugin_name(name)) plugin = get_plugin(_module_name_to_plugin_name(name))
if not plugin: if not plugin:
manager = _find_manager_by_name(name) if manager := _find_manager_by_name(name):
if manager:
plugin = manager.load_plugin(name) plugin = manager.load_plugin(name)
else: else:
plugin = load_plugin(name) plugin = load_plugin(name)

View File

@ -51,6 +51,9 @@ class PluginManager:
self._searched_plugin_names: Dict[str, Path] = {} self._searched_plugin_names: Dict[str, Path] = {}
self.prepare_plugins() self.prepare_plugins()
def __repr__(self) -> str:
return f"PluginManager(plugins={self.plugins}, search_path={self.search_path})"
@property @property
def third_party_plugins(self) -> Set[str]: def third_party_plugins(self) -> Set[str]:
"""返回所有独立插件名称。""" """返回所有独立插件名称。"""

View File

@ -32,9 +32,8 @@ from .manager import _current_plugin_chain
def _store_matcher(matcher: Type[Matcher]) -> None: def _store_matcher(matcher: Type[Matcher]) -> None:
plugins = _current_plugin_chain.get()
# only store the matcher defined in the plugin # only store the matcher defined in the plugin
if plugins: if plugins := _current_plugin_chain.get():
plugins[-1].matcher.add(matcher) plugins[-1].matcher.add(matcher)
@ -370,7 +369,7 @@ def on_command(
state: 默认 state state: 默认 state
""" """
commands = set([cmd]) | (aliases or set()) commands = {cmd} | (aliases or set())
block = kwargs.pop("block", False) block = kwargs.pop("block", False)
return on_message( return on_message(
command(*commands) & rule, block=block, **kwargs, _depth=_depth + 1 command(*commands) & rule, block=block, **kwargs, _depth=_depth + 1
@ -405,7 +404,7 @@ def on_shell_command(
state: 默认 state state: 默认 state
""" """
commands = set([cmd]) | (aliases or set()) commands = {cmd} | (aliases or set())
return on_message( return on_message(
shell_command(*commands, parser=parser) & rule, shell_command(*commands, parser=parser) & rule,
**kwargs, **kwargs,
@ -486,6 +485,9 @@ class CommandGroup:
self.base_kwargs: Dict[str, Any] = kwargs self.base_kwargs: Dict[str, Any] = kwargs
"""其他传递给 `on_command` 的参数默认值""" """其他传递给 `on_command` 的参数默认值"""
def __repr__(self) -> str:
return f"CommandGroup(cmd={self.basecmd})"
def command(self, cmd: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]: def command(self, cmd: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]:
"""注册一个新的命令。新参数将会覆盖命令组默认值 """注册一个新的命令。新参数将会覆盖命令组默认值
@ -544,6 +546,9 @@ class MatcherGroup:
self.base_kwargs: Dict[str, Any] = kwargs self.base_kwargs: Dict[str, Any] = kwargs
"""其他传递给 `on` 的参数默认值""" """其他传递给 `on` 的参数默认值"""
def __repr__(self) -> str:
return f"MatcherGroup(matchers={len(self.matchers)})"
def on(self, **kwargs) -> Type[Matcher]: def on(self, **kwargs) -> Type[Matcher]:
"""注册一个基础事件响应器,可自定义类型。 """注册一个基础事件响应器,可自定义类型。

View File

@ -7,5 +7,5 @@ echo = on_command("echo", to_me())
@echo.handle() @echo.handle()
async def echo_escape(message: Message = CommandArg()): async def handle_echo(message: Message = CommandArg()):
await echo.send(message=message) await echo.send(message=message)

View File

@ -15,8 +15,7 @@ async def matcher_mutex(event: Event) -> AsyncGenerator[bool, None]:
yield result yield result
else: else:
current_event_id = id(event) current_event_id = id(event)
event_id = _running_matcher.get(session_id, None) if event_id := _running_matcher.get(session_id, None):
if event_id:
result = event_id != current_event_id result = event_id != current_event_id
else: else:
_running_matcher[session_id] = current_event_id _running_matcher[session_id] = current_event_id

View File

@ -131,7 +131,7 @@ class StartswithRule:
self.ignorecase = ignorecase self.ignorecase = ignorecase
def __repr__(self) -> str: 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: def __eq__(self, other: object) -> bool:
return ( return (
@ -185,7 +185,7 @@ class EndswithRule:
self.ignorecase = ignorecase self.ignorecase = ignorecase
def __repr__(self) -> str: 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: def __eq__(self, other: object) -> bool:
return ( return (
@ -239,7 +239,7 @@ class FullmatchRule:
self.ignorecase = ignorecase self.ignorecase = ignorecase
def __repr__(self) -> str: 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: def __eq__(self, other: object) -> bool:
return ( return (
@ -286,7 +286,7 @@ class KeywordsRule:
self.keywords = keywords self.keywords = keywords
def __repr__(self) -> str: def __repr__(self) -> str:
return f"KeywordsRule(keywords={self.keywords})" return f"Keywords(keywords={self.keywords})"
def __eq__(self, other: object) -> bool: def __eq__(self, other: object) -> bool:
return isinstance(other, KeywordsRule) and frozenset( return isinstance(other, KeywordsRule) and frozenset(
@ -327,7 +327,7 @@ class CommandRule:
self.cmds = tuple(cmds) self.cmds = tuple(cmds)
def __repr__(self) -> str: def __repr__(self) -> str:
return f"CommandRule(cmds={self.cmds})" return f"Command(cmds={self.cmds})"
def __eq__(self, other: object) -> bool: def __eq__(self, other: object) -> bool:
return isinstance(other, CommandRule) and frozenset(self.cmds) == frozenset( return isinstance(other, CommandRule) and frozenset(self.cmds) == frozenset(
@ -454,7 +454,7 @@ class ShellCommandRule:
self.parser = parser self.parser = parser
def __repr__(self) -> str: 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: def __eq__(self, other: object) -> bool:
return ( return (
@ -571,7 +571,7 @@ class RegexRule:
self.flags = flags self.flags = flags
def __repr__(self) -> str: 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: def __eq__(self, other: object) -> bool:
return ( return (
@ -630,7 +630,7 @@ class ToMeRule:
__slots__ = () __slots__ = ()
def __repr__(self) -> str: def __repr__(self) -> str:
return "ToMeRule()" return "ToMe()"
def __eq__(self, other: object) -> bool: def __eq__(self, other: object) -> bool:
return isinstance(other, ToMeRule) return isinstance(other, ToMeRule)
@ -657,7 +657,7 @@ class IsTypeRule:
self.types = types self.types = types
def __repr__(self) -> str: 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: def __eq__(self, other: object) -> bool:
return isinstance(other, IsTypeRule) and self.types == other.types return isinstance(other, IsTypeRule) and self.types == other.types

View File

@ -1,6 +1,7 @@
[report] [report]
exclude_lines = exclude_lines =
def __repr__ def __repr__
def __str__
pragma: no cover pragma: no cover
if TYPE_CHECKING: if TYPE_CHECKING:
@(abc\.)?abstractmethod @(abc\.)?abstractmethod

View File

@ -61,7 +61,7 @@ async def test_get(monkeypatch: pytest.MonkeyPatch, nonebug_clear):
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_bot() get_bot()
monkeypatch.setattr(driver, "_clients", {"test": "test"}) monkeypatch.setattr(driver, "_bots", {"test": "test"})
assert get_bot() == "test" assert get_bot() == "test"
assert get_bot("test") == "test" assert get_bot("test") == "test"
assert get_bots() == {"test": "test"} assert get_bots() == {"test": "test"}