Last active
October 9, 2019 14:39
-
-
Save Snawoot/36e6d9d4f483f677c36a9fccf8951f96 to your computer and use it in GitHub Desktop.
Proxy for Python objects which allows nullable items and attributes
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
#!/usr/bin/env python3 | |
from abc import ABC, abstractmethod | |
class NullableProxy(ABC): | |
@abstractmethod | |
def __getitem__(self, key): | |
""" Overloaded brackets """ | |
@abstractmethod | |
def __getattr__(self, attr): | |
""" Attribute hook """ | |
@property | |
@abstractmethod | |
def value(self): | |
""" Container getter """ | |
@property | |
@abstractmethod | |
def opt(self): | |
""" Option builder """ | |
class FinalizedChain(NullableProxy): | |
def __init__(self, value): | |
self._value = value | |
def __getitem__(self, key): | |
return self | |
def __getattr__(self, attr): | |
return self | |
@property | |
def value(self): | |
return self._value | |
@property | |
def opt(self): | |
return self | |
class NullableChain(NullableProxy): | |
def __init__(self, obj, *, defvalue=None, optional=False): | |
self._obj = obj | |
self._defvalue = defvalue | |
self._optional = optional | |
def __getitem__(self, key): | |
if self._optional: | |
try: | |
payload = self._obj[key] | |
if payload is None: | |
return FinalizedChain(payload) | |
except (KeyError, IndexError): | |
return FinalizedChain(self._defvalue) | |
else: | |
return NullableChain(payload, | |
defvalue=self._defvalue, | |
optional=False) | |
else: | |
return NullableChain(self._obj[key], | |
defvalue=self._defvalue, | |
optional=False) | |
def __getattr__(self, attr): | |
if self._optional: | |
try: | |
payload = getattr(self._obj, attr) | |
if payload is None: | |
return FinalizedChain(payload) | |
except AttributeError: | |
return FinalizedChain(self._defvalue) | |
else: | |
return NullableChain(payload, | |
defvalue=self._defvalue, | |
optional=False) | |
else: | |
return NullableChain(getattr(self._obj, attr), | |
defvalue=self._defvalue, | |
optional=False) | |
@property | |
def value(self): | |
return self._obj | |
@property | |
def opt(self): | |
if self._optional: | |
return self | |
else: | |
return NullableChain(self._obj, | |
defvalue=self._defvalue, | |
optional=True) | |
def Nullable(obj, *, defvalue=None, nullable=False): | |
if obj is None and nullable: | |
return FinalizedChain(obj) | |
else: | |
return NullableChain(obj, defvalue=defvalue, optional=False) | |
if __name__ == "__main__": | |
data = { | |
"foo": { | |
"bar": None, | |
"baz": "abc", | |
"lst": [ | |
0, | |
1, | |
2, | |
], | |
"asd": { | |
}, | |
} | |
} | |
p = Nullable(data) | |
print(p.value) | |
# Outputs: {'foo': {'bar': None, 'baz': 'abc', 'lst': [0, 1, 2]}} | |
print(p["foo"]["baz"].value) | |
# Outputs: abc | |
print(p["foo"].opt["non-existent-optional-key"]["mandatory-key"].value) | |
# Outputs: None | |
print(p["foo"].opt["bar"]["mandatory-key"].value) | |
# Outputs: None | |
print(p["foo"]["lst"].opt[2].value) | |
# Outputs: 2 | |
print(p["foo"]["lst"].opt[3].value) | |
# Outputs: None | |
try: | |
print(p["foo"].opt["asd"]["def"]) | |
except: | |
pass | |
else: | |
assert False, "Should throw exception if mandatory key is missing" | |
# In tests below every item in chain allowed to be absent | |
p = Nullable(None, nullable=True) | |
print(p.opt["a"].opt["b"].opt["c"].value) | |
# Outputs: None | |
p = Nullable({}) | |
print(p.opt["a"].opt["b"].opt["c"].value) | |
# Outputs: None | |
p = Nullable({"a": {}}) | |
print(p.opt["a"].opt["b"].opt["c"].value) | |
# Outputs: None | |
p = Nullable({"a": {"b": {}}}) | |
print(p.opt["a"].opt["b"].opt["c"].value) | |
# Outputs: None | |
p = Nullable({"a": {"b": {"c":{}}}}) | |
print(p.opt["a"].opt["b"].opt["c"].value) | |
# Outputs: {} | |
p = Nullable({}) | |
assert p.copy.value | |
print(p.opt.nonexistent.value) | |
# Outputs: None |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment