5
5
from __future__ import annotations
6
6
7
7
import 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
8
13
from functools import partialmethod
9
14
from functools import wraps
10
15
from typing import TYPE_CHECKING
16
+ from typing import Any
11
17
12
18
from django .apps import apps as django_apps
13
19
from django .db import models
14
20
from django .db .models import Field
21
+ from django .db .models import QuerySet
15
22
from django .db .models .query_utils import DeferredAttribute
16
23
from django .db .models .signals import class_prepared
17
24
34
41
]
35
42
36
43
if 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
42
45
46
+ from _typeshed import Incomplete
43
47
from django .contrib .auth .models import PermissionsMixin as UserWithPermissions
44
48
from django .utils .functional import _StrOrPromise
45
49
46
- _Model = models .Model
50
+ _FSMModel = models .Model
47
51
_Field = models .Field [Any , Any ]
48
52
CharField = models .CharField [Any , Any ]
49
53
IntegerField = models .IntegerField [Any , Any ]
50
54
ForeignKey = models .ForeignKey [Any , Any ]
51
55
52
56
_StateValue = str | int
57
+ _Permission = str | Callable [[_FSMModel , UserWithPermissions ], bool ]
53
58
_Instance = models .Model # TODO: use real type
54
- _ToDo = Any # TODO: use real type
59
+
55
60
else :
56
- _Model = object
61
+ _FSMModel = object
57
62
_Field = object
58
63
CharField = models .CharField
59
64
IntegerField = models .IntegerField
60
65
ForeignKey = models .ForeignKey
66
+ Self = Any
61
67
62
68
63
69
class TransitionNotAllowed (Exception ):
@@ -277,7 +283,7 @@ class FSMFieldMixin(_Field):
277
283
278
284
def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
279
285
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)
281
287
self .state_proxy = {} # state -> ProxyClsRef
282
288
283
289
state_choices = kwargs .pop ("state_choices" , None )
@@ -329,7 +335,7 @@ def set_proxy(self, instance: _Instance, state: str) -> None:
329
335
330
336
instance .__class__ = model
331
337
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 :
333
339
meta = method ._django_fsm
334
340
method_name = method .__name__
335
341
current_state = self .get_state (instance )
@@ -382,7 +388,7 @@ def change_state(self, instance: _Instance, method: _ToDo, *args: Any, **kwargs:
382
388
383
389
return result
384
390
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 ]:
386
392
"""
387
393
Returns [(source, target, name, method)] for all field transitions
388
394
"""
@@ -394,7 +400,7 @@ def get_all_transitions(self, instance_cls: type[_Model]) -> Generator[Transitio
394
400
for transition in meta .transitions .values ():
395
401
yield transition
396
402
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 :
398
404
self .base_cls = cls
399
405
400
406
super ().contribute_to_class (cls , name , private_only = private_only , ** kwargs )
@@ -415,7 +421,7 @@ def _collect_transitions(self, *args: Any, **kwargs: Any) -> None:
415
421
if not issubclass (sender , self .base_cls ):
416
422
return
417
423
418
- def is_field_transition_method (attr : _ToDo ) -> bool :
424
+ def is_field_transition_method (attr : Incomplete ) -> bool :
419
425
return (
420
426
(inspect .ismethod (attr ) or inspect .isfunction (attr ))
421
427
and hasattr (attr , "_django_fsm" )
@@ -461,14 +467,14 @@ class FSMKeyField(FSMFieldMixin, ForeignKey):
461
467
State Machine support for Django model
462
468
"""
463
469
464
- def get_state (self , instance : _Instance ) -> _ToDo :
470
+ def get_state (self , instance : _Instance ) -> Incomplete :
465
471
return instance .__dict__ [self .attname ]
466
472
467
473
def set_state (self , instance : _Instance , state : str ) -> None :
468
474
instance .__dict__ [self .attname ] = self .to_python (state )
469
475
470
476
471
- class FSMModelMixin (_Model ):
477
+ class FSMModelMixin (_FSMModel ):
472
478
"""
473
479
Mixin that allows refresh_from_db for models with fsm protected fields
474
480
"""
@@ -495,7 +501,7 @@ def refresh_from_db(self, *args, **kwargs):
495
501
super ().refresh_from_db (* args , ** kwargs )
496
502
497
503
498
- class ConcurrentTransitionMixin (_Model ):
504
+ class ConcurrentTransitionMixin (_FSMModel ):
499
505
"""
500
506
Protects a Model from undesirable effects caused by concurrently executed transitions,
501
507
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:
529
535
def state_fields (self ) -> Iterable [Any ]:
530
536
return filter (lambda field : isinstance (field , FSMFieldMixin ), self ._meta .fields )
531
537
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 :
533
547
# _do_update is called once for each model class in the inheritance hierarchy.
534
548
# We can only filter the base_qs on state fields (can be more than one!) present in this particular model.
535
549
@@ -539,7 +553,7 @@ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_updat
539
553
# state filter will be used to narrow down the standard filter checking only PK
540
554
state_filter = {field .attname : self .__initial_states [field .attname ] for field in filter_on }
541
555
542
- updated = super ()._do_update ( # type: ignore[misc]
556
+ updated : bool = super ()._do_update ( # type: ignore[misc]
543
557
base_qs = base_qs .filter (** state_filter ),
544
558
using = using ,
545
559
pk_val = pk_val ,
@@ -577,7 +591,7 @@ def transition(
577
591
target : _StateValue | State | None = None ,
578
592
on_error : _StateValue | None = None ,
579
593
conditions : list [Callable [[Any ], bool ]] = [],
580
- permission : str | Callable [[ models . Model , UserWithPermissions ], bool ] | None = None ,
594
+ permission : _Permission | None = None ,
581
595
custom : dict [str , _StrOrPromise ] = {},
582
596
) -> Callable [[Any ], Any ]:
583
597
"""
@@ -587,21 +601,22 @@ def transition(
587
601
has not changed after the function call.
588
602
"""
589
603
590
- def inner_transition (func : _ToDo ) -> _ToDo :
604
+ def inner_transition (func : Incomplete ) -> Incomplete :
591
605
wrapper_installed , fsm_meta = True , getattr (func , "_django_fsm" , None )
592
606
if not fsm_meta :
593
607
wrapper_installed = False
594
608
fsm_meta = FSMMeta (field = field , method = func )
595
609
setattr (func , "_django_fsm" , fsm_meta )
596
610
611
+ # if isinstance(source, Iterable):
597
612
if isinstance (source , (list , tuple , set )):
598
613
for state in source :
599
614
func ._django_fsm .add_transition (func , state , target , on_error , conditions , permission , custom )
600
615
else :
601
616
func ._django_fsm .add_transition (func , source , target , on_error , conditions , permission , custom )
602
617
603
618
@wraps (func )
604
- def _change_state (instance : _Instance , * args : Any , ** kwargs : Any ) -> _ToDo :
619
+ def _change_state (instance : _Instance , * args : Any , ** kwargs : Any ) -> Incomplete :
605
620
return fsm_meta .field .change_state (instance , func , * args , ** kwargs )
606
621
607
622
if not wrapper_installed :
@@ -612,7 +627,7 @@ def _change_state(instance: _Instance, *args: Any, **kwargs: Any) -> _ToDo:
612
627
return inner_transition
613
628
614
629
615
- def can_proceed (bound_method : _ToDo , check_conditions : bool = True ) -> bool :
630
+ def can_proceed (bound_method : Incomplete , check_conditions : bool = True ) -> bool :
616
631
"""
617
632
Returns True if model in state allows to call bound_method
618
633
@@ -629,7 +644,7 @@ def can_proceed(bound_method: _ToDo, check_conditions: bool = True) -> bool:
629
644
return meta .has_transition (current_state ) and (not check_conditions or meta .conditions_met (self , current_state ))
630
645
631
646
632
- def has_transition_perm (bound_method : _ToDo , user : UserWithPermissions ) -> bool :
647
+ def has_transition_perm (bound_method : Incomplete , user : UserWithPermissions ) -> bool :
633
648
"""
634
649
Returns True if model in state allows to call bound_method and user have rights on it
635
650
"""
@@ -648,15 +663,15 @@ def has_transition_perm(bound_method: _ToDo, user: UserWithPermissions) -> bool:
648
663
649
664
650
665
class 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 :
652
667
raise NotImplementedError
653
668
654
669
655
670
class RETURN_VALUE (State ):
656
671
def __init__ (self , * allowed_states : Sequence [_StateValue ]) -> None :
657
672
self .allowed_states = allowed_states if allowed_states else None
658
673
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 :
660
675
if self .allowed_states is not None :
661
676
if result not in self .allowed_states :
662
677
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
669
684
self .allowed_states = states
670
685
671
686
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 :
674
689
result_state = self .func (model , * args , ** kwargs )
675
690
if self .allowed_states is not None :
676
691
if result_state not in self .allowed_states :
0 commit comments