Skip to content

Commit 79013f3

Browse files
Minor improvements to collections items. (#14)
* Changed Item.__repr__. * Updated collections. * Added list to ItemDict. * Added tests for MultiEntryDict. * Updated collection docs.
1 parent b3be4ed commit 79013f3

File tree

5 files changed

+85
-52
lines changed

5 files changed

+85
-52
lines changed

README.md

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ print(nt.forced) # {'hello': 'world'}
231231
</details>
232232

233233
<details>
234-
<summary><a href="https://synchronizing.github.io/toolbox/module/collections.html#toolbox.collections.item.Item"><code>BidirectionalDict</code></a> — Dictionary with two-way capabilities.</summary><br>
234+
<summary><a href="https://synchronizing.github.io/toolbox/module/collections.html#toolbox.collections.mapping.BidirectionalDict"><code>BidirectionalDict</code></a> — Dictionary with two-way capabilities.</summary><br>
235235

236236
```python
237237
from toolbox import BidirectionalDict
@@ -273,33 +273,33 @@ print(d1) # {'hello': 'world'}
273273
<summary><a href="https://synchronizing.github.io/toolbox/module/collections.html#toolbox.collections.mapping.UnderscoreAccessDict"><code>UnderscoreAccessDict</code></a> — Dictionary with underscore access.</summary><br>
274274

275275
```python
276-
from toolbox import OverloadedDict
277-
278-
d1 = OverloadedDict({"hello": "world"})
279-
d2 = OverloadedDict({"ola": "mundo"})
276+
from toolbox import UnderscoreAccessDict
280277

281-
d1 += d2
282-
print(d1) # {'hello': 'world', 'ola': 'mundo'}
283-
284-
d1 -= d2
285-
print(d1) # {'hello': 'world'}
278+
d = UnderscoreAccessDict({"hello": "world"})
279+
print(d.hello) # 'world'
286280
```
287281
</details>
288282

289283
<details>
290284
<summary><a href="https://synchronizing.github.io/toolbox/module/collections.html#toolbox.collections.mapping.FrozenDict"><code>FrozenDict</code></a> — Dictionary that is frozen.</summary><br>
291285

292286
```python
293-
from toolbox import OverloadedDict
287+
from toolbox import FrozenDict
294288

295-
d1 = OverloadedDict({"hello": "world"})
296-
d2 = OverloadedDict({"ola": "mundo"})
289+
d = FrozenDict({"hello": "world"})
290+
d["hello"] = "mundo" # KeyError: Cannot set key and value because this is a frozen dictionary.
291+
```
292+
</details>
297293

298-
d1 += d2
299-
print(d1) # {'hello': 'world', 'ola': 'mundo'}
294+
<details>
295+
<summary><a href="https://synchronizing.github.io/toolbox/module/collections.html#toolbox.collections.mapping.MultiEntryDict"><code>MultiEntryDict</code></a> — Dictionary that can have multiple entries for the same key.</summary><br>
300296

301-
d1 -= d2
302-
print(d1) # {'hello': 'world'}
297+
```python
298+
from toolbox import MultiEntryDict
299+
300+
d = MultiEntryDict({"hello": "world"})
301+
d["hello"] = "mundo"
302+
print(d) # {'hello': ['world', 'mundo']}
303303
```
304304
</details>
305305

docs/source/module/collections.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ Note that :py:class:`*Dict` types below can be combined together (as mixins) to
155155
.. autoclass:: toolbox.collections.mapping.OverloadedDict(BaseDict)
156156
.. autoclass:: toolbox.collections.mapping.UnderscoreAccessDict(BaseDict)
157157
.. autoclass:: toolbox.collections.mapping.FrozenDict(BaseDict)
158+
.. autoclass:: toolbox.collections.mapping.MultiEntryDict(BaseDict)
158159
.. autoclass:: toolbox.collections.mapping.ItemDict(BaseDict)
159160

160161
----

tests/test_collections.py

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
UnderscoreAccessDict,
99
FrozenDict,
1010
ItemDict,
11+
MultiEntryDict,
1112
fdict,
1213
nestednamedtuple,
1314
)
@@ -109,22 +110,10 @@ def test_item_str(self):
109110
assert str(item) == item.string
110111

111112
def test_item_repr(self):
112-
assert (
113-
repr(Item(100))
114-
== "Item(bytes=b'100', str='100', int=100, bool=True, original_type=int)"
115-
)
116-
assert (
117-
repr(Item(None))
118-
== "Item(bytes=b'', str='', int=None, bool=False, original_type=NoneType)"
119-
)
120-
assert (
121-
repr(Item(True))
122-
== "Item(bytes=b'1', str='1', int=1, bool=True, original_type=bool)"
123-
)
124-
assert (
125-
repr(Item("hello"))
126-
== "Item(bytes=b'hello', str='hello', int=None, bool=True, original_type=str)"
127-
)
113+
assert repr(Item(100)) == "100"
114+
assert repr(Item(None)) == "None"
115+
assert repr(Item(True)) == "True"
116+
assert repr(Item("hello")) == "hello"
128117

129118
def test_item_byte_item_err(self):
130119
with pytest.raises(TypeError):
@@ -167,6 +156,11 @@ def test_object_dict_data(self):
167156
d = ObjectDict({"hello": "world"})
168157
assert d.hello == "world"
169158

159+
def test_object_dict_setattr(self):
160+
d = ObjectDict({"hello": "world"})
161+
d.ola = "mundo"
162+
assert d.ola == "mundo"
163+
170164
class Test_collection_overloaded:
171165
def test_overloaded_dict_type(self):
172166
assert isinstance(OverloadedDict(), dict)
@@ -210,6 +204,20 @@ def test_underscore_access_str(self):
210204
assert d["key"] == "value"
211205
assert d["_100"] == "one hundred"
212206

207+
def test_underscore_access_bytes(self):
208+
d = UnderscoreAccessDict(
209+
{
210+
b"hello world": "ola mundo",
211+
b"ola_mundo": "hello world",
212+
b"key": "value",
213+
b"100": "one hundred",
214+
}
215+
)
216+
assert d[b"hello_world"] == "ola mundo"
217+
assert d[b"ola_mundo"] == "hello world"
218+
assert d[b"key"] == "value"
219+
assert d[b"_100"] == "one hundred"
220+
213221
class Test_collection_frozen:
214222
def test_frozen_init(self):
215223
d = FrozenDict({"hello": "world"})
@@ -223,6 +231,14 @@ def test_frozen_update(self):
223231
with pytest.raises(KeyError):
224232
d.update({"ola": "mundo"})
225233

234+
class Test_collection_multi:
235+
def test_multi_init(self):
236+
d = MultiEntryDict({"hello": "world", "hello": "mundo"})
237+
assert d["hello"] == "mundo"
238+
239+
d["hello"] = "globo"
240+
assert d["hello"] == ["mundo", "globo"]
241+
226242
class Test_collection_item:
227243
def test_item_init(self):
228244
d = ItemDict({"100": "one hundred"})
@@ -233,6 +249,9 @@ def test_item_setitem(self):
233249
d[100] = "one hundred"
234250
assert d["100"] == d[b"100"] == d[100] == d[Item(100)]
235251

252+
d[200] = [1, 2, 3]
253+
assert d["200"] == d[b"200"] == d[200] == d[Item(200)]
254+
236255
def test_item_delitem(self):
237256
d = ItemDict()
238257
d[100] = "one hundred"

toolbox/collections/item.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,7 @@ def __str__(self) -> str:
168168
return self.string
169169

170170
def __repr__(self) -> str:
171-
return "Item(bytes={}, str='{}', int={}, bool={}, original_type={})".format(
172-
self.raw,
173-
self.string,
174-
self.integer,
175-
self.boolean,
176-
self._type.__name__,
177-
)
171+
return str(self.original)
178172

179173
@staticmethod
180174
def byte_item(item: ItemType) -> "Item":

toolbox/collections/mapping.py

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def update(self, dictionary: dict = Optional[dict], **kwargs):
7070

7171

7272
class ObjectDict(BaseDict):
73-
"""Dictionary that can be accessed as though it was an object.
73+
"""Dictionary that can be accessed and set as though it was an object.
7474
7575
Example:
7676
@@ -82,11 +82,17 @@ class ObjectDict(BaseDict):
8282
print(d) # >>> <ObjectDict {'hello': 'world'}>
8383
8484
print(d.hello) # >>> 'world'
85+
86+
d.hello = "mundo"
87+
print(d.hello) # >>> 'mundo'
8588
"""
8689

8790
def __getattr__(self, key: Any) -> Any:
8891
return self.__getitem__(key)
8992

93+
def __setattr__(self, key: str, value: Any):
94+
return self.__setitem__(key, value)
95+
9096

9197
class OverloadedDict(BaseDict):
9298
"""Dictionary that can be added or subtracted.
@@ -140,8 +146,13 @@ class UnderscoreAccessDict(BaseDict):
140146
"""
141147

142148
def __getitem__(self, key: Any) -> Any:
143-
utw = key.replace("_", " ")
144-
wtu = key.replace("_", "")
149+
if isinstance(key, bytes):
150+
utw = key.replace(b"_", b" ")
151+
wtu = key.replace(b"_", b"")
152+
else:
153+
utw = key.replace("_", " ")
154+
wtu = key.replace("_", "")
155+
145156
if utw in self:
146157
return super(UnderscoreAccessDict, self).__getitem__(utw)
147158
elif wtu in self:
@@ -192,7 +203,7 @@ def __setitem__(self, key, value):
192203
if isinstance(self[key], list):
193204
self[key].append(value)
194205
else:
195-
self[key] = [self[key], value]
206+
super().__setitem__(key, [self[key], value])
196207
else:
197208
super(MultiEntryDict, self).__setitem__(key, value)
198209

@@ -208,25 +219,33 @@ class ItemDict(BaseDict):
208219
209220
d = ItemDict({"100": "one hundred"})
210221
print(d)
211-
# >>> <ItemDict {Item(bytes=b'100', str='100', int=100, bool=True, original_type=str): Item(bytes=b'one hundred', str='one hundred', int=None, bool=True, original_type=str)}>
222+
# >>> <ItemDict {100: one hundred}>
212223
213224
print(d["100"] == d[100] == d[b"100"]) # >>> True
214225
"""
215226

216227
def __init__(self, dictionary: Optional[dict] = None, **kwargs):
217228
dictionary = dictionary or {}
218-
super(ItemDict, self).__init__(
219-
{
220-
**{Item(k): Item(v) for k, v in dictionary.items()},
221-
**{Item(k): Item(v) for k, v in kwargs.items()},
222-
}
223-
)
229+
kwargs = kwargs or {}
230+
dictionary = {**dictionary, **kwargs}
231+
232+
new = {}
233+
for k, v in dictionary.items():
234+
if isinstance(v, list):
235+
new[Item(k)] = [Item(i) for i in v]
236+
else:
237+
new[Item(k)] = Item(v)
238+
239+
return super(ItemDict, self).__init__(new)
224240

225241
def __getitem__(self, key: ItemType):
226242
return super(ItemDict, self).__getitem__(Item(key))
227243

228244
def __setitem__(self, key: ItemType, value: ItemType):
229-
super(ItemDict, self).__setitem__(Item(key), Item(value))
245+
if isinstance(value, list):
246+
super(ItemDict, self).__setitem__(Item(key), [Item(i) for i in value])
247+
else:
248+
super(ItemDict, self).__setitem__(Item(key), Item(value))
230249

231250
def __delitem__(self, key: ItemType):
232251
super(ItemDict, self).__delitem__(Item(key))

0 commit comments

Comments
 (0)