1+ from collections import defaultdict
2+ from collections import deque
3+ from typing import Callable
4+ from typing import Dict
5+ from typing import List
6+
17from .exceptions import AttrNotFound
2- from .exceptions import InvalidDefinition
38from .i18n import _
49from .utils import ensure_iterable
510
611
712class CallbackWrapper :
8- """A thin wrapper that ensures the target callback is a proper callable.
13+ def __init__ (
14+ self ,
15+ callback : Callable ,
16+ condition : Callable ,
17+ unique_key : str ,
18+ expected_value : "bool | None" = None ,
19+ ) -> None :
20+ self ._callback = callback
21+ self .condition = condition
22+ self .unique_key = unique_key
23+ self .expected_value = expected_value
24+
25+ def __repr__ (self ):
26+ return f"{ type (self ).__name__ } ({ self .unique_key } )"
27+
28+ def __call__ (self , * args , ** kwargs ):
29+ result = self ._callback (* args , ** kwargs )
30+ if self .expected_value is not None :
31+ return bool (result ) == self .expected_value
32+ return result
33+
34+
35+ class CallbackMeta :
36+ """A thin wrapper that register info about actions and guards.
937
1038 At first, `func` can be a string or a callable, and even if it's already
1139 a callable, his signature can mismatch.
@@ -14,12 +42,11 @@ class CallbackWrapper:
1442 call is performed, to allow the proper callback resolution.
1543 """
1644
17- def __init__ (self , func , suppress_errors = False , cond = None ):
45+ def __init__ (self , func , suppress_errors = False , cond = None , expected_value = None ):
1846 self .func = func
1947 self .suppress_errors = suppress_errors
20- self .cond = Callbacks (factory = ConditionWrapper ).add (cond )
21- self ._callback = None
22- self ._resolver_id = None
48+ self .cond = CallbackMetaList ().add (cond )
49+ self .expected_value = expected_value
2350
2451 def __repr__ (self ):
2552 return f"{ type (self ).__name__ } ({ self .func !r} )"
@@ -28,57 +55,67 @@ def __str__(self):
2855 return getattr (self .func , "__name__" , self .func )
2956
3057 def __eq__ (self , other ):
31- return self .func == other .func and self . _resolver_id == other . _resolver_id
58+ return self .func == other .func
3259
3360 def __hash__ (self ):
3461 return id (self )
3562
3663 def _update_func (self , func ):
3764 self .func = func
3865
39- def setup (self , resolver ):
66+ def build (self , resolver ) -> "CallbackWrapper | None" :
4067 """
4168 Resolves the `func` into a usable callable.
4269
4370 Args:
4471 resolver (callable): A method responsible to build and return a valid callable that
4572 can receive arbitrary parameters like `*args, **kwargs`.
4673 """
47- self .cond .setup (resolver )
48- try :
49- self ._resolver_id = getattr (resolver , "id" , id (resolver ))
50- self ._callback = resolver (self .func )
51- return True
52- except AttrNotFound :
53- if not self .suppress_errors :
54- raise
55- return False
74+ callback = resolver (self .func )
75+ if not callback .is_empty :
76+ conditions = CallbacksExecutor ()
77+ conditions .add (self .cond , resolver )
78+
79+ return CallbackWrapper (
80+ callback = callback ,
81+ condition = conditions .all ,
82+ unique_key = callback .unique_key ,
83+ expected_value = self .expected_value ,
84+ )
5685
57- def __call__ (self , * args , ** kwargs ):
58- if self ._callback is None :
59- raise InvalidDefinition (
60- _ ("Callback {!r} not property configured." ).format (self )
86+ if not self .suppress_errors :
87+ raise AttrNotFound (
88+ _ ("Did not found name '{}' from model or statemachine" ).format (
89+ self .func
90+ )
6191 )
62- return self ._callback (* args , ** kwargs )
92+ return None
93+
6394
95+ class BoolCallbackMeta (CallbackMeta ):
96+ """A thin wrapper that register info about actions and guards.
97+
98+ At first, `func` can be a string or a callable, and even if it's already
99+ a callable, his signature can mismatch.
100+
101+ After instantiation, `.setup(resolver)` must be called before any real
102+ call is performed, to allow the proper callback resolution.
103+ """
64104
65- class ConditionWrapper (CallbackWrapper ):
66- def __init__ (self , func , suppress_errors = False , expected_value = True ):
67- super ().__init__ (func , suppress_errors )
105+ def __init__ (self , func , suppress_errors = False , cond = None , expected_value = True ):
106+ self .func = func
107+ self .suppress_errors = suppress_errors
108+ self .cond = CallbackMetaList ().add (cond )
68109 self .expected_value = expected_value
69110
70111 def __str__ (self ):
71112 name = super ().__str__ ()
72113 return name if self .expected_value else f"!{ name } "
73114
74- def __call__ (self , * args , ** kwargs ):
75- return bool (super ().__call__ (* args , ** kwargs )) == self .expected_value
76-
77115
78- class Callbacks :
79- def __init__ (self , resolver = None , factory = CallbackWrapper ):
80- self .items = []
81- self ._resolver = resolver
116+ class CallbackMetaList :
117+ def __init__ (self , factory = CallbackMeta ):
118+ self .items : List [CallbackMeta ] = []
82119 self .factory = factory
83120
84121 def __repr__ (self ):
@@ -87,13 +124,6 @@ def __repr__(self):
87124 def __str__ (self ):
88125 return ", " .join (str (c ) for c in self )
89126
90- def setup (self , resolver ):
91- """Validate configurations"""
92- self ._resolver = resolver
93- self .items = [
94- callback for callback in self .items if callback .setup (self ._resolver )
95- ]
96-
97127 def _add_unbounded_callback (self , func , is_event = False , transitions = None , ** kwargs ):
98128 """This list was a target for adding a func using decorator
99129 `@<state|event>[.on|before|after|enter|exit]` syntax.
@@ -135,31 +165,19 @@ def __iter__(self):
135165 def clear (self ):
136166 self .items = []
137167
138- def call (self , * args , ** kwargs ):
139- return [
140- callback (* args , ** kwargs )
141- for callback in self .items
142- if callback .cond .all (* args , ** kwargs )
143- ]
144-
145- def all (self , * args , ** kwargs ):
146- return all (condition (* args , ** kwargs ) for condition in self )
147-
148- def _add (self , func , resolver = None , prepend = False , ** kwargs ):
149- resolver = resolver or self ._resolver
150-
151- callback = self .factory (func , ** kwargs )
152- if resolver is not None and not callback .setup (resolver ):
168+ def _add (self , func , registry = None , prepend = False , ** kwargs ):
169+ meta = self .factory (func , ** kwargs )
170+ if registry is not None and not registry (self , meta , prepend = prepend ):
153171 return
154172
155- if callback in self .items :
173+ if meta in self .items :
156174 return
157175
158176 if prepend :
159- self .items .insert (0 , callback )
177+ self .items .insert (0 , meta )
160178 else :
161- self .items .append (callback )
162- return callback
179+ self .items .append (meta )
180+ return meta
163181
164182 def add (self , callbacks , ** kwargs ):
165183 if callbacks is None :
@@ -170,3 +188,73 @@ def add(self, callbacks, **kwargs):
170188 self ._add (func , ** kwargs )
171189
172190 return self
191+
192+
193+ class CallbacksExecutor :
194+ def __init__ (self ):
195+ self .items : List [CallbackWrapper ] = deque ()
196+ self .items_already_seen = set ()
197+
198+ def __iter__ (self ):
199+ return iter (self .items )
200+
201+ def __repr__ (self ):
202+ return f"{ type (self ).__name__ } ({ self .items !r} )"
203+
204+ def add_one (
205+ self , callback_info : CallbackMeta , resolver : Callable , prepend : bool = False
206+ ) -> "CallbackWrapper | None" :
207+ callback = callback_info .build (resolver )
208+ if callback is None :
209+ return None
210+
211+ if callback .unique_key in self .items_already_seen :
212+ return None
213+
214+ self .items_already_seen .add (callback .unique_key )
215+ if prepend :
216+ self .items .insert (0 , callback )
217+ else :
218+ self .items .append (callback )
219+ return callback
220+
221+ def add (self , items : CallbackMetaList , resolver : Callable ):
222+ """Validate configurations"""
223+ for item in items :
224+ self .add_one (item , resolver )
225+ return self
226+
227+ def call (self , * args , ** kwargs ):
228+ return [
229+ callback (* args , ** kwargs )
230+ for callback in self
231+ if callback .condition (* args , ** kwargs )
232+ ]
233+
234+ def all (self , * args , ** kwargs ):
235+ return all (condition (* args , ** kwargs ) for condition in self )
236+
237+
238+ class CallbacksRegistry :
239+ def __init__ (self ) -> None :
240+ self ._registry : Dict [CallbackMetaList , CallbacksExecutor ] = defaultdict (
241+ CallbacksExecutor
242+ )
243+
244+ def register (self , callbacks : CallbackMetaList , resolver ):
245+ executor_list = self [callbacks ]
246+ executor_list .add (callbacks , resolver )
247+ return executor_list
248+
249+ def __getitem__ (self , callbacks : CallbackMetaList ) -> CallbacksExecutor :
250+ return self ._registry [callbacks ]
251+
252+ def build_register_function_for_resolver (self , resolver ):
253+ def register (
254+ meta_list : CallbackMetaList ,
255+ meta : CallbackMeta ,
256+ prepend : bool = False ,
257+ ):
258+ return self [meta_list ].add_one (meta , resolver , prepend = prepend )
259+
260+ return register
0 commit comments