94067636b5faf11e798aa6b70869a8c96eae9ce2
[mdis.git] / src / mdis / dispatcher.py
1 import collections as _collections
2 import inspect as _inspect
3 import types as _types
4
5 from . import core as _core
6
7
8 class DispatcherMeta(type):
9 def __new__(metacls, name, bases, ns):
10 hooks = {}
11 ishook = lambda member: isinstance(member, _core.CallHook)
12
13 for basecls in reversed(bases):
14 members = _inspect.getmembers(basecls, predicate=ishook)
15 for (_, hook) in members:
16 hooks.update(dict.fromkeys(hook, hook))
17
18 conflicts = _collections.defaultdict(list)
19 for (key, value) in tuple(ns.items()):
20 if not ishook(value):
21 continue
22 hook = value
23 for typeid in hook:
24 hooks[typeid] = hook
25 conflicts[typeid].append(key)
26 ns[key] = hook
27
28 for (typeid, keys) in conflicts.items():
29 if len(keys) > 1:
30 raise ValueError(f"dispatch conflict: {keys!r}")
31
32 ns["__hooks__"] = _types.MappingProxyType(hooks)
33
34 return super().__new__(metacls, name, bases, ns)
35
36 def dispatch(cls, typeid=object):
37 return cls.__hooks__.get(typeid)
38
39
40 class Dispatcher(metaclass=DispatcherMeta):
41 def __call__(self, instance):
42 for typeid in instance.__class__.__mro__:
43 hook = self.__class__.dispatch(typeid=typeid)
44 if hook is not None:
45 break
46 if hook is None:
47 hook = self.__class__.dispatch()
48 return hook(dispatcher=self, instance=instance)
49
50 @_core.hook(object)
51 def dispatch_object(self, instance):
52 raise NotImplementedError()