Skip to content

Instantly share code, notes, and snippets.

@Snawoot
Last active October 9, 2019 14:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Snawoot/36e6d9d4f483f677c36a9fccf8951f96 to your computer and use it in GitHub Desktop.
Save Snawoot/36e6d9d4f483f677c36a9fccf8951f96 to your computer and use it in GitHub Desktop.
Proxy for Python objects which allows nullable items and attributes
#!/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