Created
October 11, 2020 14:32
-
-
Save M0r13n/64a3f68a5df38f6523eb7977656a7928 to your computer and use it in GitHub Desktop.
A simple Python dictionary whose keys can be queried using dot notation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import Any | |
class CaseInsensitiveDict(dict): | |
""" | |
A dict object where the case of keys does not matter. | |
Create a new instance: | |
>>> c = CaseInsensitiveDict({'a' : 123}) | |
then you access the values normally | |
>>> assert c['a'] == c['A'] | |
""" | |
def __getitem__(self, k: str) -> Any: | |
""" | |
Invoked when a key is accessed. | |
""" | |
if k in self: | |
return super().__getitem__(k) | |
return self._get_item_case_insensitive(k) | |
def _get_item_case_insensitive(self, key: str): | |
""" | |
Iterate over every key and compare it's lowercase representation | |
with a given key. | |
""" | |
key = key.lower() | |
for k, v in self.items(): | |
lk = k.lower() | |
if lk == key: | |
return v | |
raise KeyError(f'{key}') | |
class DotDict(CaseInsensitiveDict): | |
""" | |
A single level dict object that supports accessing via dot notation. | |
>>> x = DotDict() | |
>>> x.a = "A value" | |
>>> assert x['a'] == x.a | |
>>> del x.a | |
>>> assert 'a' not in x | |
""" | |
def __getattr__(self, attr: str) -> Any: | |
""" | |
Pass attribute call to getitem | |
""" | |
return self.__getitem__(attr) | |
def __setattr__(self, key: str, val: Any) -> None: | |
""" | |
Pass attribute assignment to setitem | |
""" | |
self.__setitem__(key, val) | |
def __delattr__(self, item: str) -> None: | |
""" | |
Pass attribute deletion to delitem | |
""" | |
self.__delitem__(item) | |
class NestedDotDict(DotDict): | |
""" | |
Multi-level DotDict where each level is itself a DotDict. | |
Builds the final object recursively. | |
Makes it possible to use chained dot operations: | |
>>> sample = { | |
... "A": { | |
... "B": { | |
... "C": "Hello" | |
... }, | |
... "D": "Nice" | |
... }, | |
... "test": 42 | |
... } | |
>>> n = NestedDotDict(sample) | |
>>> print(n["A"]) | |
{'B': {'C': 'Hello'}, 'D': 'Nice'} | |
>>> print(n.a.b.c) | |
Hello | |
>>> print(n.TEST) | |
42 | |
""" | |
def __init__(self, *args: Any, **kwargs) -> Any: | |
super().__init__(*args, **kwargs) | |
self.to_dot_dict() | |
def to_dot_dict(self): | |
""" | |
Iterate over every key and transform every value that is a normal dict | |
into a NestedDotDict recursively. | |
""" | |
for k, v in self.items(): | |
if isinstance(v, dict): | |
if not isinstance(v, DotDict): | |
self[k] = NestedDotDict(v) | |
if __name__ == "__main__": | |
import doctest | |
doctest.testmod() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment