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