Skip to content

Commit 21f7551

Browse files
committed
Step 4
1 parent 253e685 commit 21f7551

File tree

2 files changed

+44
-40
lines changed

2 files changed

+44
-40
lines changed

.mypy.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ disallow_any_generics = True
1919

2020
# These next few are various gradations of forcing use of type annotations
2121
disallow_untyped_calls = True
22-
; disallow_incomplete_defs = True
22+
disallow_incomplete_defs = True
2323
; disallow_untyped_defs = True
2424

2525
# This one isn't too hard to get passing, but return on investment is lower

django_fsm/__init__.py

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
if TYPE_CHECKING:
3636
from collections.abc import Callable
3737
from collections.abc import Generator
38+
from collections.abc import Iterable
3839
from collections.abc import Sequence
3940
from typing import Any
4041

@@ -46,6 +47,9 @@
4647
CharField = models.CharField[str, str]
4748
IntegerField = models.IntegerField[int, int]
4849
ForeignKey = models.ForeignKey[Any, Any]
50+
51+
_Instance = models.Model # TODO: use real type
52+
_ToDo = Any # TODO: use real type
4953
else:
5054
_Model = object
5155
_Field = object
@@ -78,12 +82,12 @@ class ConcurrentTransition(Exception):
7882
class Transition:
7983
def __init__(
8084
self,
81-
method: Callable[..., Any],
85+
method: Callable[..., str | int | None],
8286
source: str | int | Sequence[str | int] | State,
83-
target: str | int | State | None,
87+
target: str | int,
8488
on_error: str | int | None,
85-
conditions: list[Callable[[Any], bool]],
86-
permission: str | Callable[[models.Model, UserWithPermissions], bool] | None,
89+
conditions: list[Callable[[_Instance], bool]],
90+
permission: str | Callable[[_Instance, UserWithPermissions], bool] | None,
8791
custom: dict[str, _StrOrPromise],
8892
) -> None:
8993
self.method = method
@@ -98,7 +102,7 @@ def __init__(
98102
def name(self) -> str:
99103
return self.method.__name__
100104

101-
def has_perm(self, instance, user: UserWithPermissions) -> bool:
105+
def has_perm(self, instance: _Instance, user: UserWithPermissions) -> bool:
102106
if not self.permission:
103107
return True
104108
elif callable(self.permission):
@@ -111,7 +115,7 @@ def has_perm(self, instance, user: UserWithPermissions) -> bool:
111115
return False
112116

113117

114-
def get_available_FIELD_transitions(instance, field: FSMFieldMixin) -> Generator[Transition, None, None]:
118+
def get_available_FIELD_transitions(instance: _Instance, field: FSMFieldMixin) -> Generator[Transition, None, None]:
115119
"""
116120
List of transitions available in current model state
117121
with all conditions met
@@ -125,15 +129,15 @@ def get_available_FIELD_transitions(instance, field: FSMFieldMixin) -> Generator
125129
yield meta.get_transition(curr_state)
126130

127131

128-
def get_all_FIELD_transitions(instance, field: FSMFieldMixin) -> Generator[Transition, None, None]:
132+
def get_all_FIELD_transitions(instance: _Instance, field: FSMFieldMixin) -> Generator[Transition, None, None]:
129133
"""
130134
List of all transitions available in current model state
131135
"""
132136
return field.get_all_transitions(instance.__class__)
133137

134138

135139
def get_available_user_FIELD_transitions(
136-
instance, user: UserWithPermissions, field: FSMFieldMixin
140+
instance: _Instance, user: UserWithPermissions, field: FSMFieldMixin
137141
) -> Generator[Transition, None, None]:
138142
"""
139143
List of transitions available in current model state
@@ -149,11 +153,11 @@ class FSMMeta:
149153
Models methods transitions meta information
150154
"""
151155

152-
def __init__(self, field, method) -> None:
156+
def __init__(self, field: FSMFieldMixin, method: Any) -> None:
153157
self.field = field
154-
self.transitions: dict[str, Any] = {} # source -> Transition
158+
self.transitions: dict[str, Transition] = {} # source -> Transition
155159

156-
def get_transition(self, source: str):
160+
def get_transition(self, source: str) -> Transition | None:
157161
transition = self.transitions.get(source, None)
158162
if transition is None:
159163
transition = self.transitions.get("*", None)
@@ -163,12 +167,12 @@ def get_transition(self, source: str):
163167

164168
def add_transition(
165169
self,
166-
method: Callable[..., Any],
170+
method: Callable[..., str | int | None],
167171
source: str,
168172
target: str | int,
169173
on_error: str | int | None = None,
170-
conditions: list[Callable[[Any], bool]] = [],
171-
permission: str | Callable[[models.Model, UserWithPermissions], bool] | None = None,
174+
conditions: list[Callable[[_Instance], bool]] = [],
175+
permission: str | Callable[[_Instance, UserWithPermissions], bool] | None = None,
172176
custom: dict[str, _StrOrPromise] = {},
173177
) -> None:
174178
if source in self.transitions:
@@ -184,7 +188,7 @@ def add_transition(
184188
custom=custom,
185189
)
186190

187-
def has_transition(self, state) -> bool:
191+
def has_transition(self, state: str) -> bool:
188192
"""
189193
Lookup if any transition exists from current model state using current method
190194
"""
@@ -199,7 +203,7 @@ def has_transition(self, state) -> bool:
199203

200204
return False
201205

202-
def conditions_met(self, instance, state) -> bool:
206+
def conditions_met(self, instance: _Instance, state: str) -> bool:
203207
"""
204208
Check if all conditions have been met
205209
"""
@@ -212,23 +216,23 @@ def conditions_met(self, instance, state) -> bool:
212216
else:
213217
return all(map(lambda condition: condition(instance), transition.conditions))
214218

215-
def has_transition_perm(self, instance, state, user: UserWithPermissions) -> bool:
219+
def has_transition_perm(self, instance: _Instance, state: str, user: UserWithPermissions) -> bool:
216220
transition = self.get_transition(state)
217221

218222
if not transition:
219223
return False
220224
else:
221225
return bool(transition.has_perm(instance, user))
222226

223-
def next_state(self, current_state):
227+
def next_state(self, current_state: str) -> str | int:
224228
transition = self.get_transition(current_state)
225229

226230
if transition is None:
227231
raise TransitionNotAllowed(f"No transition from {current_state}")
228232

229233
return transition.target
230234

231-
def exception_state(self, current_state):
235+
def exception_state(self, current_state: str) -> str | int | None:
232236
transition = self.get_transition(current_state)
233237

234238
if transition is None:
@@ -238,15 +242,15 @@ def exception_state(self, current_state):
238242

239243

240244
class FSMFieldDescriptor:
241-
def __init__(self, field) -> None:
245+
def __init__(self, field: FSMFieldMixin) -> None:
242246
self.field = field
243247

244-
def __get__(self, instance, type=None):
248+
def __get__(self, instance: _Instance, type: Any | None = None) -> Any:
245249
if instance is None:
246250
return self
247251
return self.field.get_state(instance)
248252

249-
def __set__(self, instance, value) -> None:
253+
def __set__(self, instance: _Instance, value: Any) -> None:
250254
if self.field.protected and self.field.name in instance.__dict__:
251255
raise AttributeError(f"Direct {self.field.name} modification is not allowed")
252256

@@ -260,7 +264,7 @@ class FSMFieldMixin(_Field):
260264

261265
def __init__(self, *args: Any, **kwargs: Any) -> None:
262266
self.protected = kwargs.pop("protected", False)
263-
self.transitions: dict[Any, dict[str, Any]] = {} # cls -> (transitions name -> method)
267+
self.transitions: dict[type[_Model], dict[str, Any]] = {} # cls -> (transitions name -> method)
264268
self.state_proxy = {} # state -> ProxyClsRef
265269

266270
state_choices = kwargs.pop("state_choices", None)
@@ -277,21 +281,21 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
277281

278282
super().__init__(*args, **kwargs)
279283

280-
def deconstruct(self):
284+
def deconstruct(self) -> Any:
281285
name, path, args, kwargs = super().deconstruct()
282286
if self.protected:
283287
kwargs["protected"] = self.protected
284288
return name, path, args, kwargs
285289

286-
def get_state(self, instance) -> Any:
290+
def get_state(self, instance: _Instance) -> Any:
287291
# The state field may be deferred. We delegate the logic of figuring this out
288292
# and loading the deferred field on-demand to Django's built-in DeferredAttribute class.
289293
return DeferredAttribute(self).__get__(instance) # type: ignore[attr-defined]
290294

291-
def set_state(self, instance, state: str) -> None:
295+
def set_state(self, instance: _Instance, state: str) -> None:
292296
instance.__dict__[self.name] = state
293297

294-
def set_proxy(self, instance, state: str) -> None:
298+
def set_proxy(self, instance: _Instance, state: str) -> None:
295299
"""
296300
Change class
297301
"""
@@ -312,7 +316,7 @@ def set_proxy(self, instance, state: str) -> None:
312316

313317
instance.__class__ = model
314318

315-
def change_state(self, instance, method, *args: Any, **kwargs: Any):
319+
def change_state(self, instance: _Instance, method: _ToDo, *args: Any, **kwargs: Any) -> Any:
316320
meta = method._django_fsm
317321
method_name = method.__name__
318322
current_state = self.get_state(instance)
@@ -365,7 +369,7 @@ def change_state(self, instance, method, *args: Any, **kwargs: Any):
365369

366370
return result
367371

368-
def get_all_transitions(self, instance_cls) -> Generator[Transition, None, None]:
372+
def get_all_transitions(self, instance_cls: type[_Model]) -> Generator[Transition, None, None]:
369373
"""
370374
Returns [(source, target, name, method)] for all field transitions
371375
"""
@@ -377,7 +381,7 @@ def get_all_transitions(self, instance_cls) -> Generator[Transition, None, None]
377381
for transition in meta.transitions.values():
378382
yield transition
379383

380-
def contribute_to_class(self, cls, name, private_only=False, **kwargs):
384+
def contribute_to_class(self, cls: type[_Model], name: str, private_only: bool = False, **kwargs: Any) -> None:
381385
self.base_cls = cls
382386

383387
super().contribute_to_class(cls, name, private_only=private_only, **kwargs)
@@ -392,7 +396,7 @@ def contribute_to_class(self, cls, name, private_only=False, **kwargs):
392396

393397
class_prepared.connect(self._collect_transitions)
394398

395-
def _collect_transitions(self, *args: Any, **kwargs: Any):
399+
def _collect_transitions(self, *args: Any, **kwargs: Any) -> None:
396400
sender = kwargs["sender"]
397401

398402
if not issubclass(sender, self.base_cls):
@@ -444,10 +448,10 @@ class FSMKeyField(FSMFieldMixin, ForeignKey):
444448
State Machine support for Django model
445449
"""
446450

447-
def get_state(self, instance):
451+
def get_state(self, instance: _Instance) -> _ToDo:
448452
return instance.__dict__[self.attname]
449453

450-
def set_state(self, instance, state):
454+
def set_state(self, instance: _Instance, state: str) -> None:
451455
instance.__dict__[self.attname] = self.to_python(state)
452456

453457

@@ -482,7 +486,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
482486
self._update_initial_state()
483487

484488
@property
485-
def state_fields(self):
489+
def state_fields(self) -> Iterable[Any]:
486490
return filter(lambda field: isinstance(field, FSMFieldMixin), self._meta.fields)
487491

488492
def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_update):
@@ -528,14 +532,14 @@ def save(self, *args: Any, **kwargs: Any) -> None:
528532

529533

530534
def transition(
531-
field,
535+
field: FSMFieldMixin,
532536
source: str | int | Sequence[str | int] | State = "*",
533537
target: str | int | State | None = None,
534538
on_error: str | int | None = None,
535539
conditions: list[Callable[[Any], bool]] = [],
536540
permission: str | Callable[[models.Model, UserWithPermissions], bool] | None = None,
537541
custom: dict[str, _StrOrPromise] = {},
538-
):
542+
) -> _ToDo:
539543
"""
540544
Method decorator to mark allowed transitions.
541545
@@ -557,7 +561,7 @@ def inner_transition(func):
557561
func._django_fsm.add_transition(func, source, target, on_error, conditions, permission, custom)
558562

559563
@wraps(func)
560-
def _change_state(instance, *args: Any, **kwargs: Any):
564+
def _change_state(instance: _Instance, *args: Any, **kwargs: Any) -> _ToDo:
561565
return fsm_meta.field.change_state(instance, func, *args, **kwargs)
562566

563567
if not wrapper_installed:
@@ -568,7 +572,7 @@ def _change_state(instance, *args: Any, **kwargs: Any):
568572
return inner_transition
569573

570574

571-
def can_proceed(bound_method, check_conditions: bool = True) -> bool:
575+
def can_proceed(bound_method: _ToDo, check_conditions: bool = True) -> bool:
572576
"""
573577
Returns True if model in state allows to call bound_method
574578
@@ -585,7 +589,7 @@ def can_proceed(bound_method, check_conditions: bool = True) -> bool:
585589
return meta.has_transition(current_state) and (not check_conditions or meta.conditions_met(self, current_state))
586590

587591

588-
def has_transition_perm(bound_method, user: UserWithPermissions) -> bool:
592+
def has_transition_perm(bound_method: _ToDo, user: UserWithPermissions) -> bool:
589593
"""
590594
Returns True if model in state allows to call bound_method and user have rights on it
591595
"""

0 commit comments

Comments
 (0)