14 def __init__(self
, *typeids
):
15 for typeid
in typeids
:
16 if not callable(typeid
):
17 raise ValueError(typeid
)
18 self
.__typeids
= typeids
19 return super().__init
__()
22 yield from self
.__typeids
26 for typeid
in self
.__typeids
:
27 name
= typeid
.__qualname
__
28 module
= typeid
.__module
__
29 if module
not in ("builtins",):
30 name
= f
"{module}.{name}"
32 return f
"<{', '.join(names)}>"
34 def __call__(self
, call
):
35 class ConcreteHook(Hook
):
36 @functools.wraps(call
)
37 def __call__(self
, dispatcher
, node
, *args
, **kwargs
):
38 # We do not force specific arguments other than node.
39 # API users can introduce additional *args and **kwargs.
40 # However, in case they choose not to, this is fine too.
41 parameters
= tuple(inspect
.signature(call
).parameters
.values())
42 if len(parameters
) < 2:
43 raise TypeError(f
"{call.__name__}: missing required arguments")
44 if parameters
[0].kind
!= inspect
.Parameter
.POSITIONAL_OR_KEYWORD
:
45 raise TypeError(f
"{call.__name__}: incorrect self argument")
46 if parameters
[1].kind
!= inspect
.Parameter
.POSITIONAL_OR_KEYWORD
:
47 raise TypeError(f
"{call.__name__}: incorrect node argument")
49 kwargs_present
= False
50 for parameter
in parameters
:
52 inspect
.Parameter
.POSITIONAL_OR_KEYWORD
,
53 inspect
.Parameter
.VAR_POSITIONAL
,
56 inspect
.Parameter
.POSITIONAL_OR_KEYWORD
,
57 inspect
.Parameter
.VAR_KEYWORD
,
58 inspect
.Parameter
.KEYWORD_ONLY
,
60 if parameter
.kind
in positionals
:
62 elif parameter
.kind
in keywords
:
64 if args_present
and kwargs_present
:
65 return call(dispatcher
, node
, *args
, **kwargs
)
67 return call(dispatcher
, node
, *args
)
69 return call(dispatcher
, node
, **kwargs
)
71 return call(dispatcher
, node
)
73 return ConcreteHook(*tuple(self
))
76 class DispatcherMeta(type):
79 def __new__(metacls
, name
, bases
, ns
):
81 ishook
= lambda member
: isinstance(member
, Hook
)
83 for basecls
in reversed(bases
):
84 members
= inspect
.getmembers(basecls
, predicate
=ishook
)
85 for (_
, hook
) in members
:
86 hooks
.update(dict.fromkeys(hook
, hook
))
88 conflicts
= collections
.defaultdict(list)
89 for (key
, value
) in tuple(ns
.items()):
95 conflicts
[typeid
].append(key
)
98 for (typeid
, keys
) in conflicts
.items():
100 raise ValueError(f
"dispatch conflict: {keys!r}")
102 ns
["__hooks__"] = types
.MappingProxyType(hooks
)
104 return super().__new
__(metacls
, name
, bases
, ns
)
106 @functools.lru_cache(maxsize
=None)
107 def dispatch(cls
, typeid
=object):
108 hook
= cls
.__hooks
__.get(typeid
)
111 for (checker
, hook
) in cls
.__hooks
__.items():
112 if not isinstance(checker
, type) and checker(typeid
):
117 class Dispatcher(metaclass
=DispatcherMeta
):
118 def __call__(self
, node
, *args
, **kwargs
):
119 for typeid
in node
.__class
__.__mro
__:
120 hook
= self
.__class
__.dispatch(typeid
=typeid
)
124 hook
= self
.__class
__.dispatch()
125 return hook(self
, node
)
128 def dispatch_object(self
, node
):
129 raise NotImplementedError()