Last active
February 12, 2019 22:44
-
-
Save ehlertjd/08a5be15106fc3135ce6ddeb75608ad7 to your computer and use it in GitHub Desktop.
Model Base class for Core
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
import collections | |
import types | |
# ==== Model Definition ==== | |
class Model(collections.MutableMapping): | |
"""Base class for core models""" | |
@classmethod | |
def from_dict(cls, dct): | |
"""Construct a model from a dictionary""" | |
result = cls.__new__(cls) | |
result.set_dict(dct) | |
return result | |
def set_dict(self, dct): | |
"""Replace the model contents with dct""" | |
self.__dict__ = dct | |
def to_dict(self): | |
"""Convert the model to a dictionary. | |
The resulting model should be convertable to JSON or BSON | |
Returns: | |
dict: The converted model | |
""" | |
return _model_to_dict(self.__dict__) | |
def __contains__(self, key): | |
return key in self.__dict__ | |
def __getitem__(self, key): | |
return self.__dict__[key] | |
def __setitem__(self, key, value): | |
self.__dict__[key] = value | |
def __delitem__(self, key): | |
del self.__dict__[key] | |
def __iter__(self): | |
return iter(self.__dict__) | |
def __len__(self): | |
return len(self.__dict__) | |
def __getattr__(self, name): | |
"""Not found in the usual places, so return None""" | |
return None | |
def _model_to_dict(obj): | |
"""Flexible model conversion function | |
Conversion is prioritized as follows: | |
- Model subclasses are converted via to_dict | |
- mappings (e.g dict or OrderedDict) are converted to dict | |
- iterables (e.g. list or set) are converted to lists | |
- Everything else is left as-is | |
""" | |
if isinstance(obj, Model): | |
return obj.to_dict() | |
if isinstance(obj, collections.Mapping): | |
result = {} | |
for key, value in obj.iteritems(): | |
result[key] = _model_to_dict(value) | |
return result | |
# TODO: Python3 - Iterable and not (str or bytes) | |
if isinstance(obj, collections.Iterable) and not isinstance(obj, types.StringTypes): | |
result = [] | |
for value in obj.iteritems(): | |
result.append(_model_to_dict(value)) | |
return result | |
return obj | |
# Testing models | |
class Container(Model): | |
def __init__(self, label, public=False, info=None): | |
self._id = None | |
self.label = label | |
self.public = public | |
self.info = info | |
import pytest | |
def test_container(): | |
container = Container('Test Label', info={'my': 'value'}) | |
assert container.label == 'Test Label' | |
assert container.public == False | |
assert container.info == {'my': 'value'} | |
assert container.foo == None # Arbitrary attribute access results in None | |
container._id = 'test' | |
assert container.to_dict() == { | |
'_id': 'test', | |
'label': 'Test Label', | |
'public': False, | |
'info': {'my': 'value'} | |
} | |
# Can delete any field, and add any additional fields | |
del container._id | |
del container.info | |
container['foo'] = 'bar' | |
assert container.to_dict() == { | |
'label': 'Test Label', | |
'public': False, | |
'foo': 'bar' | |
} | |
# Can get a field via square-brackets | |
assert container['label'] == 'Test Label' | |
# Can get with default | |
assert container.get('label', 'blah') == 'Test Label' | |
assert container.get('label2', 'blah') == 'blah' | |
with pytest.raises(KeyError): | |
y = container['not_exist'] | |
# Can get keys | |
expected_keys = {'label', 'public', 'foo'} | |
assert set(container.keys()) == expected_keys | |
# Can iter items | |
for key, item in container.iteritems(): | |
assert key in expected_keys | |
# Can create from a dictionary | |
c2 = Container.from_dict({ | |
'label': 'Test Label', | |
'public': False, | |
'foo': 'bar' | |
}) | |
assert container._id == None | |
assert container.label == 'Test Label' | |
assert container.public == False | |
assert container.info == None | |
assert container.foo == 'bar' # Arbitrary attribute access results in None | |
# Test equality | |
assert container == c2 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment