7
7
import inspect
8
8
from functools import partialmethod
9
9
from functools import wraps
10
+ from typing import TYPE_CHECKING
11
+ from typing import Any
12
+ from typing import Callable
10
13
11
14
from django .apps import apps as django_apps
12
15
from django .db import models
32
35
"RETURN_VALUE" ,
33
36
]
34
37
38
+ if TYPE_CHECKING :
39
+ _Model = models .Model
40
+ else :
41
+ _Model = object
42
+
35
43
36
44
class TransitionNotAllowed (Exception ):
37
45
"""Raised when a transition is not allowed"""
38
46
39
- def __init__ (self , * args , ** kwargs ):
47
+ def __init__ (self , * args , ** kwargs ) -> None :
40
48
self .object = kwargs .pop ("object" , None )
41
49
self .method = kwargs .pop ("method" , None )
42
50
super ().__init__ (* args , ** kwargs )
@@ -55,7 +63,7 @@ class ConcurrentTransition(Exception):
55
63
56
64
57
65
class Transition :
58
- def __init__ (self , method , source , target , on_error , conditions , permission , custom ):
66
+ def __init__ (self , method : Callable , source , target , on_error , conditions , permission , custom ) -> None :
59
67
self .method = method
60
68
self .source = source
61
69
self .target = target
@@ -65,10 +73,10 @@ def __init__(self, method, source, target, on_error, conditions, permission, cus
65
73
self .custom = custom
66
74
67
75
@property
68
- def name (self ):
76
+ def name (self ) -> str :
69
77
return self .method .__name__
70
78
71
- def has_perm (self , instance , user ):
79
+ def has_perm (self , instance , user ) -> bool :
72
80
if not self .permission :
73
81
return True
74
82
if callable (self .permission ):
@@ -127,9 +135,9 @@ class FSMMeta:
127
135
Models methods transitions meta information
128
136
"""
129
137
130
- def __init__ (self , field , method ):
138
+ def __init__ (self , field , method ) -> None :
131
139
self .field = field
132
- self .transitions = {} # source -> Transition
140
+ self .transitions : dict [ str , Any ] = {} # source -> Transition
133
141
134
142
def get_transition (self , source ):
135
143
transition = self .transitions .get (source , None )
@@ -139,7 +147,7 @@ def get_transition(self, source):
139
147
transition = self .transitions .get ("+" , None )
140
148
return transition
141
149
142
- def add_transition (self , method , source , target , on_error = None , conditions = [], permission = None , custom = {}):
150
+ def add_transition (self , method , source , target , on_error = None , conditions = [], permission = None , custom = {}) -> None :
143
151
if source in self .transitions :
144
152
raise AssertionError (f"Duplicate transition for { source } state" )
145
153
@@ -153,7 +161,7 @@ def add_transition(self, method, source, target, on_error=None, conditions=[], p
153
161
custom = custom ,
154
162
)
155
163
156
- def has_transition (self , state ):
164
+ def has_transition (self , state ) -> bool :
157
165
"""
158
166
Lookup if any transition exists from current model state using current method
159
167
"""
@@ -168,7 +176,7 @@ def has_transition(self, state):
168
176
169
177
return False
170
178
171
- def conditions_met (self , instance , state ):
179
+ def conditions_met (self , instance , state ) -> bool :
172
180
"""
173
181
Check if all conditions have been met
174
182
"""
@@ -182,13 +190,13 @@ def conditions_met(self, instance, state):
182
190
183
191
return all (condition (instance ) for condition in transition .conditions )
184
192
185
- def has_transition_perm (self , instance , state , user ):
193
+ def has_transition_perm (self , instance , state , user ) -> bool :
186
194
transition = self .get_transition (state )
187
195
188
196
if not transition :
189
197
return False
190
-
191
- return transition .has_perm (instance , user )
198
+ else :
199
+ return bool ( transition .has_perm (instance , user ) )
192
200
193
201
def next_state (self , current_state ):
194
202
transition = self .get_transition (current_state )
@@ -208,15 +216,15 @@ def exception_state(self, current_state):
208
216
209
217
210
218
class FSMFieldDescriptor :
211
- def __init__ (self , field ):
219
+ def __init__ (self , field ) -> None :
212
220
self .field = field
213
221
214
222
def __get__ (self , instance , type = None ):
215
223
if instance is None :
216
224
return self
217
225
return self .field .get_state (instance )
218
226
219
- def __set__ (self , instance , value ):
227
+ def __set__ (self , instance , value ) -> None :
220
228
if self .field .protected and self .field .name in instance .__dict__ :
221
229
raise AttributeError (f"Direct { self .field .name } modification is not allowed" )
222
230
@@ -225,12 +233,12 @@ def __set__(self, instance, value):
225
233
self .field .set_state (instance , value )
226
234
227
235
228
- class FSMFieldMixin :
236
+ class FSMFieldMixin ( Field ) :
229
237
descriptor_class = FSMFieldDescriptor
230
238
231
- def __init__ (self , * args , ** kwargs ):
239
+ def __init__ (self , * args , ** kwargs ) -> None :
232
240
self .protected = kwargs .pop ("protected" , False )
233
- self .transitions = {} # cls -> (transitions name -> method)
241
+ self .transitions : dict [ Any , dict [ str , Any ]] = {} # cls -> (transitions name -> method)
234
242
self .state_proxy = {} # state -> ProxyClsRef
235
243
236
244
state_choices = kwargs .pop ("state_choices" , None )
@@ -256,7 +264,7 @@ def deconstruct(self):
256
264
def get_state (self , instance ):
257
265
# The state field may be deferred. We delegate the logic of figuring this out
258
266
# and loading the deferred field on-demand to Django's built-in DeferredAttribute class.
259
- return DeferredAttribute (self ).__get__ (instance )
267
+ return DeferredAttribute (self ).__get__ (instance ) # type: ignore[attr-defined]
260
268
261
269
def set_state (self , instance , state ):
262
270
instance .__dict__ [self .name ] = state
@@ -396,7 +404,7 @@ class FSMField(FSMFieldMixin, models.CharField):
396
404
State Machine support for Django model as CharField
397
405
"""
398
406
399
- def __init__ (self , * args , ** kwargs ):
407
+ def __init__ (self , * args , ** kwargs ) -> None :
400
408
kwargs .setdefault ("max_length" , 50 )
401
409
super ().__init__ (* args , ** kwargs )
402
410
@@ -421,7 +429,7 @@ def set_state(self, instance, state):
421
429
instance .__dict__ [self .attname ] = self .to_python (state )
422
430
423
431
424
- class FSMModelMixin :
432
+ class FSMModelMixin ( _Model ) :
425
433
"""
426
434
Mixin that allows refresh_from_db for models with fsm protected fields
427
435
"""
@@ -448,7 +456,7 @@ def refresh_from_db(self, *args, **kwargs):
448
456
super ().refresh_from_db (* args , ** kwargs )
449
457
450
458
451
- class ConcurrentTransitionMixin :
459
+ class ConcurrentTransitionMixin ( _Model ) :
452
460
"""
453
461
Protects a Model from undesirable effects caused by concurrently executed transitions,
454
462
e.g. running the same transition multiple times at the same time, or running different
@@ -474,7 +482,7 @@ class ConcurrentTransitionMixin:
474
482
state, thus practically negating their effect.
475
483
"""
476
484
477
- def __init__ (self , * args , ** kwargs ):
485
+ def __init__ (self , * args , ** kwargs ) -> None :
478
486
super ().__init__ (* args , ** kwargs )
479
487
self ._update_initial_state ()
480
488
@@ -492,7 +500,7 @@ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_updat
492
500
# state filter will be used to narrow down the standard filter checking only PK
493
501
state_filter = {field .attname : self .__initial_states [field .attname ] for field in filter_on }
494
502
495
- updated = super ()._do_update (
503
+ updated = super ()._do_update ( # type: ignore[misc]
496
504
base_qs = base_qs .filter (** state_filter ),
497
505
using = using ,
498
506
pk_val = pk_val ,
@@ -557,7 +565,7 @@ def _change_state(instance, *args, **kwargs):
557
565
return inner_transition
558
566
559
567
560
- def can_proceed (bound_method , check_conditions = True ):
568
+ def can_proceed (bound_method , check_conditions = True ) -> bool :
561
569
"""
562
570
Returns True if model in state allows to call bound_method
563
571
@@ -574,7 +582,7 @@ def can_proceed(bound_method, check_conditions=True):
574
582
return meta .has_transition (current_state ) and (not check_conditions or meta .conditions_met (self , current_state ))
575
583
576
584
577
- def has_transition_perm (bound_method , user ):
585
+ def has_transition_perm (bound_method , user ) -> bool :
578
586
"""
579
587
Returns True if model in state allows to call bound_method and user have rights on it
580
588
"""
@@ -585,7 +593,7 @@ def has_transition_perm(bound_method, user):
585
593
self = bound_method .__self__
586
594
current_state = meta .field .get_state (self )
587
595
588
- return (
596
+ return bool (
589
597
meta .has_transition (current_state )
590
598
and meta .conditions_met (self , current_state )
591
599
and meta .has_transition_perm (self , current_state , user )
@@ -598,7 +606,7 @@ def get_state(self, model, transition, result, args=[], kwargs={}):
598
606
599
607
600
608
class RETURN_VALUE (State ):
601
- def __init__ (self , * allowed_states ):
609
+ def __init__ (self , * allowed_states ) -> None :
602
610
self .allowed_states = allowed_states if allowed_states else None
603
611
604
612
def get_state (self , model , transition , result , args = [], kwargs = {}):
@@ -609,7 +617,7 @@ def get_state(self, model, transition, result, args=[], kwargs={}):
609
617
610
618
611
619
class GET_STATE (State ):
612
- def __init__ (self , func , states = None ):
620
+ def __init__ (self , func , states = None ) -> None :
613
621
self .func = func
614
622
self .allowed_states = states
615
623
0 commit comments