plugin load

This commit is contained in:
yanyongyu 2020-06-30 10:13:58 +08:00
parent 16099b2c35
commit 7529559d24
10 changed files with 244 additions and 132 deletions

9
nonebot/__init__.py Normal file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
from .log import logger
logger.setLevel(level=logging.DEBUG)
from .plugin import load_plugins, get_loaded_plugins

View File

@ -1,4 +1,7 @@
from typing import Dict, Any, Optional
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any, Dict, Optional
class Event(dict):

View File

@ -1,3 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class PausedException(Exception):
"""Block a message from further handling and try to receive a new message"""
pass

11
nonebot/log.py Normal file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import logging
logger = logging.getLogger("nonebot")
default_handler = logging.StreamHandler(sys.stdout)
default_handler.setFormatter(
logging.Formatter("[%(asctime)s %(name)s] %(levelname)s: %(message)s"))
logger.addHandler(default_handler)

View File

@ -1,76 +1,70 @@
import re
import copy
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from functools import wraps
from typing import Type, Union, Optional, Callable
from collections import defaultdict
from typing import Type, List, Dict, Optional, Callable
from .event import Event
from .typing import Scope, Handler
from .rule import Rule, startswith, regex, user
from .typing import Handler
from .rule import Rule, user
from .exception import PausedException, RejectedException, FinishedException
matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list)
class Matcher:
rule: Rule = Rule()
scope: Scope = "ALL"
permission: str = "ALL"
block: bool = True
handlers: list = []
handlers: List[Handler] = []
temp: bool = False
priority: int = 1
_default_state: dict = {}
_default_parser: Optional[Callable[[Event, dict], None]] = None
_args_parser: Optional[Callable[[Event, dict], None]] = None
# _default_parser: Optional[Callable[[Event, dict], None]] = None
# _args_parser: Optional[Callable[[Event, dict], None]] = None
def __init__(self):
self.handlers = self.handlers.copy()
self.state = self._default_state.copy()
self.parser = self._args_parser or self._default_parser
# self.parser = self._args_parser or self._default_parser
@classmethod
def new(
cls,
rule: Rule = Rule(),
scope: Scope = "ALL",
permission: str = "ALL",
block: bool = True,
handlers: list = [],
temp: bool = False,
*,
default_state: dict = {},
default_parser: Optional[Callable[[Event, dict], None]] = None,
args_parser: Optional[Callable[[Event, dict], None]] = None
) -> Type["Matcher"]:
# class NewMatcher(cls):
# rule: Rule = rule
# scope: Scope = scope
# permission: str = permission
# block: bool = block
# handlers: list = handlers
# temp: bool = temp
# _default_state = default_state
def new(cls,
rule: Rule = Rule(),
handlers: list = [],
temp: bool = False,
priority: int = 1,
*,
default_state: dict = {}) -> Type["Matcher"]:
NewMatcher = type(
"Matcher", (Matcher,), {
"rule": rule,
"scope": scope,
"permission": permission,
"block": block,
"handlers": handlers,
"temp": temp,
"_default_state": default_state,
"_default_parser": default_parser,
"_args_parser": args_parser,
"priority": priority,
"_default_state": default_state
})
matchers[priority].append(NewMatcher)
return NewMatcher
# @classmethod
# def args_parser(cls, func: Callable[[Event, dict], None]):
# cls._default_parser = func
# return func
@classmethod
def args_parser(cls, func: Callable[[Event, dict], None]):
cls._default_parser = func
return func
def handle(cls):
def _decorator(func: Handler) -> Handler:
cls.handlers.append(func)
return func
return _decorator
@classmethod
def receive(cls):
@ -78,121 +72,78 @@ class Matcher:
def _decorator(func: Handler) -> Handler:
@wraps(func)
def _handler(event: Event, state: dict):
async def _handler(bot, event: Event, state: dict):
raise PausedException
cls.handlers.append(_handler)
cls.handlers.append(func)
return func
return _decorator
@classmethod
def got(cls,
key: str,
prompt: Optional[str] = None,
args_parser: Optional[Callable[[Event, dict], None]] = None):
# @classmethod
# def got(cls,
# key: str,
# prompt: Optional[str] = None,
# args_parser: Optional[Callable[[Event, dict], None]] = None):
def _decorator(func: Handler) -> Handler:
# def _decorator(func: Handler) -> Handler:
@wraps(func)
def _handler(event: Event, state: dict):
if key not in state:
if state.get("__current_arg__", None) == key:
state[key] = event.message
del state["__current_arg__"]
return func(event, state)
state["__current_arg__"] = key
cls._args_parser = args_parser
raise RejectedException
# @wraps(func)
# def _handler(event: Event, state: dict):
# if key not in state:
# if state.get("__current_arg__", None) == key:
# state[key] = event.message
# del state["__current_arg__"]
# return func(event, state)
# state["__current_arg__"] = key
# cls._args_parser = args_parser
# raise RejectedException
return func(event, state)
# return func(event, state)
cls.handlers.append(_handler)
# cls.handlers.append(_handler)
return func
# return func
return _decorator
# return _decorator
@classmethod
def finish(cls, prompt: Optional[str] = None):
raise FinishedException
# @classmethod
# def finish(cls, prompt: Optional[str] = None):
# raise FinishedException
@classmethod
def reject(cls, prompt: Optional[str] = None):
raise RejectedException
# @classmethod
# def reject(cls, prompt: Optional[str] = None):
# raise RejectedException
async def run(self, event):
async def run(self, bot, event):
if not self.rule(event):
return
try:
if self.parser:
await self.parser(event, state) # type: ignore
# if self.parser:
# await self.parser(event, state) # type: ignore
for _ in range(len(self.handlers)):
handler = self.handlers.pop(0)
await handler(event, self.state)
await handler(bot, event, self.state)
except RejectedException:
# TODO: add tmp matcher to matcher tree
self.handlers.insert(handler, 0)
matcher = Matcher.new(self.rule,
self.scope,
self.permission,
self.block,
self.handlers.insert(0, handler) # type: ignore
matcher = Matcher.new(user(event.user_id) & self.rule,
self.handlers,
temp=True,
default_state=self.state,
default_parser=self._default_parser,
args_parser=self._args_parser)
priority=0,
default_state=self.state)
matchers[0].append(matcher)
return
except PausedException:
# TODO: add tmp matcher to matcher tree
matcher = Matcher.new(self.rule,
self.scope,
self.permission,
self.block,
matcher = Matcher.new(user(event.user_id) & self.rule,
self.handlers,
temp=True,
default_state=self.state,
default_parser=self._default_parser,
args_parser=self._args_parser)
priority=0,
default_state=self.state)
matchers[0].append(matcher)
return
except FinishedException:
return
def on_message(rule: Rule,
scope="ALL",
permission="ALL",
block=True,
*,
handlers=[],
temp=False,
state={}) -> Type[Matcher]:
# TODO: add matcher to matcher tree
return Matcher.new(rule,
scope,
permission,
block,
handlers=handlers,
temp=temp,
default_state=state)
# def on_startswith(msg,
# start: int = None,
# end: int = None,
# rule: Optional[Rule] = None,
# **kwargs) -> Type[Matcher]:
# return on_message(startswith(msg, start, end) &
# rule, **kwargs) if rule else on_message(
# startswith(msg, start, end), **kwargs)
# def on_regex(pattern,
# flags: Union[int, re.RegexFlag] = 0,
# rule: Optional[Rule] = None,
# **kwargs) -> Type[Matcher]:
# return on_message(regex(pattern, flags) &
# rule, **kwargs) if rule else on_message(
# regex(pattern, flags), **kwargs)

99
nonebot/plugin.py Normal file
View File

@ -0,0 +1,99 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import re
import importlib
from types import ModuleType
from typing import Set, Dict, Type, Optional
from .log import logger
from .rule import Rule
from .matcher import Matcher
plugins: Dict[str, "Plugin"] = {}
_tmp_matchers: Set[Type[Matcher]] = set()
class Plugin(object):
# TODO: store plugin informations
def __init__(self, module_path: str, module: ModuleType,
matchers: Set[Type[Matcher]]):
self.module_path = module_path
self.module = module
self.matchers = matchers
def on_message(rule: Rule,
*,
handlers=[],
temp=False,
priority: int = 1,
state={}) -> Type[Matcher]:
matcher = Matcher.new(rule,
temp=temp,
priority=priority,
handlers=handlers,
default_state=state)
_tmp_matchers.add(matcher)
return matcher
# def on_startswith(msg,
# start: int = None,
# end: int = None,
# rule: Optional[Rule] = None,
# **kwargs) -> Type[Matcher]:
# return on_message(startswith(msg, start, end) &
# rule, **kwargs) if rule else on_message(
# startswith(msg, start, end), **kwargs)
# def on_regex(pattern,
# flags: Union[int, re.RegexFlag] = 0,
# rule: Optional[Rule] = None,
# **kwargs) -> Type[Matcher]:
# return on_message(regex(pattern, flags) &
# rule, **kwargs) if rule else on_message(
# regex(pattern, flags), **kwargs)
def load_plugin(module_path: str) -> Optional[Plugin]:
try:
_tmp_matchers.clear()
module = importlib.import_module(module_path)
plugin = Plugin(module_path, module, _tmp_matchers.copy())
plugins[module_path] = plugin
logger.info(f"Succeeded to import \"{module_path}\"")
return plugin
except Exception as e:
logger.error(f"Failed to import \"{module_path}\", error: {e}")
logger.exception(e)
return None
def load_plugins(plugin_dir: str) -> Set[Plugin]:
plugins = set()
for name in os.listdir(plugin_dir):
path = os.path.join(plugin_dir, name)
if os.path.isfile(path) and \
(name.startswith("_") or not name.endswith(".py")):
continue
if os.path.isdir(path) and \
(name.startswith("_") or not os.path.exists(
os.path.join(path, "__init__.py"))):
continue
m = re.match(r"([_A-Z0-9a-z]+)(.py)?", name)
if not m:
continue
result = load_plugin(f"{plugin_dir.replace(os.sep, '.')}.{m.group(1)}")
if result:
plugins.add(result)
return plugins
def get_loaded_plugins() -> Set[Plugin]:
return set(plugins.values())

View File

@ -1,3 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import re
from typing import Union, Callable, Optional

View File

@ -1,4 +1,6 @@
from typing import Literal, Callable
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
Scope = Literal["PRIVATE", "DISCUSS", "GROUP", "ALL"]
Handler = Callable[["Event", dict], None]
from typing import Callable, Awaitable
Handler = Callable[["Bot", "Event", dict], Awaitable[None]]

17
tests/bot.py Normal file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
import nonebot
from nonebot.matcher import matchers
if __name__ == "__main__":
nonebot.load_plugins(os.path.join(os.path.dirname(__file__),
"test_plugins"))
print(nonebot.get_loaded_plugins())
print(matchers)
print(matchers[1][0].handlers)

View File

@ -0,0 +1,13 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from nonebot.rule import Rule
from nonebot.event import Event
from nonebot.plugin import on_message
test_matcher = on_message(Rule(), state={"default": 1})
@test_matcher.handle()
async def test_handler(bot, event: Event, state: dict):
print(state)