36
36
if TYPE_CHECKING :
37
37
from collections .abc import Callable
38
38
from collections .abc import Generator
39
+ from collections .abc import Iterable
39
40
from collections .abc import Sequence
40
41
from typing import Any
41
42
47
48
CharField = models .CharField [str , str ]
48
49
IntegerField = models .IntegerField [int , int ]
49
50
ForeignKey = models .ForeignKey [Any , Any ]
51
+
52
+ _Instance = models .Model # TODO: use real type
53
+ _ToDo = Any # TODO: use real type
50
54
else :
51
55
_Model = object
52
56
_Field = object
@@ -79,12 +83,12 @@ class ConcurrentTransition(Exception):
79
83
class Transition :
80
84
def __init__ (
81
85
self ,
82
- method : Callable [..., Any ],
86
+ method : Callable [..., str | int | None ],
83
87
source : str | int | Sequence [str | int ] | State ,
84
- target : str | int | State | None ,
88
+ target : str | int ,
85
89
on_error : str | int | None ,
86
- conditions : list [Callable [[Any ], bool ]],
87
- permission : str | Callable [[models . Model , UserWithPermissions ], bool ] | None ,
90
+ conditions : list [Callable [[_Instance ], bool ]],
91
+ permission : str | Callable [[_Instance , UserWithPermissions ], bool ] | None ,
88
92
custom : dict [str , _StrOrPromise ],
89
93
) -> None :
90
94
self .method = method
@@ -99,7 +103,7 @@ def __init__(
99
103
def name (self ) -> str :
100
104
return self .method .__name__
101
105
102
- def has_perm (self , instance , user : UserWithPermissions ) -> bool :
106
+ def has_perm (self , instance : _Instance , user : UserWithPermissions ) -> bool :
103
107
if not self .permission :
104
108
return True
105
109
if callable (self .permission ):
@@ -122,7 +126,7 @@ def __eq__(self, other):
122
126
return False
123
127
124
128
125
- def get_available_FIELD_transitions (instance , field : FSMFieldMixin ) -> Generator [Transition , None , None ]:
129
+ def get_available_FIELD_transitions (instance : _Instance , field : FSMFieldMixin ) -> Generator [Transition , None , None ]:
126
130
"""
127
131
List of transitions available in current model state
128
132
with all conditions met
@@ -136,15 +140,15 @@ def get_available_FIELD_transitions(instance, field: FSMFieldMixin) -> Generator
136
140
yield meta .get_transition (curr_state )
137
141
138
142
139
- def get_all_FIELD_transitions (instance , field : FSMFieldMixin ) -> Generator [Transition , None , None ]:
143
+ def get_all_FIELD_transitions (instance : _Instance , field : FSMFieldMixin ) -> Generator [Transition , None , None ]:
140
144
"""
141
145
List of all transitions available in current model state
142
146
"""
143
147
return field .get_all_transitions (instance .__class__ )
144
148
145
149
146
150
def get_available_user_FIELD_transitions (
147
- instance , user : UserWithPermissions , field : FSMFieldMixin
151
+ instance : _Instance , user : UserWithPermissions , field : FSMFieldMixin
148
152
) -> Generator [Transition , None , None ]:
149
153
"""
150
154
List of transitions available in current model state
@@ -160,11 +164,11 @@ class FSMMeta:
160
164
Models methods transitions meta information
161
165
"""
162
166
163
- def __init__ (self , field , method ) -> None :
167
+ def __init__ (self , field : FSMFieldMixin , method : Any ) -> None :
164
168
self .field = field
165
- self .transitions : dict [str , Any ] = {} # source -> Transition
169
+ self .transitions : dict [str , Transition ] = {} # source -> Transition
166
170
167
- def get_transition (self , source : str ):
171
+ def get_transition (self , source : str ) -> Transition | None :
168
172
transition = self .transitions .get (source , None )
169
173
if transition is None :
170
174
transition = self .transitions .get ("*" , None )
@@ -174,12 +178,12 @@ def get_transition(self, source: str):
174
178
175
179
def add_transition (
176
180
self ,
177
- method : Callable [..., Any ],
181
+ method : Callable [..., str | int | None ],
178
182
source : str ,
179
183
target : str | int ,
180
184
on_error : str | int | None = None ,
181
- conditions : list [Callable [[Any ], bool ]] = [],
182
- permission : str | Callable [[models . Model , UserWithPermissions ], bool ] | None = None ,
185
+ conditions : list [Callable [[_Instance ], bool ]] = [],
186
+ permission : str | Callable [[_Instance , UserWithPermissions ], bool ] | None = None ,
183
187
custom : dict [str , _StrOrPromise ] = {},
184
188
) -> None :
185
189
if source in self .transitions :
@@ -195,7 +199,7 @@ def add_transition(
195
199
custom = custom ,
196
200
)
197
201
198
- def has_transition (self , state ) -> bool :
202
+ def has_transition (self , state : str ) -> bool :
199
203
"""
200
204
Lookup if any transition exists from current model state using current method
201
205
"""
@@ -210,7 +214,7 @@ def has_transition(self, state) -> bool:
210
214
211
215
return False
212
216
213
- def conditions_met (self , instance , state ) -> bool :
217
+ def conditions_met (self , instance : _Instance , state : str ) -> bool :
214
218
"""
215
219
Check if all conditions have been met
216
220
"""
@@ -224,23 +228,23 @@ def conditions_met(self, instance, state) -> bool:
224
228
225
229
return all (condition (instance ) for condition in transition .conditions )
226
230
227
- def has_transition_perm (self , instance , state , user : UserWithPermissions ) -> bool :
231
+ def has_transition_perm (self , instance : _Instance , state : str , user : UserWithPermissions ) -> bool :
228
232
transition = self .get_transition (state )
229
233
230
234
if not transition :
231
235
return False
232
236
else :
233
237
return bool (transition .has_perm (instance , user ))
234
238
235
- def next_state (self , current_state ) :
239
+ def next_state (self , current_state : str ) -> str | int :
236
240
transition = self .get_transition (current_state )
237
241
238
242
if transition is None :
239
243
raise TransitionNotAllowed (f"No transition from { current_state } " )
240
244
241
245
return transition .target
242
246
243
- def exception_state (self , current_state ) :
247
+ def exception_state (self , current_state : str ) -> str | int | None :
244
248
transition = self .get_transition (current_state )
245
249
246
250
if transition is None :
@@ -250,15 +254,15 @@ def exception_state(self, current_state):
250
254
251
255
252
256
class FSMFieldDescriptor :
253
- def __init__ (self , field ) -> None :
257
+ def __init__ (self , field : FSMFieldMixin ) -> None :
254
258
self .field = field
255
259
256
- def __get__ (self , instance , type = None ):
260
+ def __get__ (self , instance : _Instance , type : Any | None = None ) -> Any :
257
261
if instance is None :
258
262
return self
259
263
return self .field .get_state (instance )
260
264
261
- def __set__ (self , instance , value ) -> None :
265
+ def __set__ (self , instance : _Instance , value : Any ) -> None :
262
266
if self .field .protected and self .field .name in instance .__dict__ :
263
267
raise AttributeError (f"Direct { self .field .name } modification is not allowed" )
264
268
@@ -272,7 +276,7 @@ class FSMFieldMixin(_Field):
272
276
273
277
def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
274
278
self .protected = kwargs .pop ("protected" , False )
275
- self .transitions : dict [Any , dict [str , Any ]] = {} # cls -> (transitions name -> method)
279
+ self .transitions : dict [type [ _Model ] , dict [str , Any ]] = {} # cls -> (transitions name -> method)
276
280
self .state_proxy = {} # state -> ProxyClsRef
277
281
278
282
state_choices = kwargs .pop ("state_choices" , None )
@@ -289,21 +293,21 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
289
293
290
294
super ().__init__ (* args , ** kwargs )
291
295
292
- def deconstruct (self ):
296
+ def deconstruct (self ) -> Any :
293
297
name , path , args , kwargs = super ().deconstruct ()
294
298
if self .protected :
295
299
kwargs ["protected" ] = self .protected
296
300
return name , path , args , kwargs
297
301
298
- def get_state (self , instance ) -> Any :
302
+ def get_state (self , instance : _Instance ) -> Any :
299
303
# The state field may be deferred. We delegate the logic of figuring this out
300
304
# and loading the deferred field on-demand to Django's built-in DeferredAttribute class.
301
305
return DeferredAttribute (self ).__get__ (instance ) # type: ignore[attr-defined]
302
306
303
- def set_state (self , instance , state : str ) -> None :
307
+ def set_state (self , instance : _Instance , state : str ) -> None :
304
308
instance .__dict__ [self .name ] = state
305
309
306
- def set_proxy (self , instance , state : str ) -> None :
310
+ def set_proxy (self , instance : _Instance , state : str ) -> None :
307
311
"""
308
312
Change class
309
313
"""
@@ -324,7 +328,7 @@ def set_proxy(self, instance, state: str) -> None:
324
328
325
329
instance .__class__ = model
326
330
327
- def change_state (self , instance , method , * args : Any , ** kwargs : Any ):
331
+ def change_state (self , instance : _Instance , method : _ToDo , * args : Any , ** kwargs : Any ) -> Any :
328
332
meta = method ._django_fsm
329
333
method_name = method .__name__
330
334
current_state = self .get_state (instance )
@@ -377,7 +381,7 @@ def change_state(self, instance, method, *args: Any, **kwargs: Any):
377
381
378
382
return result
379
383
380
- def get_all_transitions (self , instance_cls ) -> Generator [Transition , None , None ]:
384
+ def get_all_transitions (self , instance_cls : type [ _Model ] ) -> Generator [Transition , None , None ]:
381
385
"""
382
386
Returns [(source, target, name, method)] for all field transitions
383
387
"""
@@ -389,7 +393,7 @@ def get_all_transitions(self, instance_cls) -> Generator[Transition, None, None]
389
393
for transition in meta .transitions .values ():
390
394
yield transition
391
395
392
- def contribute_to_class (self , cls , name , private_only = False , ** kwargs ) :
396
+ def contribute_to_class (self , cls : type [ _Model ] , name : str , private_only : bool = False , ** kwargs : Any ) -> None :
393
397
self .base_cls = cls
394
398
395
399
super ().contribute_to_class (cls , name , private_only = private_only , ** kwargs )
@@ -404,7 +408,7 @@ def contribute_to_class(self, cls, name, private_only=False, **kwargs):
404
408
405
409
class_prepared .connect (self ._collect_transitions )
406
410
407
- def _collect_transitions (self , * args : Any , ** kwargs : Any ):
411
+ def _collect_transitions (self , * args : Any , ** kwargs : Any ) -> None :
408
412
sender = kwargs ["sender" ]
409
413
410
414
if not issubclass (sender , self .base_cls ):
@@ -456,10 +460,10 @@ class FSMKeyField(FSMFieldMixin, ForeignKey):
456
460
State Machine support for Django model
457
461
"""
458
462
459
- def get_state (self , instance ) :
463
+ def get_state (self , instance : _Instance ) -> _ToDo :
460
464
return instance .__dict__ [self .attname ]
461
465
462
- def set_state (self , instance , state ) :
466
+ def set_state (self , instance : _Instance , state : str ) -> None :
463
467
instance .__dict__ [self .attname ] = self .to_python (state )
464
468
465
469
@@ -521,7 +525,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
521
525
self ._update_initial_state ()
522
526
523
527
@property
524
- def state_fields (self ):
528
+ def state_fields (self ) -> Iterable [ Any ] :
525
529
return filter (lambda field : isinstance (field , FSMFieldMixin ), self ._meta .fields )
526
530
527
531
def _do_update (self , base_qs , using , pk_val , values , update_fields , forced_update ):
@@ -567,14 +571,14 @@ def save(self, *args: Any, **kwargs: Any) -> None:
567
571
568
572
569
573
def transition (
570
- field ,
574
+ field : FSMFieldMixin ,
571
575
source : str | int | Sequence [str | int ] | State = "*" ,
572
576
target : str | int | State | None = None ,
573
577
on_error : str | int | None = None ,
574
578
conditions : list [Callable [[Any ], bool ]] = [],
575
579
permission : str | Callable [[models .Model , UserWithPermissions ], bool ] | None = None ,
576
580
custom : dict [str , _StrOrPromise ] = {},
577
- ):
581
+ ) -> _ToDo :
578
582
"""
579
583
Method decorator to mark allowed transitions.
580
584
@@ -596,7 +600,7 @@ def inner_transition(func):
596
600
func ._django_fsm .add_transition (func , source , target , on_error , conditions , permission , custom )
597
601
598
602
@wraps (func )
599
- def _change_state (instance , * args : Any , ** kwargs : Any ):
603
+ def _change_state (instance : _Instance , * args : Any , ** kwargs : Any ) -> _ToDo :
600
604
return fsm_meta .field .change_state (instance , func , * args , ** kwargs )
601
605
602
606
if not wrapper_installed :
@@ -607,7 +611,7 @@ def _change_state(instance, *args: Any, **kwargs: Any):
607
611
return inner_transition
608
612
609
613
610
- def can_proceed (bound_method , check_conditions : bool = True ) -> bool :
614
+ def can_proceed (bound_method : _ToDo , check_conditions : bool = True ) -> bool :
611
615
"""
612
616
Returns True if model in state allows to call bound_method
613
617
@@ -624,7 +628,7 @@ def can_proceed(bound_method, check_conditions: bool = True) -> bool:
624
628
return meta .has_transition (current_state ) and (not check_conditions or meta .conditions_met (self , current_state ))
625
629
626
630
627
- def has_transition_perm (bound_method , user : UserWithPermissions ) -> bool :
631
+ def has_transition_perm (bound_method : _ToDo , user : UserWithPermissions ) -> bool :
628
632
"""
629
633
Returns True if model in state allows to call bound_method and user have rights on it
630
634
"""
0 commit comments