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
16+ from typing import Self
1017
1118from django .apps import apps as django_apps
1219from django .db import models
1320from django .db .models import Field
21+ from django .db .models import QuerySet
1422from django .db .models .query_utils import DeferredAttribute
1523from django .db .models .signals import class_prepared
1624
3341]
3442
3543if 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
41-
44+ from _typeshed import Incomplete
4245 from django .contrib .auth .models import PermissionsMixin as UserWithPermissions
4346 from django .utils .functional import _StrOrPromise
4447
45- _Model = models .Model
48+ _FSMModel = models .Model
4649 _Field = models .Field [Any , Any ]
4750 CharField = models .CharField [Any , Any ]
4851 IntegerField = models .IntegerField [Any , Any ]
4952 ForeignKey = models .ForeignKey [Any , Any ]
5053
5154 _StateValue = str | int
55+ _Permission = str | Callable [[_FSMModel , UserWithPermissions ], bool ]
5256 _Instance = models .Model # TODO: use real type
53- _ToDo = Any # TODO: use real type
5457else :
55- _Model = object
58+ _FSMModel = object
5659 _Field = object
5760 CharField = models .CharField
5861 IntegerField = models .IntegerField
@@ -265,7 +268,7 @@ class FSMFieldMixin(_Field):
265268
266269 def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
267270 self .protected = kwargs .pop ("protected" , False )
268- self .transitions : dict [type [_Model ], dict [str , Any ]] = {} # cls -> (transitions name -> method)
271+ self .transitions : dict [type [_FSMModel ], dict [str , Any ]] = {} # cls -> (transitions name -> method)
269272 self .state_proxy = {} # state -> ProxyClsRef
270273
271274 state_choices = kwargs .pop ("state_choices" , None )
@@ -317,7 +320,7 @@ def set_proxy(self, instance: _Instance, state: str) -> None:
317320
318321 instance .__class__ = model
319322
320- def change_state (self , instance : _Instance , method : _ToDo , * args : Any , ** kwargs : Any ) -> Any :
323+ def change_state (self , instance : _Instance , method : Incomplete , * args : Any , ** kwargs : Any ) -> Any :
321324 meta = method ._django_fsm
322325 method_name = method .__name__
323326 current_state = self .get_state (instance )
@@ -370,7 +373,7 @@ def change_state(self, instance: _Instance, method: _ToDo, *args: Any, **kwargs:
370373
371374 return result
372375
373- def get_all_transitions (self , instance_cls : type [_Model ]) -> Generator [Transition , None , None ]:
376+ def get_all_transitions (self , instance_cls : type [_FSMModel ]) -> Generator [Transition , None , None ]:
374377 """
375378 Returns [(source, target, name, method)] for all field transitions
376379 """
@@ -382,7 +385,7 @@ def get_all_transitions(self, instance_cls: type[_Model]) -> Generator[Transitio
382385 for transition in meta .transitions .values ():
383386 yield transition
384387
385- def contribute_to_class (self , cls : type [_Model ], name : str , private_only : bool = False , ** kwargs : Any ) -> None :
388+ def contribute_to_class (self , cls : type [_FSMModel ], name : str , private_only : bool = False , ** kwargs : Any ) -> None :
386389 self .base_cls = cls
387390
388391 super ().contribute_to_class (cls , name , private_only = private_only , ** kwargs )
@@ -403,7 +406,7 @@ def _collect_transitions(self, *args: Any, **kwargs: Any) -> None:
403406 if not issubclass (sender , self .base_cls ):
404407 return
405408
406- def is_field_transition_method (attr : _ToDo ) -> bool :
409+ def is_field_transition_method (attr : Incomplete ) -> bool :
407410 return (
408411 (inspect .ismethod (attr ) or inspect .isfunction (attr ))
409412 and hasattr (attr , "_django_fsm" )
@@ -449,14 +452,14 @@ class FSMKeyField(FSMFieldMixin, ForeignKey):
449452 State Machine support for Django model
450453 """
451454
452- def get_state (self , instance : _Instance ) -> _ToDo :
455+ def get_state (self , instance : _Instance ) -> Incomplete :
453456 return instance .__dict__ [self .attname ]
454457
455458 def set_state (self , instance : _Instance , state : str ) -> None :
456459 instance .__dict__ [self .attname ] = self .to_python (state )
457460
458461
459- class ConcurrentTransitionMixin (_Model ):
462+ class ConcurrentTransitionMixin (_FSMModel ):
460463 """
461464 Protects a Model from undesirable effects caused by concurrently executed transitions,
462465 e.g. running the same transition multiple times at the same time, or running different
@@ -490,7 +493,15 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
490493 def state_fields (self ) -> Iterable [Any ]:
491494 return filter (lambda field : isinstance (field , FSMFieldMixin ), self ._meta .fields )
492495
493- def _do_update (self , base_qs , using , pk_val , values , update_fields , forced_update ): # type: ignore[no-untyped-def]
496+ def _do_update (
497+ self ,
498+ base_qs : QuerySet [Self ],
499+ using : Any ,
500+ pk_val : Any ,
501+ values : Collection [Any ],
502+ update_fields : Iterable [str ],
503+ forced_update : bool ,
504+ ) -> bool :
494505 # _do_update is called once for each model class in the inheritance hierarchy.
495506 # We can only filter the base_qs on state fields (can be more than one!) present in this particular model.
496507
@@ -500,7 +511,7 @@ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_updat
500511 # state filter will be used to narrow down the standard filter checking only PK
501512 state_filter = {field .attname : self .__initial_states [field .attname ] for field in filter_on }
502513
503- updated = super ()._do_update ( # type: ignore[misc]
514+ updated : bool = super ()._do_update ( # type: ignore[misc]
504515 base_qs = base_qs .filter (** state_filter ),
505516 using = using ,
506517 pk_val = pk_val ,
@@ -538,7 +549,7 @@ def transition(
538549 target : _StateValue | State | None = None ,
539550 on_error : _StateValue | None = None ,
540551 conditions : list [Callable [[Any ], bool ]] = [],
541- permission : str | Callable [[ models . Model , UserWithPermissions ], bool ] | None = None ,
552+ permission : _Permission | None = None ,
542553 custom : dict [str , _StrOrPromise ] = {},
543554) -> Callable [[Any ], Any ]:
544555 """
@@ -548,21 +559,22 @@ def transition(
548559 has not changed after the function call.
549560 """
550561
551- def inner_transition (func : _ToDo ) -> _ToDo :
562+ def inner_transition (func : Incomplete ) -> Incomplete :
552563 wrapper_installed , fsm_meta = True , getattr (func , "_django_fsm" , None )
553564 if not fsm_meta :
554565 wrapper_installed = False
555566 fsm_meta = FSMMeta (field = field , method = func )
556567 setattr (func , "_django_fsm" , fsm_meta )
557568
569+ # if isinstance(source, Iterable):
558570 if isinstance (source , (list , tuple , set )):
559571 for state in source :
560572 func ._django_fsm .add_transition (func , state , target , on_error , conditions , permission , custom )
561573 else :
562574 func ._django_fsm .add_transition (func , source , target , on_error , conditions , permission , custom )
563575
564576 @wraps (func )
565- def _change_state (instance : _Instance , * args : Any , ** kwargs : Any ) -> _ToDo :
577+ def _change_state (instance : _Instance , * args : Any , ** kwargs : Any ) -> Incomplete :
566578 return fsm_meta .field .change_state (instance , func , * args , ** kwargs )
567579
568580 if not wrapper_installed :
@@ -573,7 +585,7 @@ def _change_state(instance: _Instance, *args: Any, **kwargs: Any) -> _ToDo:
573585 return inner_transition
574586
575587
576- def can_proceed (bound_method : _ToDo , check_conditions : bool = True ) -> bool :
588+ def can_proceed (bound_method : Incomplete , check_conditions : bool = True ) -> bool :
577589 """
578590 Returns True if model in state allows to call bound_method
579591
@@ -590,7 +602,7 @@ def can_proceed(bound_method: _ToDo, check_conditions: bool = True) -> bool:
590602 return meta .has_transition (current_state ) and (not check_conditions or meta .conditions_met (self , current_state ))
591603
592604
593- def has_transition_perm (bound_method : _ToDo , user : UserWithPermissions ) -> bool :
605+ def has_transition_perm (bound_method : Incomplete , user : UserWithPermissions ) -> bool :
594606 """
595607 Returns True if model in state allows to call bound_method and user have rights on it
596608 """
@@ -609,15 +621,15 @@ def has_transition_perm(bound_method: _ToDo, user: UserWithPermissions) -> bool:
609621
610622
611623class State :
612- def get_state (self , model : _Model , transition : Transition , result : Any , args : Any = [], kwargs : Any = {}) -> _ToDo :
624+ def get_state (self , model : _FSMModel , transition : Transition , result : Any , args : Any = [], kwargs : Any = {}) -> Incomplete :
613625 raise NotImplementedError
614626
615627
616628class RETURN_VALUE (State ):
617629 def __init__ (self , * allowed_states : Sequence [_StateValue ]) -> None :
618630 self .allowed_states = allowed_states if allowed_states else None
619631
620- def get_state (self , model : _Model , transition : Transition , result : Any , args : Any = [], kwargs : Any = {}) -> _ToDo :
632+ def get_state (self , model : _FSMModel , transition : Transition , result : Any , args : Any = [], kwargs : Any = {}) -> Incomplete :
621633 if self .allowed_states is not None :
622634 if result not in self .allowed_states :
623635 raise InvalidResultState (f"{ result } is not in list of allowed states\n { self .allowed_states } " )
@@ -630,8 +642,8 @@ def __init__(self, func: Callable[..., _StateValue | Any], states: Sequence[_Sta
630642 self .allowed_states = states
631643
632644 def get_state (
633- self , model : _Model , transition : Transition , result : _StateValue | Any , args : Any = [], kwargs : Any = {}
634- ) -> _ToDo :
645+ self , model : _FSMModel , transition : Transition , result : _StateValue | Any , args : Any = [], kwargs : Any = {}
646+ ) -> Incomplete :
635647 result_state = self .func (model , * args , ** kwargs )
636648 if self .allowed_states is not None :
637649 if result_state not in self .allowed_states :
0 commit comments