Skip to content

Commit d726a61

Browse files
authored
Merge pull request #138 from binary-butterfly/more-strict-mypy-rules
Enable more strict mypy rules
2 parents 855412f + 8965534 commit d726a61

43 files changed

Lines changed: 191 additions & 48 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pyproject.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,21 @@ explicit_package_bases = true
1919
strict = true
2020

2121
# Enable further checks that are not included in strict mode
22+
disallow_any_unimported = true
23+
strict_equality_for_none = true
24+
warn_unreachable = true
2225
enable_error_code = [
2326
"deprecated",
27+
"explicit-override",
28+
"ignore-without-code",
29+
# TODO: Maybe enable this is in the future (when we have the mypy plugin)
30+
# "mutable-override",
31+
"possibly-undefined",
32+
"redundant-expr",
33+
"redundant-self",
34+
"truthy-bool",
35+
"truthy-iterable",
36+
"unused-awaitable",
2437
]
2538

2639
[[tool.mypy.overrides]]

src/validataclass/dataclasses/defaults.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from copy import deepcopy
1111
from typing import Any, Generic, TypeVar
1212

13-
from typing_extensions import Never, Self
13+
from typing_extensions import Never, Self, override
1414

1515
from validataclass.helpers import UnsetValue, UnsetValueType
1616

@@ -35,9 +35,11 @@ class BaseDefault(Generic[T_Default], ABC):
3535
See also: `Default`, `DefaultFactory()`, `DefaultUnset`, `NoDefault`
3636
"""
3737

38+
@override
3839
def __repr__(self) -> str:
3940
return type(self).__name__
4041

42+
@override
4143
def __eq__(self, other: Any) -> bool:
4244
return NotImplemented
4345

@@ -79,9 +81,11 @@ def __init__(self, value: T_Default):
7981
# If copying the value resulted in the identical object, no factory is needed
8082
self._needs_factory = self._value is not value
8183

84+
@override
8285
def __repr__(self) -> str:
8386
return f'{type(self).__name__}({self._value!r})'
8487

88+
@override
8589
def __eq__(self, other: Any) -> bool:
8690
# Only handle this if self is of the same type as other OR self is a subclass of other.
8791
# In other words, don't handle this if other is a completely different type or more specialized than self.
@@ -90,6 +94,7 @@ def __eq__(self, other: Any) -> bool:
9094
return isinstance(other, Default) and bool(self._value == other._value)
9195
return NotImplemented
9296

97+
@override
9398
def __hash__(self) -> int:
9499
return hash(self._value)
95100

@@ -105,12 +110,14 @@ def __call__(self) -> Self:
105110
return self
106111
raise TypeError(f"'{type(self).__name__}' object is not callable")
107112

113+
@override
108114
def get_value(self) -> T_Default:
109115
"""
110116
Get actual default value.
111117
"""
112118
return deepcopy(self._value)
113119

120+
@override
114121
def needs_factory(self) -> bool:
115122
"""
116123
Return True if a dataclass `default_factory` is needed for this default object, for example if the value is a
@@ -144,9 +151,11 @@ class DefaultFactory(BaseDefault[T_Default]):
144151
def __init__(self, factory: Callable[[], T_Default]):
145152
self._factory = factory
146153

154+
@override
147155
def __repr__(self) -> str:
148156
return f'{type(self).__name__}({self._factory!r})'
149157

158+
@override
150159
def __eq__(self, other: Any) -> bool:
151160
# Only handle this if self is of the same type as other OR self is a subclass of other.
152161
# In other words, don't handle this if other is a completely different type or more specialized than self.
@@ -155,15 +164,18 @@ def __eq__(self, other: Any) -> bool:
155164
return isinstance(other, DefaultFactory) and bool(self._factory == other._factory)
156165
return NotImplemented
157166

167+
@override
158168
def __hash__(self) -> int:
159169
return hash(self._factory)
160170

171+
@override
161172
def get_value(self) -> T_Default:
162173
"""
163174
Get an actual default value by calling the factory function.
164175
"""
165176
return self._factory()
166177

178+
@override
167179
def needs_factory(self) -> bool:
168180
"""
169181
Return True if a dataclass `default_factory` is needed for this default object.
@@ -185,18 +197,22 @@ class _NoDefault(BaseDefault[Never]):
185197
A validataclass field with `NoDefault` is equivalent to a validataclass field without specified default.
186198
"""
187199

200+
@override
188201
def __repr__(self) -> str:
189202
return 'NoDefault'
190203

204+
@override
191205
def __eq__(self, other: Any) -> bool:
192206
# Nothing is equal to NoDefault except itself
193207
return self is other
194208

195209
__hash__ = BaseDefault.__hash__
196210

211+
@override
197212
def get_value(self) -> Never:
198213
raise ValueError('No default value specified!')
199214

215+
@override
200216
def needs_factory(self) -> bool:
201217
raise NotImplementedError('NoDefault can be used neither as a value nor as a factory.')
202218

@@ -212,5 +228,5 @@ def __call__(self) -> Self:
212228

213229
# Create sentinel object NoDefault, redefine __new__ to always return the same instance, and delete temporary class
214230
NoDefault = _NoDefault()
215-
_NoDefault.__new__ = lambda cls: NoDefault # type: ignore
231+
_NoDefault.__new__ = lambda cls: NoDefault # type: ignore[assignment, method-assign, return-value]
216232
del _NoDefault

src/validataclass/exceptions/base_exceptions.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
from typing import Any
88

9+
from typing_extensions import override
10+
911
__all__ = [
1012
'ValidationError',
1113
]
@@ -38,10 +40,12 @@ def __init__(self, *, code: str | None = None, reason: str | None = None, **kwar
3840
self.reason = reason
3941
self.extra_data = {key: value for key, value in kwargs.items() if value is not None}
4042

43+
@override
4144
def __repr__(self) -> str:
42-
params_string = ', '.join(f'{key}={value}' for key, value in self._get_repr_dict().items() if value is not None)
45+
params_string = ', '.join(f'{key}={value}' for key, value in self._get_repr_dict().items())
4346
return f'{type(self).__name__}({params_string})'
4447

48+
@override
4549
def __str__(self) -> str:
4650
return self.__repr__()
4751

src/validataclass/exceptions/common_exceptions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
from typing import Any
88

9+
from typing_extensions import override
10+
911
from .base_exceptions import ValidationError
1012

1113
__all__ = [
@@ -69,6 +71,7 @@ def add_expected_type(self, new_type: type | str) -> None:
6971
if new_type not in self.expected_types:
7072
self.expected_types.append(new_type)
7173

74+
@override
7275
def to_dict(self) -> dict[str, Any]:
7376
base_dict = super().to_dict()
7477
self.expected_types.sort()

src/validataclass/exceptions/dataclass_exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
from typing import Any
88

9+
from typing_extensions import override
10+
911
from .base_exceptions import ValidationError
1012

1113
__all__ = [
@@ -74,6 +76,7 @@ def __init__(
7476
assert all(isinstance(error, ValidationError) for error in field_errors.values())
7577
self.field_errors = field_errors
7678

79+
@override
7780
def _get_repr_dict(self) -> dict[str, str]:
7881
base_dict = super()._get_repr_dict()
7982

@@ -84,6 +87,7 @@ def _get_repr_dict(self) -> dict[str, str]:
8487

8588
return base_dict
8689

90+
@override
8791
def to_dict(self) -> dict[str, Any]:
8892
base_dict = super().to_dict()
8993

src/validataclass/exceptions/dict_exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
from typing import Any
88

9+
from typing_extensions import override
10+
911
from .base_exceptions import ValidationError
1012

1113
__all__ = [
@@ -33,13 +35,15 @@ def __init__(self, *, field_errors: dict[str, ValidationError], **kwargs: Any):
3335
assert all(isinstance(error, ValidationError) for error in field_errors.values())
3436
self.field_errors = field_errors
3537

38+
@override
3639
def _get_repr_dict(self) -> dict[str, str]:
3740
base_dict = super()._get_repr_dict()
3841
return {
3942
**base_dict,
4043
'field_errors': repr(self.field_errors),
4144
}
4245

46+
@override
4347
def to_dict(self) -> dict[str, Any]:
4448
base_dict = super().to_dict()
4549
return {

src/validataclass/exceptions/list_exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
from typing import Any
88

9+
from typing_extensions import override
10+
911
from .base_exceptions import ValidationError
1012

1113
__all__ = [
@@ -32,13 +34,15 @@ def __init__(self, *, item_errors: dict[int, ValidationError], **kwargs: Any):
3234
assert all(isinstance(error, ValidationError) for error in item_errors.values())
3335
self.item_errors = item_errors
3436

37+
@override
3538
def _get_repr_dict(self) -> dict[str, str]:
3639
base_dict = super()._get_repr_dict()
3740
return {
3841
**base_dict,
3942
'item_errors': repr(self.item_errors),
4043
}
4144

45+
@override
4246
def to_dict(self) -> dict[str, Any]:
4347
base_dict = super().to_dict()
4448
return {

src/validataclass/helpers/datetime_range.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from datetime import datetime, timedelta, timezone, tzinfo
1010
from typing import TypeAlias
1111

12+
from typing_extensions import override
13+
1214
__all__ = [
1315
'BaseDateTimeRange',
1416
'DateTimeRange',
@@ -101,9 +103,11 @@ def __init__(
101103
self.lower_boundary = lower_boundary
102104
self.upper_boundary = upper_boundary
103105

106+
@override
104107
def __repr__(self) -> str:
105108
return f'{type(self).__name__}(lower_boundary={self.lower_boundary!r}, upper_boundary={self.upper_boundary!r})'
106109

110+
@override
107111
def contains_datetime(self, dt: datetime, local_timezone: tzinfo | None = None) -> bool:
108112
"""
109113
Returns `True` if the datetime is contained in the datetime range.
@@ -117,6 +121,7 @@ def contains_datetime(self, dt: datetime, local_timezone: tzinfo | None = None)
117121
# Note: These comparisons will raise TypeErrors when mixing datetimes with and without timezones
118122
return (lower_datetime is None or dt >= lower_datetime) and (upper_datetime is None or dt <= upper_datetime)
119123

124+
@override
120125
def to_dict(self, local_timezone: tzinfo | None = None) -> dict[str, str]:
121126
"""
122127
Returns a dictionary with string representations of the range boundaries, suitable for the `DateTimeRangeError`
@@ -204,12 +209,14 @@ def __init__(
204209
self.offset_minus = offset_minus
205210
self.offset_plus = offset_plus
206211

212+
@override
207213
def __repr__(self) -> str:
208214
return (
209215
f'{type(self).__name__}(pivot={self.pivot!r}, offset_minus={self.offset_minus!r}, '
210216
f'offset_plus={self.offset_plus!r})'
211217
)
212218

219+
@override
213220
def contains_datetime(self, dt: datetime, local_timezone: tzinfo | None = None) -> bool:
214221
"""
215222
Returns `True` if the datetime is contained in the datetime range.
@@ -223,6 +230,7 @@ def contains_datetime(self, dt: datetime, local_timezone: tzinfo | None = None)
223230
# Note: These comparisons will raise TypeErrors when mixing datetimes with and without timezones
224231
return lower_datetime <= dt <= upper_datetime
225232

233+
@override
226234
def to_dict(self, local_timezone: tzinfo | None = None) -> dict[str, str]:
227235
"""
228236
Returns a dictionary with string representations of the range boundaries (calculating `lower_datetime` and

src/validataclass/helpers/unset_value.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from typing import TypeAlias, TypeVar
88

9-
from typing_extensions import Self
9+
from typing_extensions import Self, override
1010

1111
__all__ = [
1212
'UnsetValue',
@@ -33,9 +33,11 @@ class UnsetValueType:
3333
def __call__(self) -> Self:
3434
return self
3535

36+
@override
3637
def __repr__(self) -> str:
3738
return 'UnsetValue'
3839

40+
@override
3941
def __str__(self) -> str:
4042
return '<UnsetValue>'
4143

@@ -48,7 +50,7 @@ def __bool__(self) -> bool:
4850

4951
# Create sentinel object and redefine __new__ so that the object cannot be cloned
5052
UnsetValue = UnsetValueType()
51-
UnsetValueType.__new__ = lambda cls: UnsetValue # type: ignore
53+
UnsetValueType.__new__ = lambda cls: UnsetValue # type: ignore[assignment, method-assign, return-value]
5254

5355
# Type alias OptionalUnset[T] for fields with DefaultUnset: Allows either the type T or UnsetValue
5456
OptionalUnset: TypeAlias = T | UnsetValueType

src/validataclass/validators/allow_empty_string.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from copy import deepcopy
88
from typing import Any, overload
99

10-
from typing_extensions import Generic, TypeVar
10+
from typing_extensions import Generic, TypeVar, override
1111

1212
from validataclass.exceptions import InvalidTypeError
1313
from .validator import Validator
@@ -79,6 +79,7 @@ def __init__(self, validator: Validator[T_WrappedValidated], *, default: Any = '
7979
self.wrapped_validator = validator
8080
self.default_value = default
8181

82+
@override
8283
def validate(self, input_data: Any, **kwargs: Any) -> T_WrappedValidated | T_EmptyStringDefault:
8384
"""
8485
Validates input data.

0 commit comments

Comments
 (0)