44from __future__ import annotations
55
66import inspect
7+ from collections .abc import Callable
8+ from collections .abc import Collection
9+ from collections .abc import Generator
10+ from collections .abc import Iterable
11+ from collections .abc import Sequence
712from functools import partialmethod
813from functools import wraps
914from typing import TYPE_CHECKING
15+ from typing import Any
1016
1117from django .apps import apps as django_apps
1218from django .db import models
1319from django .db .models import Field
20+ from django .db .models import QuerySet
1421from django .db .models .query_utils import DeferredAttribute
1522from django .db .models .signals import class_prepared
1623
3340]
3441
3542if TYPE_CHECKING :
36- from collections .abc import Callable
37- from collections .abc import Generator
38- from collections .abc import Iterable
39- from collections .abc import Sequence
40- from typing import Any
43+ from typing import Self
4144
45+ from _typeshed import Incomplete
4246 from django .contrib .auth .models import PermissionsMixin as UserWithPermissions
4347 from django .utils .functional import _StrOrPromise
4448
45- _Model = models .Model
49+ _FSMModel = models .Model
4650 _Field = models .Field [Any , Any ]
4751 CharField = models .CharField [Any , Any ]
4852 IntegerField = models .IntegerField [Any , Any ]
4953 ForeignKey = models .ForeignKey [Any , Any ]
5054
5155 _StateValue = str | int
56+ _Permission = str | Callable [[_FSMModel , UserWithPermissions ], bool ]
5257 _Instance = models .Model # TODO: use real type
53- _ToDo = Any # TODO: use real type
58+
5459else :
55- _Model = object
60+ _FSMModel = object
5661 _Field = object
5762 CharField = models .CharField
5863 IntegerField = models .IntegerField
5964 ForeignKey = models .ForeignKey
65+ Self = Any
6066
6167
6268class TransitionNotAllowed (Exception ):
@@ -265,7 +271,7 @@ class FSMFieldMixin(_Field):
265271
266272 def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
267273 self .protected = kwargs .pop ("protected" , False )
268- self .transitions : dict [type [_Model ], dict [str , Any ]] = {} # cls -> (transitions name -> method)
274+ self .transitions : dict [type [_FSMModel ], dict [str , Any ]] = {} # cls -> (transitions name -> method)
269275 self .state_proxy = {} # state -> ProxyClsRef
270276
271277 state_choices = kwargs .pop ("state_choices" , None )
@@ -317,7 +323,7 @@ def set_proxy(self, instance: _Instance, state: str) -> None:
317323
318324 instance .__class__ = model
319325
320- def change_state (self , instance : _Instance , method : _ToDo , * args : Any , ** kwargs : Any ) -> Any :
326+ def change_state (self , instance : _Instance , method : Incomplete , * args : Any , ** kwargs : Any ) -> Any :
321327 meta = method ._django_fsm
322328 method_name = method .__name__
323329 current_state = self .get_state (instance )
@@ -370,7 +376,7 @@ def change_state(self, instance: _Instance, method: _ToDo, *args: Any, **kwargs:
370376
371377 return result
372378
373- def get_all_transitions (self , instance_cls : type [_Model ]) -> Generator [Transition , None , None ]:
379+ def get_all_transitions (self , instance_cls : type [_FSMModel ]) -> Generator [Transition , None , None ]:
374380 """
375381 Returns [(source, target, name, method)] for all field transitions
376382 """
@@ -382,7 +388,7 @@ def get_all_transitions(self, instance_cls: type[_Model]) -> Generator[Transitio
382388 for transition in meta .transitions .values ():
383389 yield transition
384390
385- def contribute_to_class (self , cls : type [_Model ], name : str , private_only : bool = False , ** kwargs : Any ) -> None :
391+ def contribute_to_class (self , cls : type [_FSMModel ], name : str , private_only : bool = False , ** kwargs : Any ) -> None :
386392 self .base_cls = cls
387393
388394 super ().contribute_to_class (cls , name , private_only = private_only , ** kwargs )
@@ -403,7 +409,7 @@ def _collect_transitions(self, *args: Any, **kwargs: Any) -> None:
403409 if not issubclass (sender , self .base_cls ):
404410 return
405411
406- def is_field_transition_method (attr : _ToDo ) -> bool :
412+ def is_field_transition_method (attr : Incomplete ) -> bool :
407413 return (
408414 (inspect .ismethod (attr ) or inspect .isfunction (attr ))
409415 and hasattr (attr , "_django_fsm" )
@@ -449,14 +455,14 @@ class FSMKeyField(FSMFieldMixin, ForeignKey):
449455 State Machine support for Django model
450456 """
451457
452- def get_state (self , instance : _Instance ) -> _ToDo :
458+ def get_state (self , instance : _Instance ) -> Incomplete :
453459 return instance .__dict__ [self .attname ]
454460
455461 def set_state (self , instance : _Instance , state : str ) -> None :
456462 instance .__dict__ [self .attname ] = self .to_python (state )
457463
458464
459- class ConcurrentTransitionMixin (_Model ):
465+ class ConcurrentTransitionMixin (_FSMModel ):
460466 """
461467 Protects a Model from undesirable effects caused by concurrently executed transitions,
462468 e.g. running the same transition multiple times at the same time, or running different
@@ -490,7 +496,15 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
490496 def state_fields (self ) -> Iterable [Any ]:
491497 return filter (lambda field : isinstance (field , FSMFieldMixin ), self ._meta .fields )
492498
493- def _do_update (self , base_qs , using , pk_val , values , update_fields , forced_update ): # type: ignore[no-untyped-def]
499+ def _do_update (
500+ self ,
501+ base_qs : QuerySet [Self ],
502+ using : Any ,
503+ pk_val : Any ,
504+ values : Collection [Any ] | None ,
505+ update_fields : Iterable [str ] | None ,
506+ forced_update : bool ,
507+ ) -> bool :
494508 # _do_update is called once for each model class in the inheritance hierarchy.
495509 # We can only filter the base_qs on state fields (can be more than one!) present in this particular model.
496510
@@ -500,7 +514,7 @@ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_updat
500514 # state filter will be used to narrow down the standard filter checking only PK
501515 state_filter = {field .attname : self .__initial_states [field .attname ] for field in filter_on }
502516
503- updated = super ()._do_update ( # type: ignore[misc]
517+ updated : bool = super ()._do_update ( # type: ignore[misc]
504518 base_qs = base_qs .filter (** state_filter ),
505519 using = using ,
506520 pk_val = pk_val ,
@@ -538,7 +552,7 @@ def transition(
538552 target : _StateValue | State | None = None ,
539553 on_error : _StateValue | None = None ,
540554 conditions : list [Callable [[Any ], bool ]] = [],
541- permission : str | Callable [[ models . Model , UserWithPermissions ], bool ] | None = None ,
555+ permission : _Permission | None = None ,
542556 custom : dict [str , _StrOrPromise ] = {},
543557) -> Callable [[Any ], Any ]:
544558 """
@@ -548,21 +562,22 @@ def transition(
548562 has not changed after the function call.
549563 """
550564
551- def inner_transition (func : _ToDo ) -> _ToDo :
565+ def inner_transition (func : Incomplete ) -> Incomplete :
552566 wrapper_installed , fsm_meta = True , getattr (func , "_django_fsm" , None )
553567 if not fsm_meta :
554568 wrapper_installed = False
555569 fsm_meta = FSMMeta (field = field , method = func )
556570 setattr (func , "_django_fsm" , fsm_meta )
557571
572+ # if isinstance(source, Iterable):
558573 if isinstance (source , (list , tuple , set )):
559574 for state in source :
560575 func ._django_fsm .add_transition (func , state , target , on_error , conditions , permission , custom )
561576 else :
562577 func ._django_fsm .add_transition (func , source , target , on_error , conditions , permission , custom )
563578
564579 @wraps (func )
565- def _change_state (instance : _Instance , * args : Any , ** kwargs : Any ) -> _ToDo :
580+ def _change_state (instance : _Instance , * args : Any , ** kwargs : Any ) -> Incomplete :
566581 return fsm_meta .field .change_state (instance , func , * args , ** kwargs )
567582
568583 if not wrapper_installed :
@@ -573,7 +588,7 @@ def _change_state(instance: _Instance, *args: Any, **kwargs: Any) -> _ToDo:
573588 return inner_transition
574589
575590
576- def can_proceed (bound_method : _ToDo , check_conditions : bool = True ) -> bool :
591+ def can_proceed (bound_method : Incomplete , check_conditions : bool = True ) -> bool :
577592 """
578593 Returns True if model in state allows to call bound_method
579594
@@ -590,7 +605,7 @@ def can_proceed(bound_method: _ToDo, check_conditions: bool = True) -> bool:
590605 return meta .has_transition (current_state ) and (not check_conditions or meta .conditions_met (self , current_state ))
591606
592607
593- def has_transition_perm (bound_method : _ToDo , user : UserWithPermissions ) -> bool :
608+ def has_transition_perm (bound_method : Incomplete , user : UserWithPermissions ) -> bool :
594609 """
595610 Returns True if model in state allows to call bound_method and user have rights on it
596611 """
@@ -609,15 +624,15 @@ def has_transition_perm(bound_method: _ToDo, user: UserWithPermissions) -> bool:
609624
610625
611626class State :
612- def get_state (self , model : _Model , transition : Transition , result : Any , args : Any = [], kwargs : Any = {}) -> _ToDo :
627+ def get_state (self , model : _FSMModel , transition : Transition , result : Any , args : Any = [], kwargs : Any = {}) -> Incomplete :
613628 raise NotImplementedError
614629
615630
616631class RETURN_VALUE (State ):
617632 def __init__ (self , * allowed_states : Sequence [_StateValue ]) -> None :
618633 self .allowed_states = allowed_states if allowed_states else None
619634
620- def get_state (self , model : _Model , transition : Transition , result : Any , args : Any = [], kwargs : Any = {}) -> _ToDo :
635+ def get_state (self , model : _FSMModel , transition : Transition , result : Any , args : Any = [], kwargs : Any = {}) -> Incomplete :
621636 if self .allowed_states is not None :
622637 if result not in self .allowed_states :
623638 raise InvalidResultState (f"{ result } is not in list of allowed states\n { self .allowed_states } " )
@@ -630,8 +645,8 @@ def __init__(self, func: Callable[..., _StateValue | Any], states: Sequence[_Sta
630645 self .allowed_states = states
631646
632647 def get_state (
633- self , model : _Model , transition : Transition , result : _StateValue | Any , args : Any = [], kwargs : Any = {}
634- ) -> _ToDo :
648+ self , model : _FSMModel , transition : Transition , result : _StateValue | Any , args : Any = [], kwargs : Any = {}
649+ ) -> Incomplete :
635650 result_state = self .func (model , * args , ** kwargs )
636651 if self .allowed_states is not None :
637652 if result_state not in self .allowed_states :
0 commit comments