77import inspect
88from functools import partialmethod
99from functools import wraps
10+ from typing import TYPE_CHECKING
11+ from typing import Any
12+ from typing import Callable
1013
1114from django .apps import apps as django_apps
1215from django .db import models
3235 "RETURN_VALUE" ,
3336]
3437
38+ if TYPE_CHECKING :
39+ _Model = models .Model
40+ else :
41+ _Model = object
42+
3543
3644class TransitionNotAllowed (Exception ):
3745 """Raised when a transition is not allowed"""
3846
39- def __init__ (self , * args , ** kwargs ):
47+ def __init__ (self , * args , ** kwargs ) -> None :
4048 self .object = kwargs .pop ("object" , None )
4149 self .method = kwargs .pop ("method" , None )
4250 super ().__init__ (* args , ** kwargs )
@@ -55,7 +63,7 @@ class ConcurrentTransition(Exception):
5563
5664
5765class 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 :
5967 self .method = method
6068 self .source = source
6169 self .target = target
@@ -65,10 +73,10 @@ def __init__(self, method, source, target, on_error, conditions, permission, cus
6573 self .custom = custom
6674
6775 @property
68- def name (self ):
76+ def name (self ) -> str :
6977 return self .method .__name__
7078
71- def has_perm (self , instance , user ):
79+ def has_perm (self , instance , user ) -> bool :
7280 if not self .permission :
7381 return True
7482 if callable (self .permission ):
@@ -127,9 +135,9 @@ class FSMMeta:
127135 Models methods transitions meta information
128136 """
129137
130- def __init__ (self , field , method ):
138+ def __init__ (self , field , method ) -> None :
131139 self .field = field
132- self .transitions = {} # source -> Transition
140+ self .transitions : dict [ str , Any ] = {} # source -> Transition
133141
134142 def get_transition (self , source ):
135143 transition = self .transitions .get (source , None )
@@ -139,7 +147,7 @@ def get_transition(self, source):
139147 transition = self .transitions .get ("+" , None )
140148 return transition
141149
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 :
143151 if source in self .transitions :
144152 raise AssertionError (f"Duplicate transition for { source } state" )
145153
@@ -153,7 +161,7 @@ def add_transition(self, method, source, target, on_error=None, conditions=[], p
153161 custom = custom ,
154162 )
155163
156- def has_transition (self , state ):
164+ def has_transition (self , state ) -> bool :
157165 """
158166 Lookup if any transition exists from current model state using current method
159167 """
@@ -168,7 +176,7 @@ def has_transition(self, state):
168176
169177 return False
170178
171- def conditions_met (self , instance , state ):
179+ def conditions_met (self , instance , state ) -> bool :
172180 """
173181 Check if all conditions have been met
174182 """
@@ -182,13 +190,13 @@ def conditions_met(self, instance, state):
182190
183191 return all (condition (instance ) for condition in transition .conditions )
184192
185- def has_transition_perm (self , instance , state , user ):
193+ def has_transition_perm (self , instance , state , user ) -> bool :
186194 transition = self .get_transition (state )
187195
188196 if not transition :
189197 return False
190-
191- return transition .has_perm (instance , user )
198+ else :
199+ return bool ( transition .has_perm (instance , user ) )
192200
193201 def next_state (self , current_state ):
194202 transition = self .get_transition (current_state )
@@ -208,15 +216,15 @@ def exception_state(self, current_state):
208216
209217
210218class FSMFieldDescriptor :
211- def __init__ (self , field ):
219+ def __init__ (self , field ) -> None :
212220 self .field = field
213221
214222 def __get__ (self , instance , type = None ):
215223 if instance is None :
216224 return self
217225 return self .field .get_state (instance )
218226
219- def __set__ (self , instance , value ):
227+ def __set__ (self , instance , value ) -> None :
220228 if self .field .protected and self .field .name in instance .__dict__ :
221229 raise AttributeError (f"Direct { self .field .name } modification is not allowed" )
222230
@@ -225,12 +233,12 @@ def __set__(self, instance, value):
225233 self .field .set_state (instance , value )
226234
227235
228- class FSMFieldMixin :
236+ class FSMFieldMixin ( Field ) :
229237 descriptor_class = FSMFieldDescriptor
230238
231- def __init__ (self , * args , ** kwargs ):
239+ def __init__ (self , * args , ** kwargs ) -> None :
232240 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)
234242 self .state_proxy = {} # state -> ProxyClsRef
235243
236244 state_choices = kwargs .pop ("state_choices" , None )
@@ -256,7 +264,7 @@ def deconstruct(self):
256264 def get_state (self , instance ):
257265 # The state field may be deferred. We delegate the logic of figuring this out
258266 # 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]
260268
261269 def set_state (self , instance , state ):
262270 instance .__dict__ [self .name ] = state
@@ -396,7 +404,7 @@ class FSMField(FSMFieldMixin, models.CharField):
396404 State Machine support for Django model as CharField
397405 """
398406
399- def __init__ (self , * args , ** kwargs ):
407+ def __init__ (self , * args , ** kwargs ) -> None :
400408 kwargs .setdefault ("max_length" , 50 )
401409 super ().__init__ (* args , ** kwargs )
402410
@@ -421,7 +429,7 @@ def set_state(self, instance, state):
421429 instance .__dict__ [self .attname ] = self .to_python (state )
422430
423431
424- class FSMModelMixin :
432+ class FSMModelMixin ( _Model ) :
425433 """
426434 Mixin that allows refresh_from_db for models with fsm protected fields
427435 """
@@ -448,7 +456,7 @@ def refresh_from_db(self, *args, **kwargs):
448456 super ().refresh_from_db (* args , ** kwargs )
449457
450458
451- class ConcurrentTransitionMixin :
459+ class ConcurrentTransitionMixin ( _Model ) :
452460 """
453461 Protects a Model from undesirable effects caused by concurrently executed transitions,
454462 e.g. running the same transition multiple times at the same time, or running different
@@ -474,7 +482,7 @@ class ConcurrentTransitionMixin:
474482 state, thus practically negating their effect.
475483 """
476484
477- def __init__ (self , * args , ** kwargs ):
485+ def __init__ (self , * args , ** kwargs ) -> None :
478486 super ().__init__ (* args , ** kwargs )
479487 self ._update_initial_state ()
480488
@@ -492,7 +500,7 @@ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_updat
492500 # state filter will be used to narrow down the standard filter checking only PK
493501 state_filter = {field .attname : self .__initial_states [field .attname ] for field in filter_on }
494502
495- updated = super ()._do_update (
503+ updated = super ()._do_update ( # type: ignore[misc]
496504 base_qs = base_qs .filter (** state_filter ),
497505 using = using ,
498506 pk_val = pk_val ,
@@ -557,7 +565,7 @@ def _change_state(instance, *args, **kwargs):
557565 return inner_transition
558566
559567
560- def can_proceed (bound_method , check_conditions = True ):
568+ def can_proceed (bound_method , check_conditions = True ) -> bool :
561569 """
562570 Returns True if model in state allows to call bound_method
563571
@@ -574,7 +582,7 @@ def can_proceed(bound_method, check_conditions=True):
574582 return meta .has_transition (current_state ) and (not check_conditions or meta .conditions_met (self , current_state ))
575583
576584
577- def has_transition_perm (bound_method , user ):
585+ def has_transition_perm (bound_method , user ) -> bool :
578586 """
579587 Returns True if model in state allows to call bound_method and user have rights on it
580588 """
@@ -585,7 +593,7 @@ def has_transition_perm(bound_method, user):
585593 self = bound_method .__self__
586594 current_state = meta .field .get_state (self )
587595
588- return (
596+ return bool (
589597 meta .has_transition (current_state )
590598 and meta .conditions_met (self , current_state )
591599 and meta .has_transition_perm (self , current_state , user )
@@ -598,7 +606,7 @@ def get_state(self, model, transition, result, args=[], kwargs={}):
598606
599607
600608class RETURN_VALUE (State ):
601- def __init__ (self , * allowed_states ):
609+ def __init__ (self , * allowed_states ) -> None :
602610 self .allowed_states = allowed_states if allowed_states else None
603611
604612 def get_state (self , model , transition , result , args = [], kwargs = {}):
@@ -609,7 +617,7 @@ def get_state(self, model, transition, result, args=[], kwargs={}):
609617
610618
611619class GET_STATE (State ):
612- def __init__ (self , func , states = None ):
620+ def __init__ (self , func , states = None ) -> None :
613621 self .func = func
614622 self .allowed_states = states
615623
0 commit comments