4
4
from __future__ import annotations
5
5
6
6
import 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
7
12
from functools import partialmethod
8
13
from functools import wraps
9
14
from typing import TYPE_CHECKING
15
+ from typing import Any
16
+ from typing import Self
10
17
11
18
from django .apps import apps as django_apps
12
19
from django .db import models
13
20
from django .db .models import Field
21
+ from django .db .models import QuerySet
14
22
from django .db .models .query_utils import DeferredAttribute
15
23
from django .db .models .signals import class_prepared
16
24
33
41
]
34
42
35
43
if 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
42
45
from django .contrib .auth .models import PermissionsMixin as UserWithPermissions
43
46
from django .utils .functional import _StrOrPromise
44
47
45
- _Model = models .Model
48
+ _FSMModel = models .Model
46
49
_Field = models .Field [Any , Any ]
47
50
CharField = models .CharField [Any , Any ]
48
51
IntegerField = models .IntegerField [Any , Any ]
49
52
ForeignKey = models .ForeignKey [Any , Any ]
50
53
51
54
_StateValue = str | int
55
+ _Permission = str | Callable [[_FSMModel , UserWithPermissions ], bool ]
52
56
_Instance = models .Model # TODO: use real type
53
- _ToDo = Any # TODO: use real type
54
57
else :
55
- _Model = object
58
+ _FSMModel = object
56
59
_Field = object
57
60
CharField = models .CharField
58
61
IntegerField = models .IntegerField
@@ -265,7 +268,7 @@ class FSMFieldMixin(_Field):
265
268
266
269
def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
267
270
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)
269
272
self .state_proxy = {} # state -> ProxyClsRef
270
273
271
274
state_choices = kwargs .pop ("state_choices" , None )
@@ -317,7 +320,7 @@ def set_proxy(self, instance: _Instance, state: str) -> None:
317
320
318
321
instance .__class__ = model
319
322
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 :
321
324
meta = method ._django_fsm
322
325
method_name = method .__name__
323
326
current_state = self .get_state (instance )
@@ -370,7 +373,7 @@ def change_state(self, instance: _Instance, method: _ToDo, *args: Any, **kwargs:
370
373
371
374
return result
372
375
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 ]:
374
377
"""
375
378
Returns [(source, target, name, method)] for all field transitions
376
379
"""
@@ -382,7 +385,7 @@ def get_all_transitions(self, instance_cls: type[_Model]) -> Generator[Transitio
382
385
for transition in meta .transitions .values ():
383
386
yield transition
384
387
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 :
386
389
self .base_cls = cls
387
390
388
391
super ().contribute_to_class (cls , name , private_only = private_only , ** kwargs )
@@ -403,7 +406,7 @@ def _collect_transitions(self, *args: Any, **kwargs: Any) -> None:
403
406
if not issubclass (sender , self .base_cls ):
404
407
return
405
408
406
- def is_field_transition_method (attr : _ToDo ) -> bool :
409
+ def is_field_transition_method (attr : Incomplete ) -> bool :
407
410
return (
408
411
(inspect .ismethod (attr ) or inspect .isfunction (attr ))
409
412
and hasattr (attr , "_django_fsm" )
@@ -449,14 +452,14 @@ class FSMKeyField(FSMFieldMixin, ForeignKey):
449
452
State Machine support for Django model
450
453
"""
451
454
452
- def get_state (self , instance : _Instance ) -> _ToDo :
455
+ def get_state (self , instance : _Instance ) -> Incomplete :
453
456
return instance .__dict__ [self .attname ]
454
457
455
458
def set_state (self , instance : _Instance , state : str ) -> None :
456
459
instance .__dict__ [self .attname ] = self .to_python (state )
457
460
458
461
459
- class ConcurrentTransitionMixin (_Model ):
462
+ class ConcurrentTransitionMixin (_FSMModel ):
460
463
"""
461
464
Protects a Model from undesirable effects caused by concurrently executed transitions,
462
465
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:
490
493
def state_fields (self ) -> Iterable [Any ]:
491
494
return filter (lambda field : isinstance (field , FSMFieldMixin ), self ._meta .fields )
492
495
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 :
494
505
# _do_update is called once for each model class in the inheritance hierarchy.
495
506
# We can only filter the base_qs on state fields (can be more than one!) present in this particular model.
496
507
@@ -500,7 +511,7 @@ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_updat
500
511
# state filter will be used to narrow down the standard filter checking only PK
501
512
state_filter = {field .attname : self .__initial_states [field .attname ] for field in filter_on }
502
513
503
- updated = super ()._do_update ( # type: ignore[misc]
514
+ updated : bool = super ()._do_update ( # type: ignore[misc]
504
515
base_qs = base_qs .filter (** state_filter ),
505
516
using = using ,
506
517
pk_val = pk_val ,
@@ -538,7 +549,7 @@ def transition(
538
549
target : _StateValue | State | None = None ,
539
550
on_error : _StateValue | None = None ,
540
551
conditions : list [Callable [[Any ], bool ]] = [],
541
- permission : str | Callable [[ models . Model , UserWithPermissions ], bool ] | None = None ,
552
+ permission : _Permission | None = None ,
542
553
custom : dict [str , _StrOrPromise ] = {},
543
554
) -> Callable [[Any ], Any ]:
544
555
"""
@@ -548,21 +559,22 @@ def transition(
548
559
has not changed after the function call.
549
560
"""
550
561
551
- def inner_transition (func : _ToDo ) -> _ToDo :
562
+ def inner_transition (func : Incomplete ) -> Incomplete :
552
563
wrapper_installed , fsm_meta = True , getattr (func , "_django_fsm" , None )
553
564
if not fsm_meta :
554
565
wrapper_installed = False
555
566
fsm_meta = FSMMeta (field = field , method = func )
556
567
setattr (func , "_django_fsm" , fsm_meta )
557
568
569
+ # if isinstance(source, Iterable):
558
570
if isinstance (source , (list , tuple , set )):
559
571
for state in source :
560
572
func ._django_fsm .add_transition (func , state , target , on_error , conditions , permission , custom )
561
573
else :
562
574
func ._django_fsm .add_transition (func , source , target , on_error , conditions , permission , custom )
563
575
564
576
@wraps (func )
565
- def _change_state (instance : _Instance , * args : Any , ** kwargs : Any ) -> _ToDo :
577
+ def _change_state (instance : _Instance , * args : Any , ** kwargs : Any ) -> Incomplete :
566
578
return fsm_meta .field .change_state (instance , func , * args , ** kwargs )
567
579
568
580
if not wrapper_installed :
@@ -573,7 +585,7 @@ def _change_state(instance: _Instance, *args: Any, **kwargs: Any) -> _ToDo:
573
585
return inner_transition
574
586
575
587
576
- def can_proceed (bound_method : _ToDo , check_conditions : bool = True ) -> bool :
588
+ def can_proceed (bound_method : Incomplete , check_conditions : bool = True ) -> bool :
577
589
"""
578
590
Returns True if model in state allows to call bound_method
579
591
@@ -590,7 +602,7 @@ def can_proceed(bound_method: _ToDo, check_conditions: bool = True) -> bool:
590
602
return meta .has_transition (current_state ) and (not check_conditions or meta .conditions_met (self , current_state ))
591
603
592
604
593
- def has_transition_perm (bound_method : _ToDo , user : UserWithPermissions ) -> bool :
605
+ def has_transition_perm (bound_method : Incomplete , user : UserWithPermissions ) -> bool :
594
606
"""
595
607
Returns True if model in state allows to call bound_method and user have rights on it
596
608
"""
@@ -609,15 +621,15 @@ def has_transition_perm(bound_method: _ToDo, user: UserWithPermissions) -> bool:
609
621
610
622
611
623
class 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 :
613
625
raise NotImplementedError
614
626
615
627
616
628
class RETURN_VALUE (State ):
617
629
def __init__ (self , * allowed_states : Sequence [_StateValue ]) -> None :
618
630
self .allowed_states = allowed_states if allowed_states else None
619
631
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 :
621
633
if self .allowed_states is not None :
622
634
if result not in self .allowed_states :
623
635
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
630
642
self .allowed_states = states
631
643
632
644
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 :
635
647
result_state = self .func (model , * args , ** kwargs )
636
648
if self .allowed_states is not None :
637
649
if result_state not in self .allowed_states :
0 commit comments