Skip to content

Commit e79d45d

Browse files
committed
Merge branch 'release/3.1.0'
2 parents 8361792 + 7ff8f9b commit e79d45d

File tree

5 files changed

+182
-5
lines changed

5 files changed

+182
-5
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
timeout-minutes: 2
1212
strategy:
1313
matrix:
14-
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
14+
python-version: ['3.7', '3.8', '3.9', '3.10']
1515

1616
steps:
1717
- uses: actions/checkout@v2

python_utils/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
'with the standard Python install')
77
__url__: str = 'https://github.com/WoLpH/python-utils'
88
# Omit type info due to automatic versioning script
9-
__version__ = '3.0.0'
9+
__version__ = '3.1.0'

python_utils/containers.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
from __future__ import annotations
2+
3+
import abc
4+
from typing import Any
5+
from typing import Generator
6+
7+
from . import types
8+
9+
KT = types.TypeVar('KT')
10+
VT = types.TypeVar('VT')
11+
DT = types.Dict[KT, VT]
12+
KT_cast = types.Optional[types.Callable[[Any], KT]]
13+
VT_cast = types.Optional[types.Callable[[Any], VT]]
14+
15+
# Using types.Union instead of | since Python 3.7 doesn't fully support it
16+
DictUpdateArgs = types.Union[
17+
types.Mapping,
18+
types.Iterable[types.Union[types.Tuple[Any, Any], types.Mapping]],
19+
]
20+
21+
22+
class CastedDictBase(types.Dict[KT, VT], abc.ABC):
23+
_key_cast: KT_cast
24+
_value_cast: VT_cast
25+
26+
def __init__(
27+
self,
28+
key_cast: KT_cast = None,
29+
value_cast: VT_cast = None,
30+
*args,
31+
**kwargs
32+
) -> None:
33+
self._value_cast = value_cast
34+
self._key_cast = key_cast
35+
self.update(*args, **kwargs)
36+
37+
def update(
38+
self,
39+
*args: DictUpdateArgs,
40+
**kwargs
41+
) -> None:
42+
if args:
43+
kwargs.update(*args)
44+
45+
if kwargs:
46+
for key, value in kwargs.items():
47+
self[key] = value
48+
49+
def __setitem__(self, key: Any, value: Any) -> None:
50+
if self._key_cast is not None:
51+
key = self._key_cast(key)
52+
53+
return super().__setitem__(key, value)
54+
55+
56+
class CastedDict(CastedDictBase):
57+
'''
58+
Custom dictionary that casts keys and values to the specified typing.
59+
60+
Note that you can specify the types for mypy and type hinting with:
61+
CastedDict[int, int](int, int)
62+
63+
>>> d = CastedDict(int, int)
64+
>>> d[1] = 2
65+
>>> d['3'] = '4'
66+
>>> d.update({'5': '6'})
67+
>>> d.update([('7', '8')])
68+
>>> d
69+
{1: 2, 3: 4, 5: 6, 7: 8}
70+
>>> list(d.keys())
71+
[1, 3, 5, 7]
72+
>>> list(d)
73+
[1, 3, 5, 7]
74+
>>> list(d.values())
75+
[2, 4, 6, 8]
76+
>>> list(d.items())
77+
[(1, 2), (3, 4), (5, 6), (7, 8)]
78+
>>> d[3]
79+
4
80+
81+
# Casts are optional and can be disabled by passing None as the cast
82+
>>> d = CastedDict()
83+
>>> d[1] = 2
84+
>>> d['3'] = '4'
85+
>>> d.update({'5': '6'})
86+
>>> d.update([('7', '8')])
87+
>>> d
88+
{1: 2, '3': '4', '5': '6', '7': '8'}
89+
'''
90+
91+
def __setitem__(self, key, value):
92+
if self._value_cast is not None:
93+
value = self._value_cast(value)
94+
95+
super().__setitem__(key, value)
96+
97+
98+
class LazyCastedDict(CastedDictBase):
99+
'''
100+
Custom dictionary that casts keys and lazily casts values to the specified
101+
typing. Note that the values are cast only when they are accessed and
102+
are not cached between executions.
103+
104+
Note that you can specify the types for mypy and type hinting with:
105+
LazyCastedDict[int, int](int, int)
106+
107+
>>> d = LazyCastedDict(int, int)
108+
>>> d[1] = 2
109+
>>> d['3'] = '4'
110+
>>> d.update({'5': '6'})
111+
>>> d.update([('7', '8')])
112+
>>> d
113+
{1: 2, 3: '4', 5: '6', 7: '8'}
114+
>>> list(d.keys())
115+
[1, 3, 5, 7]
116+
>>> list(d)
117+
[1, 3, 5, 7]
118+
>>> list(d.values())
119+
[2, 4, 6, 8]
120+
>>> list(d.items())
121+
[(1, 2), (3, 4), (5, 6), (7, 8)]
122+
>>> d[3]
123+
4
124+
125+
# Casts are optional and can be disabled by passing None as the cast
126+
>>> d = LazyCastedDict()
127+
>>> d[1] = 2
128+
>>> d['3'] = '4'
129+
>>> d.update({'5': '6'})
130+
>>> d.update([('7', '8')])
131+
>>> d
132+
{1: 2, '3': '4', '5': '6', '7': '8'}
133+
>>> list(d.keys())
134+
[1, '3', '5', '7']
135+
>>> list(d.values())
136+
[2, '4', '6', '8']
137+
138+
>>> list(d.items())
139+
[(1, 2), ('3', '4'), ('5', '6'), ('7', '8')]
140+
>>> d['3']
141+
'4'
142+
'''
143+
144+
def __setitem__(self, key, value):
145+
if self._key_cast is not None:
146+
key = self._key_cast(key)
147+
148+
super().__setitem__(key, value)
149+
150+
def __getitem__(self, key) -> VT:
151+
if self._key_cast is not None:
152+
key = self._key_cast(key)
153+
154+
value = super().__getitem__(key)
155+
156+
if self._value_cast is not None:
157+
value = self._value_cast(value)
158+
159+
return value
160+
161+
def items(self) -> Generator[tuple[KT, VT], None, None]: # type: ignore
162+
if self._value_cast is None:
163+
yield from super().items()
164+
else:
165+
for key, value in super().items():
166+
yield key, self._value_cast(value)
167+
168+
def values(self) -> Generator[VT, None, None]: # type: ignore
169+
if self._value_cast is None:
170+
yield from super().values()
171+
else:
172+
for value in super().values():
173+
yield self._value_cast(value)
174+
175+
176+
if __name__ == '__main__':
177+
import doctest
178+
179+
doctest.testmod()

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
tests_require=['pytest'],
3131
extras_require={
3232
'docs': [
33-
'six',
3433
'mock',
3534
'sphinx',
3635
'python-utils',

tox.ini

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
[tox]
2-
envlist = py36, py37, py38, py39, py310, pypy3, flake8, docs
2+
envlist = py37, py38, py39, py310, pypy3, flake8, docs
33
skip_missing_interpreters = True
44

55
[testenv]
66
basepython =
7-
py36: python3.6
87
py37: python3.7
98
py38: python3.8
109
py39: python3.9

0 commit comments

Comments
 (0)