Skip to content

Commit 10b9b01

Browse files
committed
Step 4
1 parent 43d61fa commit 10b9b01

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
@@ -36,6 +36,7 @@
3636
if TYPE_CHECKING:
3737
from collections.abc import Callable
3838
from collections.abc import Generator
39+
from collections.abc import Iterable
3940
from collections.abc import Sequence
4041
from typing import Any
4142

@@ -47,6 +48,9 @@
4748
CharField = models.CharField[str, str]
4849
IntegerField = models.IntegerField[int, int]
4950
ForeignKey = models.ForeignKey[Any, Any]
51+
52+
_Instance = models.Model # TODO: use real type
53+
_ToDo = Any # TODO: use real type
5054
else:
5155
_Model = object
5256
_Field = object
@@ -79,12 +83,12 @@ class ConcurrentTransition(Exception):
7983
class Transition:
8084
def __init__(
8185
self,
82-
method: Callable[..., Any],
86+
method: Callable[..., str | int | None],
8387
source: str | int | Sequence[str | int] | State,
84-
target: str | int | State | None,
88+
target: str | int,
8589
on_error: str | int | None,
86-
conditions: list[Callable[[Any], bool]],
87-
permission: str | Callable[[models.Model, UserWithPermissions], bool] | None,
90+
conditions: list[Callable[[_Instance], bool]],
91+
permission: str | Callable[[_Instance, UserWithPermissions], bool] | None,
8892
custom: dict[str, _StrOrPromise],
8993
) -> None:
9094
self.method = method
@@ -99,7 +103,7 @@ def __init__(
99103
def name(self) -> str:
100104
return self.method.__name__
101105

102-
def has_perm(self, instance, user: UserWithPermissions) -> bool:
106+
def has_perm(self, instance: _Instance, user: UserWithPermissions) -> bool:
103107
if not self.permission:
104108
return True
105109
if callable(self.permission):
@@ -122,7 +126,7 @@ def __eq__(self, other):
122126
return False
123127

124128

125-
def get_available_FIELD_transitions(instance, field: FSMFieldMixin) -> Generator[Transition, None, None]:
129+
def get_available_FIELD_transitions(instance: _Instance, field: FSMFieldMixin) -> Generator[Transition, None, None]:
126130
"""
127131
List of transitions available in current model state
128132
with all conditions met
@@ -136,15 +140,15 @@ def get_available_FIELD_transitions(instance, field: FSMFieldMixin) -> Generator
136140
yield meta.get_transition(curr_state)
137141

138142

139-
def get_all_FIELD_transitions(instance, field: FSMFieldMixin) -> Generator[Transition, None, None]:
143+
def get_all_FIELD_transitions(instance: _Instance, field: FSMFieldMixin) -> Generator[Transition, None, None]:
140144
"""
141145
List of all transitions available in current model state
142146
"""
143147
return field.get_all_transitions(instance.__class__)
144148

145149

146150
def get_available_user_FIELD_transitions(
147-
instance, user: UserWithPermissions, field: FSMFieldMixin
151+
instance: _Instance, user: UserWithPermissions, field: FSMFieldMixin
148152
) -> Generator[Transition, None, None]:
149153
"""
150154
List of transitions available in current model state
@@ -160,11 +164,11 @@ class FSMMeta:
160164
Models methods transitions meta information
161165
"""
162166

163-
def __init__(self, field, method) -> None:
167+
def __init__(self, field: FSMFieldMixin, method: Any) -> None:
164168
self.field = field
165-
self.transitions: dict[str, Any] = {} # source -> Transition
169+
self.transitions: dict[str, Transition] = {} # source -> Transition
166170

167-
def get_transition(self, source: str):
171+
def get_transition(self, source: str) -> Transition | None:
168172
transition = self.transitions.get(source, None)
169173
if transition is None:
170174
transition = self.transitions.get("*", None)
@@ -174,12 +178,12 @@ def get_transition(self, source: str):
174178

175179
def add_transition(
176180
self,
177-
method: Callable[..., Any],
181+
method: Callable[..., str | int | None],
178182
source: str,
179183
target: str | int,
180184
on_error: str | int | None = None,
181-
conditions: list[Callable[[Any], bool]] = [],
182-
permission: str | Callable[[models.Model, UserWithPermissions], bool] | None = None,
185+
conditions: list[Callable[[_Instance], bool]] = [],
186+
permission: str | Callable[[_Instance, UserWithPermissions], bool] | None = None,
183187
custom: dict[str, _StrOrPromise] = {},
184188
) -> None:
185189
if source in self.transitions:
@@ -195,7 +199,7 @@ def add_transition(
195199
custom=custom,
196200
)
197201

198-
def has_transition(self, state) -> bool:
202+
def has_transition(self, state: str) -> bool:
199203
"""
200204
Lookup if any transition exists from current model state using current method
201205
"""
@@ -210,7 +214,7 @@ def has_transition(self, state) -> bool:
210214

211215
return False
212216

213-
def conditions_met(self, instance, state) -> bool:
217+
def conditions_met(self, instance: _Instance, state: str) -> bool:
214218
"""
215219
Check if all conditions have been met
216220
"""
@@ -224,23 +228,23 @@ def conditions_met(self, instance, state) -> bool:
224228

225229
return all(condition(instance) for condition in transition.conditions)
226230

227-
def has_transition_perm(self, instance, state, user: UserWithPermissions) -> bool:
231+
def has_transition_perm(self, instance: _Instance, state: str, user: UserWithPermissions) -> bool:
228232
transition = self.get_transition(state)
229233

230234
if not transition:
231235
return False
232236
else:
233237
return bool(transition.has_perm(instance, user))
234238

235-
def next_state(self, current_state):
239+
def next_state(self, current_state: str) -> str | int:
236240
transition = self.get_transition(current_state)
237241

238242
if transition is None:
239243
raise TransitionNotAllowed(f"No transition from {current_state}")
240244

241245
return transition.target
242246

243-
def exception_state(self, current_state):
247+
def exception_state(self, current_state: str) -> str | int | None:
244248
transition = self.get_transition(current_state)
245249

246250
if transition is None:
@@ -250,15 +254,15 @@ def exception_state(self, current_state):
250254

251255

252256
class FSMFieldDescriptor:
253-
def __init__(self, field) -> None:
257+
def __init__(self, field: FSMFieldMixin) -> None:
254258
self.field = field
255259

256-
def __get__(self, instance, type=None):
260+
def __get__(self, instance: _Instance, type: Any | None = None) -> Any:
257261
if instance is None:
258262
return self
259263
return self.field.get_state(instance)
260264

261-
def __set__(self, instance, value) -> None:
265+
def __set__(self, instance: _Instance, value: Any) -> None:
262266
if self.field.protected and self.field.name in instance.__dict__:
263267
raise AttributeError(f"Direct {self.field.name} modification is not allowed")
264268

@@ -272,7 +276,7 @@ class FSMFieldMixin(_Field):
272276

273277
def __init__(self, *args: Any, **kwargs: Any) -> None:
274278
self.protected = kwargs.pop("protected", False)
275-
self.transitions: dict[Any, dict[str, Any]] = {} # cls -> (transitions name -> method)
279+
self.transitions: dict[type[_Model], dict[str, Any]] = {} # cls -> (transitions name -> method)
276280
self.state_proxy = {} # state -> ProxyClsRef
277281

278282
state_choices = kwargs.pop("state_choices", None)
@@ -289,21 +293,21 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
289293

290294
super().__init__(*args, **kwargs)
291295

292-
def deconstruct(self):
296+
def deconstruct(self) -> Any:
293297
name, path, args, kwargs = super().deconstruct()
294298
if self.protected:
295299
kwargs["protected"] = self.protected
296300
return name, path, args, kwargs
297301

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

303-
def set_state(self, instance, state: str) -> None:
307+
def set_state(self, instance: _Instance, state: str) -> None:
304308
instance.__dict__[self.name] = state
305309

306-
def set_proxy(self, instance, state: str) -> None:
310+
def set_proxy(self, instance: _Instance, state: str) -> None:
307311
"""
308312
Change class
309313
"""
@@ -324,7 +328,7 @@ def set_proxy(self, instance, state: str) -> None:
324328

325329
instance.__class__ = model
326330

327-
def change_state(self, instance, method, *args: Any, **kwargs: Any):
331+
def change_state(self, instance: _Instance, method: _ToDo, *args: Any, **kwargs: Any) -> Any:
328332
meta = method._django_fsm
329333
method_name = method.__name__
330334
current_state = self.get_state(instance)
@@ -377,7 +381,7 @@ def change_state(self, instance, method, *args: Any, **kwargs: Any):
377381

378382
return result
379383

380-
def get_all_transitions(self, instance_cls) -> Generator[Transition, None, None]:
384+
def get_all_transitions(self, instance_cls: type[_Model]) -> Generator[Transition, None, None]:
381385
"""
382386
Returns [(source, target, name, method)] for all field transitions
383387
"""
@@ -389,7 +393,7 @@ def get_all_transitions(self, instance_cls) -> Generator[Transition, None, None]
389393
for transition in meta.transitions.values():
390394
yield transition
391395

392-
def contribute_to_class(self, cls, name, private_only=False, **kwargs):
396+
def contribute_to_class(self, cls: type[_Model], name: str, private_only: bool = False, **kwargs: Any) -> None:
393397
self.base_cls = cls
394398

395399
super().contribute_to_class(cls, name, private_only=private_only, **kwargs)
@@ -404,7 +408,7 @@ def contribute_to_class(self, cls, name, private_only=False, **kwargs):
404408

405409
class_prepared.connect(self._collect_transitions)
406410

407-
def _collect_transitions(self, *args: Any, **kwargs: Any):
411+
def _collect_transitions(self, *args: Any, **kwargs: Any) -> None:
408412
sender = kwargs["sender"]
409413

410414
if not issubclass(sender, self.base_cls):
@@ -456,10 +460,10 @@ class FSMKeyField(FSMFieldMixin, ForeignKey):
456460
State Machine support for Django model
457461
"""
458462

459-
def get_state(self, instance):
463+
def get_state(self, instance: _Instance) -> _ToDo:
460464
return instance.__dict__[self.attname]
461465

462-
def set_state(self, instance, state):
466+
def set_state(self, instance: _Instance, state: str) -> None:
463467
instance.__dict__[self.attname] = self.to_python(state)
464468

465469

@@ -521,7 +525,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
521525
self._update_initial_state()
522526

523527
@property
524-
def state_fields(self):
528+
def state_fields(self) -> Iterable[Any]:
525529
return filter(lambda field: isinstance(field, FSMFieldMixin), self._meta.fields)
526530

527531
def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_update):
@@ -567,14 +571,14 @@ def save(self, *args: Any, **kwargs: Any) -> None:
567571

568572

569573
def transition(
570-
field,
574+
field: FSMFieldMixin,
571575
source: str | int | Sequence[str | int] | State = "*",
572576
target: str | int | State | None = None,
573577
on_error: str | int | None = None,
574578
conditions: list[Callable[[Any], bool]] = [],
575579
permission: str | Callable[[models.Model, UserWithPermissions], bool] | None = None,
576580
custom: dict[str, _StrOrPromise] = {},
577-
):
581+
) -> _ToDo:
578582
"""
579583
Method decorator to mark allowed transitions.
580584
@@ -596,7 +600,7 @@ def inner_transition(func):
596600
func._django_fsm.add_transition(func, source, target, on_error, conditions, permission, custom)
597601

598602
@wraps(func)
599-
def _change_state(instance, *args: Any, **kwargs: Any):
603+
def _change_state(instance: _Instance, *args: Any, **kwargs: Any) -> _ToDo:
600604
return fsm_meta.field.change_state(instance, func, *args, **kwargs)
601605

602606
if not wrapper_installed:
@@ -607,7 +611,7 @@ def _change_state(instance, *args: Any, **kwargs: Any):
607611
return inner_transition
608612

609613

610-
def can_proceed(bound_method, check_conditions: bool = True) -> bool:
614+
def can_proceed(bound_method: _ToDo, check_conditions: bool = True) -> bool:
611615
"""
612616
Returns True if model in state allows to call bound_method
613617
@@ -624,7 +628,7 @@ def can_proceed(bound_method, check_conditions: bool = True) -> bool:
624628
return meta.has_transition(current_state) and (not check_conditions or meta.conditions_met(self, current_state))
625629

626630

627-
def has_transition_perm(bound_method, user: UserWithPermissions) -> bool:
631+
def has_transition_perm(bound_method: _ToDo, user: UserWithPermissions) -> bool:
628632
"""
629633
Returns True if model in state allows to call bound_method and user have rights on it
630634
"""

0 commit comments

Comments
 (0)