Skip to content

Commit e43a9c0

Browse files
authored
75 make mypy run in strict mode (#76)
* Ensure @OverRide is added to every overriding method/property (#74) Add @OverRide decorators and make mypy check for missing override decorators * Make mypy run in strict mode, fix violating mypy errors
1 parent e5c4684 commit e43a9c0

File tree

16 files changed

+340
-265
lines changed

16 files changed

+340
-265
lines changed

.github/workflows/python-package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
flake8 . --count --max-complexity=10 --max-line-length=180 --show-source --statistics
3232
- name: Type check with mypy
3333
run: |
34-
mypy src
34+
mypy src --strict
3535
- name: Test with pytest
3636
run: |
3737
pytest --cov --cov-fail-under=95

experimentation.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from typing import override
2+
3+
4+
class Base:
5+
def some_method(self) -> str:
6+
return "This is a method from the Base class."
7+
8+
9+
class Sub(Base):
10+
def some_method(self) -> str:
11+
return "This is a method from the Sub class."
12+
13+
@override
14+
def some_methods(self) -> str:
15+
return "This is a method from the Sub class."

src/spellbind/actions.py

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import itertools
44
from abc import ABC, abstractmethod
5-
from typing import Generic, SupportsIndex, Iterable, TypeVar, Callable
5+
from typing import Generic, SupportsIndex, Iterable, TypeVar, Callable, Any
66

77
from typing_extensions import override
88

@@ -20,7 +20,7 @@ def is_permutation_only(self) -> bool: ...
2020
def map(self, transformer: Callable[[_S_co], _T]) -> CollectionAction[_T]: ...
2121

2222
@override
23-
def __repr__(self):
23+
def __repr__(self) -> str:
2424
return f"{self.__class__.__name__}()"
2525

2626

@@ -98,12 +98,12 @@ def map(self, transformer: Callable[[_S_co], _T]) -> AddOneAction[_T]:
9898
return SimpleAddOneAction(transformer(self.value))
9999

100100
@override
101-
def __repr__(self):
101+
def __repr__(self) -> str:
102102
return f"{self.__class__.__name__}(value={self.value})"
103103

104104

105105
class SimpleAddOneAction(AddOneAction[_S_co], Generic[_S_co]):
106-
def __init__(self, item: _S_co):
106+
def __init__(self, item: _S_co) -> None:
107107
self._item = item
108108

109109
@property
@@ -123,12 +123,12 @@ def map(self, transformer: Callable[[_S_co], _T]) -> RemoveOneAction[_T]:
123123
return SimpleRemoveOneAction(transformer(self.value))
124124

125125
@override
126-
def __repr__(self):
126+
def __repr__(self) -> str:
127127
return f"{self.__class__.__name__}(value={self.value})"
128128

129129

130130
class SimpleRemoveOneAction(RemoveOneAction[_S_co], Generic[_S_co]):
131-
def __init__(self, item: _S_co):
131+
def __init__(self, item: _S_co) -> None:
132132
self._item = item
133133

134134
@property
@@ -167,13 +167,13 @@ def changes(self) -> Iterable[OneElementChangedAction[_S_co]]:
167167
return self._changes
168168

169169
@override
170-
def __eq__(self, other):
170+
def __eq__(self, other: object) -> bool:
171171
if not isinstance(other, ElementsChangedAction):
172172
return NotImplemented
173173
return self.changes == other.changes
174174

175175
@override
176-
def __repr__(self):
176+
def __repr__(self) -> str:
177177
return f"{self.__class__.__name__}(changes={self.changes})"
178178

179179

@@ -212,22 +212,22 @@ def old_item(self) -> _S_co:
212212
return self._old_item
213213

214214
@override
215-
def __eq__(self, other):
215+
def __eq__(self, other: object) -> bool:
216216
if not isinstance(other, OneElementChangedAction):
217217
return NotImplemented
218-
return (self.new_item == other.new_item and
219-
self.old_item == other.old_item)
218+
# mypy --strict complains that equality between two "Any" does return Any, not bool
219+
return bool(self.new_item == other.new_item and self.old_item == other.old_item)
220220

221221
@override
222-
def __repr__(self):
222+
def __repr__(self) -> str:
223223
return f"{self.__class__.__name__}(new_item={self.new_item}, old_item={self.old_item})"
224224

225225

226-
CLEAR_ACTION: ClearAction = ClearAction()
226+
_CLEAR_ACTION: ClearAction[Any] = ClearAction()
227227

228228

229229
def clear_action() -> ClearAction[_S_co]:
230-
return CLEAR_ACTION # type: ignore[return-value]
230+
return _CLEAR_ACTION
231231

232232

233233
class SequenceAction(CollectionAction[_S_co], Generic[_S_co], ABC):
@@ -272,7 +272,7 @@ def delta_actions(self) -> tuple[AtIndexDeltaAction[_S_co], ...]:
272272
def map(self, transformer: Callable[[_S_co], _T]) -> AtIndexDeltaAction[_T]: ...
273273

274274
@override
275-
def __repr__(self):
275+
def __repr__(self) -> str:
276276
return f"{self.__class__.__name__}(index={self.index}, value={self.value})"
277277

278278

@@ -283,7 +283,7 @@ def map(self, transformer: Callable[[_S_co], _T]) -> InsertAction[_T]:
283283

284284

285285
class SimpleInsertAction(InsertAction[_S_co], Generic[_S_co]):
286-
def __init__(self, index: SupportsIndex, item: _S_co):
286+
def __init__(self, index: SupportsIndex, item: _S_co) -> None:
287287
self._index = index
288288
self._item = item
289289

@@ -298,10 +298,11 @@ def value(self) -> _S_co:
298298
return self._item
299299

300300
@override
301-
def __eq__(self, other):
301+
def __eq__(self, other: object) -> bool:
302302
if not isinstance(other, InsertAction):
303303
return NotImplemented
304-
return self.index == other.index and self.value == other.value
304+
# mypy --strict complains that equality between two "Any" does return Any, not bool
305+
return self.index == other.index and bool(self.value == other.value)
305306

306307

307308
class InsertAllAction(AtIndicesDeltasAction[_S_co], Generic[_S_co], ABC):
@@ -319,10 +320,11 @@ def map(self, transformer: Callable[[_S_co], _T]) -> InsertAllAction[_T]:
319320
return SimpleInsertAllAction(tuple((index, transformer(item)) for index, item in self.index_with_items))
320321

321322
@override
322-
def __eq__(self, other):
323+
def __eq__(self, other: object) -> bool:
323324
if not isinstance(other, InsertAllAction):
324325
return NotImplemented
325-
return self.index_with_items == other.index_with_items
326+
# mypy --strict complains that equality between two "Any" does return Any, not bool
327+
return bool(self.index_with_items == other.index_with_items)
326328

327329

328330
class SimpleInsertAllAction(InsertAllAction[_S_co], Generic[_S_co]):
@@ -341,14 +343,15 @@ def map(self, transformer: Callable[[_S_co], _T]) -> RemoveAtIndexAction[_T]:
341343
return SimpleRemoveAtIndexAction(self.index, transformer(self.value))
342344

343345
@override
344-
def __eq__(self, other):
346+
def __eq__(self, other: object) -> bool:
345347
if not isinstance(other, RemoveAtIndexAction):
346348
return NotImplemented
347-
return self.index == other.index and self.value == other.value
349+
# mypy --strict complains that equality between two "Any" does return Any, not bool
350+
return self.index == other.index and bool(self.value == other.value)
348351

349352

350353
class SimpleRemoveAtIndexAction(RemoveAtIndexAction[_S_co], RemoveOneAction[_S_co], Generic[_S_co]):
351-
def __init__(self, index: SupportsIndex, item: _S_co):
354+
def __init__(self, index: SupportsIndex, item: _S_co) -> None:
352355
self._index = index.__index__()
353356
self._item = item
354357

@@ -377,13 +380,13 @@ def map(self, transformer: Callable[[_S_co], _T]) -> RemoveAtIndicesAction[_T]:
377380
))
378381

379382
@override
380-
def __eq__(self, other):
383+
def __eq__(self, other: object) -> bool:
381384
if not isinstance(other, RemoveAtIndicesAction):
382385
return NotImplemented
383-
return self.removed_elements_with_index == other.removed_elements_with_index
386+
return bool(self.removed_elements_with_index == other.removed_elements_with_index)
384387

385388
@override
386-
def __repr__(self):
389+
def __repr__(self) -> str:
387390
return f"{self.__class__.__name__}({self.removed_elements_with_index})"
388391

389392

@@ -439,16 +442,17 @@ def map(self, transformer: Callable[[_S_co], _T]) -> SetAtIndexAction[_T]:
439442
return SimpleSetAtIndexAction(self.index, old_item=transformer(self.old_item), new_item=transformer(self.new_item))
440443

441444
@override
442-
def __repr__(self):
445+
def __repr__(self) -> str:
443446
return f"{self.__class__.__name__}(index={self.index}, old_item={self.old_item}, new_item={self.new_item})"
444447

445448
@override
446-
def __eq__(self, other):
449+
def __eq__(self, other: object) -> bool:
447450
if not isinstance(other, SetAtIndexAction):
448451
return NotImplemented
452+
# mypy --strict complains that equality between two "Any" does return Any, not bool
449453
return (self.index == other.index and
450-
self.old_item == other.old_item and
451-
self.new_item == other.new_item)
454+
bool(self.old_item == other.old_item and
455+
self.new_item == other.new_item))
452456

453457

454458
class SimpleSetAtIndexAction(SetAtIndexAction[_S_co], Generic[_S_co]):
@@ -498,15 +502,16 @@ def map(self, transformer: Callable[[_S_co], _T]) -> SliceSetAction[_T]:
498502
old_items=tuple(transformer(item) for item in self.old_items))
499503

500504
@override
501-
def __eq__(self, other):
505+
def __eq__(self, other: object) -> bool:
502506
if not isinstance(other, SliceSetAction):
503507
return NotImplemented
508+
# mypy --strict complains that equality between two "Any" does return Any, not bool
504509
return (self.indices == other.indices and
505-
self.new_items == other.new_items and
506-
self.old_items == other.old_items)
510+
bool(self.new_items == other.new_items and
511+
self.old_items == other.old_items))
507512

508513
@override
509-
def __repr__(self):
514+
def __repr__(self) -> str:
510515
return (f"{self.__class__.__name__}(indices={self.indices}, "
511516
f"new_items={self.new_items}, old_items={self.old_items})")
512517

@@ -571,13 +576,14 @@ def map(self, transformer: Callable[[_S_co], _T]) -> SetAtIndicesAction[_T]:
571576
in self.indices_with_new_and_old_items))
572577

573578
@override
574-
def __eq__(self, other):
579+
def __eq__(self, other: object) -> bool:
575580
if not isinstance(other, SetAtIndicesAction):
576581
return NotImplemented
577-
return self.indices_with_new_and_old_items == other.indices_with_new_and_old_items
582+
# mypy --strict complains that equality between two "Any" does return Any, not bool
583+
return bool(self.indices_with_new_and_old_items == other.indices_with_new_and_old_items)
578584

579585
@override
580-
def __repr__(self):
586+
def __repr__(self) -> str:
581587
return f"{self.__class__.__name__}(indices_with_new_and_old_items={self.indices_with_new_and_old_items})"
582588

583589

@@ -633,14 +639,14 @@ def map(self, transformer: Callable[[_S_co], _T]) -> ExtendAction[_T]:
633639
return SimpleExtendAction(self.old_sequence_length, tuple(transformer(item) for item in self.items))
634640

635641
@override
636-
def __eq__(self, other):
642+
def __eq__(self, other: object) -> bool:
637643
if not isinstance(other, ExtendAction):
638644
return NotImplemented
639645
return (self.old_sequence_length == other.old_sequence_length and
640646
tuple(self.items) == tuple(other.items))
641647

642648
@override
643-
def __repr__(self):
649+
def __repr__(self) -> str:
644650
return f"{self.__class__.__name__}(old_sequence_length={self.old_sequence_length}, items={self.items})"
645651

646652

@@ -660,8 +666,8 @@ def old_sequence_length(self) -> int:
660666
return self._old_sequence_length
661667

662668

663-
REVERSE_SEQUENCE_ACTION: ReverseAction = ReverseAction()
669+
REVERSE_SEQUENCE_ACTION: ReverseAction[Any] = ReverseAction()
664670

665671

666672
def reverse_action() -> ReverseAction[_S_co]:
667-
return REVERSE_SEQUENCE_ACTION # type: ignore[return-value]
673+
return REVERSE_SEQUENCE_ACTION

src/spellbind/bool_values.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,20 @@ def __xor__(self, other: BoolLike) -> BoolValue:
6060
def __rxor__(self, other: bool) -> BoolValue:
6161
return BoolValue.derive_from_two(operator.xor, other, self)
6262

63-
@overload
64-
def select(self, if_true: IntValueLike, if_false: IntValueLike) -> IntValue: ...
63+
def select_int(self, if_true: IntLike, if_false: IntLike) -> IntValue:
64+
from spellbind.int_values import IntValue
65+
return IntValue.derive_from_three(_select_function, self, if_true, if_false)
66+
67+
def select_float(self, if_true: FloatLike, if_false: FloatLike) -> FloatValue:
68+
from spellbind.float_values import FloatValue
69+
return FloatValue.derive_from_three(_select_function, self, if_true, if_false)
70+
71+
def select_bool(self, if_true: BoolLike, if_false: BoolLike) -> BoolValue:
72+
return BoolValue.derive_from_three(_select_function, self, if_true, if_false)
73+
74+
def select_str(self, if_true: StrLike, if_false: StrLike) -> StrValue:
75+
from spellbind.str_values import StrValue
76+
return StrValue.derive_from_three(_select_function, self, if_true, if_false)
6577

6678
@overload
6779
def select(self, if_true: FloatValueLike, if_false: FloatValueLike) -> FloatValue: ...
@@ -75,19 +87,21 @@ def select(self, if_true: BoolValue, if_false: BoolValue) -> BoolValue: ...
7587
@overload
7688
def select(self, if_true: Value[_S] | _S, if_false: Value[_S] | _S) -> Value[_S]: ...
7789

78-
def select(self, if_true, if_false):
90+
def select(self, if_true: Value[_S] | _S, if_false: Value[_S] | _S) -> Value[_S]:
91+
from spellbind.str_values import StrValue
7992
from spellbind.float_values import FloatValue
8093
from spellbind.int_values import IntValue
81-
from spellbind.str_values import StrValue
8294

95+
# suppressing errors, because it seems mypy does not understand the connection between
96+
# parameter type and return type as it could be inferred from the overloads
8397
if isinstance(if_true, (FloatValue, float)) and isinstance(if_false, (FloatValue, float)):
84-
return FloatValue.derive_from_three(_select_function, self, if_true, if_false)
98+
return self.select_float(if_true, if_false) # type: ignore[return-value]
8599
elif isinstance(if_true, (StrValue, str)) and isinstance(if_false, (StrValue, str)):
86-
return StrValue.derive_from_three(_select_function, self, if_true, if_false)
100+
return self.select_str(if_true, if_false) # type: ignore[return-value]
87101
elif isinstance(if_true, (BoolValue, bool)) and isinstance(if_false, (BoolValue, bool)):
88-
return BoolValue.derive_from_three(_select_function, self, if_true, if_false)
102+
return self.select_bool(if_true, if_false) # type: ignore[return-value]
89103
elif isinstance(if_true, (IntValue, int)) and isinstance(if_false, (IntValue, int)):
90-
return IntValue.derive_from_three(_select_function, self, if_true, if_false)
104+
return self.select_int(if_true, if_false) # type: ignore[return-value]
91105
else:
92106
return Value.derive_three_value(_select_function, self, if_true, if_false)
93107

@@ -127,7 +141,7 @@ class OneToBoolValue(OneToOneValue[_S, bool], BoolValue, Generic[_S]):
127141

128142

129143
class NotBoolValue(OneToOneValue[bool, bool], BoolValue):
130-
def __init__(self, value: Value[bool]):
144+
def __init__(self, value: Value[bool]) -> None:
131145
super().__init__(operator.not_, value)
132146

133147

src/spellbind/event.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Callable, TypeVar, Generic, Iterable, Sequence
1+
from typing import Callable, TypeVar, Generic, Iterable, Sequence, Any
22

33
from typing_extensions import override
44

@@ -10,7 +10,7 @@
1010
_S = TypeVar("_S")
1111
_T = TypeVar("_T")
1212
_U = TypeVar("_U")
13-
_O = TypeVar('_O', bound=Callable)
13+
_O = TypeVar('_O', bound=Callable[..., Any])
1414

1515

1616
class Event(_BaseObservable[Observer], Observable, Emitter):

0 commit comments

Comments
 (0)