Skip to content

Instantly share code, notes, and snippets.

@ehlertjd
Last active February 12, 2019 22:44
Show Gist options
  • Save ehlertjd/08a5be15106fc3135ce6ddeb75608ad7 to your computer and use it in GitHub Desktop.
Save ehlertjd/08a5be15106fc3135ce6ddeb75608ad7 to your computer and use it in GitHub Desktop.
Model Base class for Core
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