mirror of
https://github.com/kristoferssolo/School.git
synced 2025-10-21 20:10:38 +00:00
139 lines
4.0 KiB
Python
139 lines
4.0 KiB
Python
import inspect
|
|
from contextvars import ContextVar
|
|
from dataclasses import dataclass
|
|
from typing import Optional, Iterable, List
|
|
|
|
ctx_data = ContextVar('ctx_handler_data')
|
|
current_handler = ContextVar('current_handler')
|
|
|
|
|
|
@dataclass
|
|
class FilterObj:
|
|
filter: callable
|
|
kwargs: dict
|
|
is_async: bool
|
|
|
|
|
|
class SkipHandler(Exception):
|
|
pass
|
|
|
|
|
|
class CancelHandler(Exception):
|
|
pass
|
|
|
|
|
|
def _get_spec(func: callable):
|
|
while hasattr(func, '__wrapped__'): # Try to resolve decorated callbacks
|
|
func = func.__wrapped__
|
|
return inspect.getfullargspec(func)
|
|
|
|
|
|
def _check_spec(spec: inspect.FullArgSpec, kwargs: dict):
|
|
if spec.varkw:
|
|
return kwargs
|
|
|
|
return {k: v for k, v in kwargs.items() if k in set(spec.args + spec.kwonlyargs)}
|
|
|
|
|
|
class Handler:
|
|
def __init__(self, dispatcher, once=True, middleware_key=None):
|
|
self.dispatcher = dispatcher
|
|
self.once = once
|
|
|
|
self.handlers: List[Handler.HandlerObj] = []
|
|
self.middleware_key = middleware_key
|
|
|
|
def register(self, handler, filters=None, index=None):
|
|
"""
|
|
Register callback
|
|
|
|
Filters can be awaitable or not.
|
|
|
|
:param handler: coroutine
|
|
:param filters: list of filters
|
|
:param index: you can reorder handlers
|
|
"""
|
|
from .filters import get_filters_spec
|
|
|
|
spec = _get_spec(handler)
|
|
|
|
if filters and not isinstance(filters, (list, tuple, set)):
|
|
filters = [filters]
|
|
filters = get_filters_spec(self.dispatcher, filters)
|
|
|
|
record = Handler.HandlerObj(handler=handler, spec=spec, filters=filters)
|
|
if index is None:
|
|
self.handlers.append(record)
|
|
else:
|
|
self.handlers.insert(index, record)
|
|
|
|
def unregister(self, handler):
|
|
"""
|
|
Remove handler
|
|
|
|
:param handler: callback
|
|
:return:
|
|
"""
|
|
for handler_obj in self.handlers:
|
|
registered = handler_obj.handler
|
|
if handler is registered:
|
|
self.handlers.remove(handler_obj)
|
|
return True
|
|
raise ValueError('This handler is not registered!')
|
|
|
|
async def notify(self, *args):
|
|
"""
|
|
Notify handlers
|
|
|
|
:param args:
|
|
:return:
|
|
"""
|
|
from .filters import check_filters, FilterNotPassed
|
|
|
|
results = []
|
|
|
|
data = {}
|
|
ctx_data.set(data)
|
|
|
|
if self.middleware_key:
|
|
try:
|
|
await self.dispatcher.middleware.trigger(f"pre_process_{self.middleware_key}", args + (data,))
|
|
except CancelHandler: # Allow to cancel current event
|
|
return results
|
|
|
|
try:
|
|
for handler_obj in self.handlers:
|
|
try:
|
|
data.update(await check_filters(handler_obj.filters, args))
|
|
except FilterNotPassed:
|
|
continue
|
|
else:
|
|
ctx_token = current_handler.set(handler_obj.handler)
|
|
try:
|
|
if self.middleware_key:
|
|
await self.dispatcher.middleware.trigger(f"process_{self.middleware_key}", args + (data,))
|
|
partial_data = _check_spec(handler_obj.spec, data)
|
|
response = await handler_obj.handler(*args, **partial_data)
|
|
if response is not None:
|
|
results.append(response)
|
|
if self.once:
|
|
break
|
|
except SkipHandler:
|
|
continue
|
|
except CancelHandler:
|
|
break
|
|
finally:
|
|
current_handler.reset(ctx_token)
|
|
finally:
|
|
if self.middleware_key:
|
|
await self.dispatcher.middleware.trigger(f"post_process_{self.middleware_key}",
|
|
args + (results, data,))
|
|
|
|
return results
|
|
|
|
@dataclass
|
|
class HandlerObj:
|
|
handler: callable
|
|
spec: inspect.FullArgSpec
|
|
filters: Optional[Iterable[FilterObj]] = None
|