🔇 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
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 <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.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

View File

@ -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"<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:
# 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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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())

View File

@ -46,10 +46,14 @@ class ParserExit(NoneBotException):
self.status = status
self.message = message
def __repr__(self):
return f"<ParserExit status={self.status} message={self.message}>"
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"<IgnoredException, reason={self.reason}>"
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"<TypeMisMatch, param={self.param}, value={self.value}>"
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"<ApiCancelledException, result={self.result}>"
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"<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] = {}
"""本协议适配器已建立连接的 {ref}`nonebot.adapters.Bot` 实例"""
def __repr__(self) -> str:
return f"Adapter(name={self.get_name()!r})"
@classmethod
@abc.abstractmethod
def get_name(cls) -> str:

View File

@ -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

View File

@ -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:

View File

@ -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(

View File

@ -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"<Cookie {cookie.name}={cookie.value} for {cookie.domain} />"
for cookie in self.jar
]
f"Cookie({cookie.name}={cookie.value} for {cookie.domain})"
for cookie in self.jar
)
return f"<Cookies [{cookies_repr}]>"
return f"{self.__class__.__name__}({cookies_repr})"
@dataclass

View File

@ -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"<Matcher from {self.module_name or 'unknown'}, "
f"type={self.type}, priority={self.priority}, "
f"temp={self.temp}>"
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"<Matcher from {self.module_name or 'unknown'}, type={self.type}, "
f"priority={self.priority}, temp={self.temp}>"
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)

View File

@ -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], ...]

View File

@ -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

View File

@ -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,

View File

@ -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 = (
"<g>{time:MM-DD HH:mm:ss}</g> "
"[<lvl>{level}</lvl>] "
@ -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}

View File

@ -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 <b>cancelled</b>"
)
logger.opt(colors=True).info(f"{matcher} running is <b>cancelled</b>")
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"<r><bg #f8bbd0>Running matcher {matcher} failed.</bg #f8bbd0></r>"
f"<r><bg #f8bbd0>Running {matcher} failed.</bg #f8bbd0></r>"
)
exception = e
@ -233,7 +231,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
```
"""
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:
log_msg += event.get_log_string()
except NoLogException:

View File

@ -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()}"

View File

@ -27,6 +27,7 @@
- `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>`
- `require` => {ref}``require` <nonebot.plugin.load.require>`
- `PluginMetadata` => {ref}``PluginMetadata` <nonebot.plugin.plugin.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"]:

View File

@ -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

View File

@ -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]:
"""返回所有独立插件名称。"""

View File

@ -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]:
"""注册一个基础事件响应器,可自定义类型。

View File

@ -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)

View File

@ -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

View File

@ -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

View File

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

View File

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