all: construct hooks dynamically
[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 __hooks__ = {}
10
11 def __new__(metacls, name, bases, ns):
12 hooks = {}
13 ishook = lambda member: isinstance(member, _core.Hook)
14
15 for basecls in reversed(bases):
16 members = _inspect.getmembers(basecls, predicate=ishook)
17 for (_, hook) in members:
18 hooks.update(dict.fromkeys(hook, hook))
19
20 conflicts = _collections.defaultdict(list)
21 for (key, value) in tuple(ns.items()):
22 if not ishook(value):
23 continue
24 hook = value
25 for typeid in hook:
26 hooks[typeid] = hook
27 conflicts[typeid].append(key)
28 ns[key] = hook
29
30 for (typeid, keys) in conflicts.items():
31 if len(keys) > 1:
32 raise ValueError(f"dispatch conflict: {keys!r}")
33
34 ns["__hooks__"] = _types.MappingProxyType(hooks)
35
36 return super().__new__(metacls, name, bases, ns)
37
38 def dispatch(cls, typeid=object):
39 return cls.__hooks__.get(typeid)
40
41
42 class Dispatcher(metaclass=DispatcherMeta):
43 def __call__(self, instance):
44 for typeid in instance.__class__.__mro__:
45 hook = self.__class__.dispatch(typeid=typeid)
46 if hook is not None:
47 break
48 if hook is None:
49 hook = self.__class__.dispatch()
50 return hook(dispatcher=self, instance=instance)
51
52 @_core.hook(object)
53 def dispatch_object(self, instance):
54 raise NotImplementedError()