Skip to content

Commit 5a200d8

Browse files
committed
Dropped Python 3.7 support and added full generic strict Pyright and Mypy compliant type hinting
1 parent 9427331 commit 5a200d8

15 files changed

+212
-164
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
/docs/_build
55
/cover
66
/.eggs
7+
/.*

pyrightconfig.json

Lines changed: 0 additions & 8 deletions
This file was deleted.

python_utils/aio.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,19 @@
77

88
from . import types
99

10+
_N = types.TypeVar('_N', int, float)
11+
1012

1113
async def acount(
12-
start: float = 0,
13-
step: float = 1,
14+
start: _N = 0,
15+
step: _N = 1,
1416
delay: float = 0,
15-
stop: types.Optional[float] = None,
16-
) -> types.AsyncIterator[float]:
17+
stop: types.Optional[_N] = None,
18+
) -> types.AsyncIterator[_N]:
1719
'''Asyncio version of itertools.count()'''
1820
for item in itertools.count(start, step): # pragma: no branch
1921
if stop is not None and item >= stop:
2022
break
2123

22-
yield item
24+
yield types.cast(_N, item)
2325
await asyncio.sleep(delay)

python_utils/containers.py

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# pyright: reportIncompatibleMethodOverride=false
12
import abc
23
import typing
34
from typing import Any, Generator
@@ -22,28 +23,32 @@
2223

2324
# Using types.Union instead of | since Python 3.7 doesn't fully support it
2425
DictUpdateArgs = types.Union[
25-
types.Mapping,
26-
types.Iterable[types.Union[types.Tuple[Any, Any], types.Mapping]],
26+
types.Mapping[types.Any, types.Any],
27+
types.Iterable[
28+
types.Union[types.Tuple[Any, Any], types.Mapping[types.Any, types.Any]]
29+
],
2730
'_typeshed.SupportsKeysAndGetItem[KT, VT]',
2831
]
2932

3033

3134
class CastedDictBase(types.Dict[KT, VT], abc.ABC):
32-
_key_cast: KT_cast
33-
_value_cast: VT_cast
35+
_key_cast: KT_cast[KT]
36+
_value_cast: VT_cast[VT]
3437

3538
def __init__(
3639
self,
37-
key_cast: KT_cast = None,
38-
value_cast: VT_cast = None,
39-
*args: DictUpdateArgs,
40+
key_cast: KT_cast[KT] = None,
41+
value_cast: VT_cast[VT] = None,
42+
*args: DictUpdateArgs[KT, VT],
4043
**kwargs: VT,
4144
) -> None:
4245
self._value_cast = value_cast
4346
self._key_cast = key_cast
4447
self.update(*args, **kwargs)
4548

46-
def update(self, *args: DictUpdateArgs, **kwargs: VT) -> None:
49+
def update(
50+
self, *args: DictUpdateArgs[types.Any, types.Any], **kwargs: types.Any
51+
) -> None:
4752
if args:
4853
kwargs.update(*args)
4954

@@ -93,7 +98,7 @@ class CastedDict(CastedDictBase[KT, VT]):
9398
{1: 2, '3': '4', '5': '6', '7': '8'}
9499
'''
95100

96-
def __setitem__(self, key: Any, value: Any) -> None:
101+
def __setitem__(self, key: typing.Any, value: typing.Any) -> None:
97102
if self._value_cast is not None:
98103
value = self._value_cast(value)
99104

@@ -146,13 +151,13 @@ class LazyCastedDict(CastedDictBase[KT, VT]):
146151
'4'
147152
'''
148153

149-
def __setitem__(self, key: Any, value: Any) -> None:
154+
def __setitem__(self, key: types.Any, value: types.Any):
150155
if self._key_cast is not None:
151156
key = self._key_cast(key)
152157

153158
super().__setitem__(key, value)
154159

155-
def __getitem__(self, key: Any) -> VT:
160+
def __getitem__(self, key: types.Any) -> VT:
156161
if self._key_cast is not None:
157162
key = self._key_cast(key)
158163

@@ -163,9 +168,7 @@ def __getitem__(self, key: Any) -> VT:
163168

164169
return value
165170

166-
def items( # type: ignore
167-
self,
168-
) -> Generator[types.Tuple[KT, VT], None, None]:
171+
def items(self) -> Generator[tuple[KT, VT], None, None]: # type: ignore
169172
if self._value_cast is None:
170173
yield from super().items()
171174
else:
@@ -247,7 +250,7 @@ def append(self, value: HT) -> None:
247250
self._set.add(value)
248251
super().append(value)
249252

250-
def __contains__(self, item):
253+
def __contains__(self, item: HT) -> bool: # type: ignore
251254
return item in self._set
252255

253256
@types.overload
@@ -258,29 +261,37 @@ def __setitem__(self, indices: types.SupportsIndex, values: HT) -> None:
258261
def __setitem__(self, indices: slice, values: types.Iterable[HT]) -> None:
259262
...
260263

261-
def __setitem__(self, indices, values) -> None:
264+
def __setitem__(
265+
self,
266+
indices: types.Union[slice, types.SupportsIndex],
267+
values: types.Union[types.Iterable[HT], HT],
268+
) -> None:
262269
if isinstance(indices, slice):
270+
values = types.cast(types.Iterable[HT], values)
263271
if self.on_duplicate == 'ignore':
264272
raise RuntimeError(
265273
'ignore mode while setting slices introduces ambiguous '
266274
'behaviour and is therefore not supported'
267275
)
268276

269-
duplicates = set(values) & self._set
270-
if duplicates and values != self[indices]:
271-
raise ValueError('Duplicate values: %s' % duplicates)
277+
duplicates: types.Set[HT] = set(values) & self._set
278+
if duplicates and values != list(self[indices]):
279+
raise ValueError(f'Duplicate values: {duplicates}')
272280

273281
self._set.update(values)
274-
super().__setitem__(indices, values)
275282
else:
283+
values = types.cast(HT, values)
276284
if values in self._set and values != self[indices]:
277285
if self.on_duplicate == 'raise':
278-
raise ValueError('Duplicate value: %s' % values)
286+
raise ValueError(f'Duplicate value: {values}')
279287
else:
280288
return
281289

282290
self._set.add(values)
283-
super().__setitem__(indices, values)
291+
292+
super().__setitem__(
293+
types.cast(slice, indices), types.cast(types.List[HT], values)
294+
)
284295

285296
def __delitem__(
286297
self, index: types.Union[types.SupportsIndex, slice]

python_utils/converters.py

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55

66
from . import types
77

8+
_TN = types.TypeVar('_TN', bound=types.DecimalNumber)
9+
810

911
def to_int(
1012
input_: typing.Optional[str] = None,
1113
default: int = 0,
1214
exception: types.ExceptionsType = (ValueError, TypeError),
13-
regexp: types.Optional[types.Pattern] = None,
15+
regexp: types.Optional[types.Pattern[str]] = None,
1416
) -> int:
1517
r'''
1618
Convert the given input to an integer or return default
@@ -84,8 +86,7 @@ def to_int(
8486

8587
try:
8688
if regexp and input_:
87-
match = regexp.search(input_)
88-
if match:
89+
if match := regexp.search(input_):
8990
input_ = match.groups()[-1]
9091

9192
if input_ is None:
@@ -100,7 +101,7 @@ def to_float(
100101
input_: str,
101102
default: int = 0,
102103
exception: types.ExceptionsType = (ValueError, TypeError),
103-
regexp: types.Optional[types.Pattern] = None,
104+
regexp: types.Optional[types.Pattern[str]] = None,
104105
) -> types.Number:
105106
r'''
106107
Convert the given `input_` to an integer or return default
@@ -165,8 +166,7 @@ def to_float(
165166

166167
try:
167168
if regexp:
168-
match = regexp.search(input_)
169-
if match:
169+
if match := regexp.search(input_):
170170
input_ = match.group(1)
171171
return float(input_)
172172
except exception:
@@ -223,9 +223,7 @@ def to_str(
223223
>>> to_str(Foo)
224224
"<class 'python_utils.converters.Foo'>"
225225
'''
226-
if isinstance(input_, bytes):
227-
pass
228-
else:
226+
if not isinstance(input_, bytes):
229227
if not hasattr(input_, 'encode'):
230228
input_ = str(input_)
231229

@@ -263,12 +261,12 @@ def scale_1024(
263261

264262

265263
def remap(
266-
value: types.DecimalNumber,
267-
old_min: types.DecimalNumber,
268-
old_max: types.DecimalNumber,
269-
new_min: types.DecimalNumber,
270-
new_max: types.DecimalNumber,
271-
) -> types.DecimalNumber:
264+
value: _TN,
265+
old_min: _TN,
266+
old_max: _TN,
267+
new_min: _TN,
268+
new_max: _TN,
269+
) -> _TN:
272270
'''
273271
remap a value from one range into another.
274272
@@ -362,24 +360,22 @@ def remap(
362360
else:
363361
type_ = int
364362

365-
value = type_(value)
366-
old_min = type_(old_min)
367-
old_max = type_(old_max)
368-
new_max = type_(new_max)
369-
new_min = type_(new_min)
363+
value = types.cast(_TN, type_(value))
364+
old_min = types.cast(_TN, type_(old_min))
365+
old_max = types.cast(_TN, type_(old_max))
366+
new_max = types.cast(_TN, type_(new_max))
367+
new_min = types.cast(_TN, type_(new_min))
370368

371-
old_range = old_max - old_min # type: ignore
372-
new_range = new_max - new_min # type: ignore
369+
# These might not be floats but the Python type system doesn't understand the
370+
# generic type system in this case
371+
old_range = types.cast(float, old_max) - types.cast(float, old_min)
372+
new_range = types.cast(float, new_max) - types.cast(float, new_min)
373373

374374
if old_range == 0:
375-
raise ValueError(
376-
'Input range ({}-{}) is empty'.format(old_min, old_max)
377-
)
375+
raise ValueError(f'Input range ({old_min}-{old_max}) is empty')
378376

379377
if new_range == 0:
380-
raise ValueError(
381-
'Output range ({}-{}) is empty'.format(new_min, new_max)
382-
)
378+
raise ValueError(f'Output range ({new_min}-{new_max}) is empty')
383379

384380
new_value = (value - old_min) * new_range # type: ignore
385381

@@ -390,4 +386,4 @@ def remap(
390386

391387
new_value += new_min # type: ignore
392388

393-
return new_value
389+
return types.cast(_TN, new_value)

0 commit comments

Comments
 (0)