55from __future__ import annotations
66
77import inspect
8+ from collections .abc import Callable
9+ from collections .abc import Collection
10+ from collections .abc import Generator
11+ from collections .abc import Iterable
12+ from collections .abc import Sequence
813from functools import partialmethod
914from functools import wraps
1015from typing import TYPE_CHECKING
16+ from typing import Any
1117
1218from django .apps import apps as django_apps
1319from django .db import models
1420from django .db .models import Field
21+ from django .db .models import QuerySet
1522from django .db .models .query_utils import DeferredAttribute
1623from django .db .models .signals import class_prepared
1724
3441]
3542
3643if TYPE_CHECKING :
37- from collections .abc import Callable
38- from collections .abc import Generator
39- from collections .abc import Iterable
40- from collections .abc import Sequence
41- from typing import Any
44+ from typing import Self
4245
46+ from _typeshed import Incomplete
4347 from django .contrib .auth .models import PermissionsMixin as UserWithPermissions
4448 from django .utils .functional import _StrOrPromise
4549
46- _Model = models .Model
50+ _FSMModel = models .Model
4751 _Field = models .Field [Any , Any ]
4852 CharField = models .CharField [Any , Any ]
4953 IntegerField = models .IntegerField [Any , Any ]
5054 ForeignKey = models .ForeignKey [Any , Any ]
5155
5256 _StateValue = str | int
57+ _Permission = str | Callable [[_FSMModel , UserWithPermissions ], bool ]
5358 _Instance = models .Model # TODO: use real type
54- _ToDo = Any # TODO: use real type
59+
5560else :
56- _Model = object
61+ _FSMModel = object
5762 _Field = object
5863 CharField = models .CharField
5964 IntegerField = models .IntegerField
6065 ForeignKey = models .ForeignKey
66+ Self = Any
6167
6268
6369class TransitionNotAllowed (Exception ):
@@ -277,7 +283,7 @@ class FSMFieldMixin(_Field):
277283
278284 def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
279285 self .protected = kwargs .pop ("protected" , False )
280- self .transitions : dict [type [_Model ], dict [str , Any ]] = {} # cls -> (transitions name -> method)
286+ self .transitions : dict [type [_FSMModel ], dict [str , Any ]] = {} # cls -> (transitions name -> method)
281287 self .state_proxy = {} # state -> ProxyClsRef
282288
283289 state_choices = kwargs .pop ("state_choices" , None )
@@ -329,7 +335,7 @@ def set_proxy(self, instance: _Instance, state: str) -> None:
329335
330336 instance .__class__ = model
331337
332- def change_state (self , instance : _Instance , method : _ToDo , * args : Any , ** kwargs : Any ) -> Any :
338+ def change_state (self , instance : _Instance , method : Incomplete , * args : Any , ** kwargs : Any ) -> Any :
333339 meta = method ._django_fsm
334340 method_name = method .__name__
335341 current_state = self .get_state (instance )
@@ -382,7 +388,7 @@ def change_state(self, instance: _Instance, method: _ToDo, *args: Any, **kwargs:
382388
383389 return result
384390
385- def get_all_transitions (self , instance_cls : type [_Model ]) -> Generator [Transition , None , None ]:
391+ def get_all_transitions (self , instance_cls : type [_FSMModel ]) -> Generator [Transition , None , None ]:
386392 """
387393 Returns [(source, target, name, method)] for all field transitions
388394 """
@@ -394,7 +400,7 @@ def get_all_transitions(self, instance_cls: type[_Model]) -> Generator[Transitio
394400 for transition in meta .transitions .values ():
395401 yield transition
396402
397- def contribute_to_class (self , cls : type [_Model ], name : str , private_only : bool = False , ** kwargs : Any ) -> None :
403+ def contribute_to_class (self , cls : type [_FSMModel ], name : str , private_only : bool = False , ** kwargs : Any ) -> None :
398404 self .base_cls = cls
399405
400406 super ().contribute_to_class (cls , name , private_only = private_only , ** kwargs )
@@ -415,7 +421,7 @@ def _collect_transitions(self, *args: Any, **kwargs: Any) -> None:
415421 if not issubclass (sender , self .base_cls ):
416422 return
417423
418- def is_field_transition_method (attr : _ToDo ) -> bool :
424+ def is_field_transition_method (attr : Incomplete ) -> bool :
419425 return (
420426 (inspect .ismethod (attr ) or inspect .isfunction (attr ))
421427 and hasattr (attr , "_django_fsm" )
@@ -461,14 +467,14 @@ class FSMKeyField(FSMFieldMixin, ForeignKey):
461467 State Machine support for Django model
462468 """
463469
464- def get_state (self , instance : _Instance ) -> _ToDo :
470+ def get_state (self , instance : _Instance ) -> Incomplete :
465471 return instance .__dict__ [self .attname ]
466472
467473 def set_state (self , instance : _Instance , state : str ) -> None :
468474 instance .__dict__ [self .attname ] = self .to_python (state )
469475
470476
471- class FSMModelMixin (_Model ):
477+ class FSMModelMixin (_FSMModel ):
472478 """
473479 Mixin that allows refresh_from_db for models with fsm protected fields
474480 """
@@ -495,7 +501,7 @@ def refresh_from_db(self, *args, **kwargs):
495501 super ().refresh_from_db (* args , ** kwargs )
496502
497503
498- class ConcurrentTransitionMixin (_Model ):
504+ class ConcurrentTransitionMixin (_FSMModel ):
499505 """
500506 Protects a Model from undesirable effects caused by concurrently executed transitions,
501507 e.g. running the same transition multiple times at the same time, or running different
@@ -529,7 +535,15 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
529535 def state_fields (self ) -> Iterable [Any ]:
530536 return filter (lambda field : isinstance (field , FSMFieldMixin ), self ._meta .fields )
531537
532- def _do_update (self , base_qs , using , pk_val , values , update_fields , forced_update ): # type: ignore[no-untyped-def]
538+ def _do_update (
539+ self ,
540+ base_qs : QuerySet [Self ],
541+ using : Any ,
542+ pk_val : Any ,
543+ values : Collection [Any ] | None ,
544+ update_fields : Iterable [str ] | None ,
545+ forced_update : bool ,
546+ ) -> bool :
533547 # _do_update is called once for each model class in the inheritance hierarchy.
534548 # We can only filter the base_qs on state fields (can be more than one!) present in this particular model.
535549
@@ -539,7 +553,7 @@ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_updat
539553 # state filter will be used to narrow down the standard filter checking only PK
540554 state_filter = {field .attname : self .__initial_states [field .attname ] for field in filter_on }
541555
542- updated = super ()._do_update ( # type: ignore[misc]
556+ updated : bool = super ()._do_update ( # type: ignore[misc]
543557 base_qs = base_qs .filter (** state_filter ),
544558 using = using ,
545559 pk_val = pk_val ,
@@ -577,7 +591,7 @@ def transition(
577591 target : _StateValue | State | None = None ,
578592 on_error : _StateValue | None = None ,
579593 conditions : list [Callable [[Any ], bool ]] = [],
580- permission : str | Callable [[ models . Model , UserWithPermissions ], bool ] | None = None ,
594+ permission : _Permission | None = None ,
581595 custom : dict [str , _StrOrPromise ] = {},
582596) -> Callable [[Any ], Any ]:
583597 """
@@ -587,21 +601,22 @@ def transition(
587601 has not changed after the function call.
588602 """
589603
590- def inner_transition (func : _ToDo ) -> _ToDo :
604+ def inner_transition (func : Incomplete ) -> Incomplete :
591605 wrapper_installed , fsm_meta = True , getattr (func , "_django_fsm" , None )
592606 if not fsm_meta :
593607 wrapper_installed = False
594608 fsm_meta = FSMMeta (field = field , method = func )
595609 setattr (func , "_django_fsm" , fsm_meta )
596610
611+ # if isinstance(source, Iterable):
597612 if isinstance (source , (list , tuple , set )):
598613 for state in source :
599614 func ._django_fsm .add_transition (func , state , target , on_error , conditions , permission , custom )
600615 else :
601616 func ._django_fsm .add_transition (func , source , target , on_error , conditions , permission , custom )
602617
603618 @wraps (func )
604- def _change_state (instance : _Instance , * args : Any , ** kwargs : Any ) -> _ToDo :
619+ def _change_state (instance : _Instance , * args : Any , ** kwargs : Any ) -> Incomplete :
605620 return fsm_meta .field .change_state (instance , func , * args , ** kwargs )
606621
607622 if not wrapper_installed :
@@ -612,7 +627,7 @@ def _change_state(instance: _Instance, *args: Any, **kwargs: Any) -> _ToDo:
612627 return inner_transition
613628
614629
615- def can_proceed (bound_method : _ToDo , check_conditions : bool = True ) -> bool :
630+ def can_proceed (bound_method : Incomplete , check_conditions : bool = True ) -> bool :
616631 """
617632 Returns True if model in state allows to call bound_method
618633
@@ -629,7 +644,7 @@ def can_proceed(bound_method: _ToDo, check_conditions: bool = True) -> bool:
629644 return meta .has_transition (current_state ) and (not check_conditions or meta .conditions_met (self , current_state ))
630645
631646
632- def has_transition_perm (bound_method : _ToDo , user : UserWithPermissions ) -> bool :
647+ def has_transition_perm (bound_method : Incomplete , user : UserWithPermissions ) -> bool :
633648 """
634649 Returns True if model in state allows to call bound_method and user have rights on it
635650 """
@@ -648,15 +663,15 @@ def has_transition_perm(bound_method: _ToDo, user: UserWithPermissions) -> bool:
648663
649664
650665class State :
651- def get_state (self , model : _Model , transition : Transition , result : Any , args : Any = [], kwargs : Any = {}) -> _ToDo :
666+ def get_state (self , model : _FSMModel , transition : Transition , result : Any , args : Any = [], kwargs : Any = {}) -> Incomplete :
652667 raise NotImplementedError
653668
654669
655670class RETURN_VALUE (State ):
656671 def __init__ (self , * allowed_states : Sequence [_StateValue ]) -> None :
657672 self .allowed_states = allowed_states if allowed_states else None
658673
659- def get_state (self , model : _Model , transition : Transition , result : Any , args : Any = [], kwargs : Any = {}) -> _ToDo :
674+ def get_state (self , model : _FSMModel , transition : Transition , result : Any , args : Any = [], kwargs : Any = {}) -> Incomplete :
660675 if self .allowed_states is not None :
661676 if result not in self .allowed_states :
662677 raise InvalidResultState (f"{ result } is not in list of allowed states\n { self .allowed_states } " )
@@ -669,8 +684,8 @@ def __init__(self, func: Callable[..., _StateValue | Any], states: Sequence[_Sta
669684 self .allowed_states = states
670685
671686 def get_state (
672- self , model : _Model , transition : Transition , result : _StateValue | Any , args : Any = [], kwargs : Any = {}
673- ) -> _ToDo :
687+ self , model : _FSMModel , transition : Transition , result : _StateValue | Any , args : Any = [], kwargs : Any = {}
688+ ) -> Incomplete :
674689 result_state = self .func (model , * args , ** kwargs )
675690 if self .allowed_states is not None :
676691 if result_state not in self .allowed_states :
0 commit comments