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): class Event(dict):

View File

@ -1,3 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class PausedException(Exception): class PausedException(Exception):
"""Block a message from further handling and try to receive a new message""" """Block a message from further handling and try to receive a new message"""
pass 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,198 +1,149 @@
import re #!/usr/bin/env python3
import copy # -*- coding: utf-8 -*-
from functools import wraps 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 .event import Event
from .typing import Scope, Handler from .typing import Handler
from .rule import Rule, startswith, regex, user from .rule import Rule, user
from .exception import PausedException, RejectedException, FinishedException from .exception import PausedException, RejectedException, FinishedException
matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list)
class Matcher: class Matcher:
rule: Rule = Rule() rule: Rule = Rule()
scope: Scope = "ALL" handlers: List[Handler] = []
permission: str = "ALL"
block: bool = True
handlers: list = []
temp: bool = False temp: bool = False
priority: int = 1
_default_state: dict = {} _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): def __init__(self):
self.handlers = self.handlers.copy() self.handlers = self.handlers.copy()
self.state = self._default_state.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 @classmethod
def new( def new(cls,
cls,
rule: Rule = Rule(), rule: Rule = Rule(),
scope: Scope = "ALL",
permission: str = "ALL",
block: bool = True,
handlers: list = [], handlers: list = [],
temp: bool = False, temp: bool = False,
priority: int = 1,
*, *,
default_state: dict = {}, default_state: dict = {}) -> Type["Matcher"]:
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
NewMatcher = type( NewMatcher = type(
"Matcher", (Matcher,), { "Matcher", (Matcher,), {
"rule": rule, "rule": rule,
"scope": scope,
"permission": permission,
"block": block,
"handlers": handlers, "handlers": handlers,
"temp": temp, "temp": temp,
"_default_state": default_state, "priority": priority,
"_default_parser": default_parser, "_default_state": default_state
"_args_parser": args_parser,
}) })
matchers[priority].append(NewMatcher)
return NewMatcher return NewMatcher
# @classmethod
# def args_parser(cls, func: Callable[[Event, dict], None]):
# cls._default_parser = func
# return func
@classmethod @classmethod
def args_parser(cls, func: Callable[[Event, dict], None]): def handle(cls):
cls._default_parser = func
def _decorator(func: Handler) -> Handler:
cls.handlers.append(func)
return func return func
return _decorator
@classmethod @classmethod
def receive(cls): def receive(cls):
def _decorator(func: Handler) -> Handler: def _decorator(func: Handler) -> Handler:
@wraps(func) @wraps(func)
def _handler(event: Event, state: dict): async def _handler(bot, event: Event, state: dict):
raise PausedException raise PausedException
cls.handlers.append(_handler) cls.handlers.append(_handler)
cls.handlers.append(func)
return func return func
return _decorator return _decorator
@classmethod # @classmethod
def got(cls, # def got(cls,
key: str, # key: str,
prompt: Optional[str] = None, # prompt: Optional[str] = None,
args_parser: Optional[Callable[[Event, dict], None]] = None): # args_parser: Optional[Callable[[Event, dict], None]] = None):
def _decorator(func: Handler) -> Handler: # def _decorator(func: Handler) -> Handler:
@wraps(func) # @wraps(func)
def _handler(event: Event, state: dict): # def _handler(event: Event, state: dict):
if key not in state: # if key not in state:
if state.get("__current_arg__", None) == key: # if state.get("__current_arg__", None) == key:
state[key] = event.message # state[key] = event.message
del state["__current_arg__"] # del state["__current_arg__"]
return func(event, state) # return func(event, state)
state["__current_arg__"] = key # state["__current_arg__"] = key
cls._args_parser = args_parser # cls._args_parser = args_parser
raise RejectedException # 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 # @classmethod
def finish(cls, prompt: Optional[str] = None): # def finish(cls, prompt: Optional[str] = None):
raise FinishedException # raise FinishedException
@classmethod # @classmethod
def reject(cls, prompt: Optional[str] = None): # def reject(cls, prompt: Optional[str] = None):
raise RejectedException # raise RejectedException
async def run(self, event): async def run(self, bot, event):
if not self.rule(event): if not self.rule(event):
return return
try: try:
if self.parser: # if self.parser:
await self.parser(event, state) # type: ignore # await self.parser(event, state) # type: ignore
for _ in range(len(self.handlers)): for _ in range(len(self.handlers)):
handler = self.handlers.pop(0) handler = self.handlers.pop(0)
await handler(event, self.state) await handler(bot, event, self.state)
except RejectedException: except RejectedException:
# TODO: add tmp matcher to matcher tree self.handlers.insert(0, handler) # type: ignore
self.handlers.insert(handler, 0) matcher = Matcher.new(user(event.user_id) & self.rule,
matcher = Matcher.new(self.rule,
self.scope,
self.permission,
self.block,
self.handlers, self.handlers,
temp=True, temp=True,
default_state=self.state, priority=0,
default_parser=self._default_parser, default_state=self.state)
args_parser=self._args_parser) matchers[0].append(matcher)
return return
except PausedException: except PausedException:
# TODO: add tmp matcher to matcher tree matcher = Matcher.new(user(event.user_id) & self.rule,
matcher = Matcher.new(self.rule,
self.scope,
self.permission,
self.block,
self.handlers, self.handlers,
temp=True, temp=True,
default_state=self.state, priority=0,
default_parser=self._default_parser, default_state=self.state)
args_parser=self._args_parser) matchers[0].append(matcher)
return return
except FinishedException: except FinishedException:
return 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 import re
from typing import Union, Callable, Optional 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"] from typing import Callable, Awaitable
Handler = Callable[["Event", dict], None]
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)