summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Waldon <bcwaldon@gmail.com>2012-10-08 17:13:02 -0700
committerBrian Waldon <bcwaldon@gmail.com>2012-10-08 17:13:02 -0700
commit26df3051f7c331c9fa9c69ab0ccba47d8dad4613 (patch)
treedbc475cb7f5876f5a75c01149a42b8d19994c273
parent0a6f016295dc7609bf88b1d36458aeab2aeb4014 (diff)
parent1001d393fe6012ef5c0a2d1cc7744fd5f240aea6 (diff)
downloadwarlock-26df3051f7c331c9fa9c69ab0ccba47d8dad4613.tar.gz
Merge pull request #1 from termie/dict_base
switch to a dict-based object
-rw-r--r--test/test_core.py52
-rw-r--r--warlock/core.py58
2 files changed, 98 insertions, 12 deletions
diff --git a/test/test_core.py b/test/test_core.py
index bc2828c..5feb9f1 100644
--- a/test/test_core.py
+++ b/test/test_core.py
@@ -14,6 +14,15 @@ fixture = {
}
+complex_fixture = {
+ 'name': 'Mixmaster',
+ 'properties': {
+ 'sub': {'type': 'object',
+ 'properties': {'foo': {'type': 'string'}}}
+ },
+}
+
+
class TestCore(unittest.TestCase):
def test_create_invalid_object(self):
Country = warlock.model_factory(fixture)
@@ -63,6 +72,49 @@ class TestCore(unittest.TestCase):
self.assertEqual(set(sweden.items()),
set([('name', 'Sweden'), ('population', 9379116)]))
+ def test_update(self):
+ Country = warlock.model_factory(fixture)
+ sweden = Country(name='Sweden', population=9379116)
+ exc = warlock.InvalidOperation
+ self.assertRaises(exc, sweden.update, {'population': 'N/A'})
+ self.assertRaises(exc, sweden.update, {'overloard': 'Bears'})
+
+ def test_deepcopy(self):
+ """Make sure we aren't leaking references."""
+ Mixmaster = warlock.model_factory(complex_fixture)
+ mike = Mixmaster(sub={'foo': 'mike'})
+
+ self.assertEquals(mike.sub['foo'], 'mike')
+
+ mike_1 = mike.copy()
+ mike_1['sub']['foo'] = 'james'
+ self.assertEquals(mike.sub['foo'], 'mike')
+
+ mike_2 = dict(mike.iteritems())
+ mike_2['sub']['foo'] = 'james'
+ self.assertEquals(mike.sub['foo'], 'mike')
+
+ mike_2 = dict(mike.items())
+ mike_2['sub']['foo'] = 'james'
+ self.assertEquals(mike.sub['foo'], 'mike')
+
+ mike_3_sub = list(mike.itervalues())[0]
+ mike_3_sub['foo'] = 'james'
+ self.assertEquals(mike.sub['foo'], 'mike')
+
+ mike_3_sub = list(mike.values())[0]
+ mike_3_sub['foo'] = 'james'
+ self.assertEquals(mike.sub['foo'], 'mike')
+
+ def test_forbidden_methods(self):
+ Country = warlock.model_factory(fixture)
+ sweden = Country(name='Sweden', population=9379116)
+ exc = warlock.InvalidOperation
+ self.assertRaises(exc, sweden.clear)
+ self.assertRaises(exc, sweden.pop, 0)
+ self.assertRaises(exc, sweden.popitem)
+ self.assertRaises(exc, sweden.__delitem__, 'name')
+
def test_dict_syntax(self):
Country = warlock.model_factory(fixture)
sweden = Country(name='Sweden', population=9379116)
diff --git a/warlock/core.py b/warlock/core.py
index eacd7ab..ad3f3bd 100644
--- a/warlock/core.py
+++ b/warlock/core.py
@@ -27,16 +27,20 @@ def model_factory(schema):
except jsonschema.ValidationError:
raise ValidationError()
- class Model(object):
+ class Model(dict):
"""Self-validating model for arbitrary objects"""
- def __init__(self, **kwargs):
+
+ def __init__(self, *args, **kwargs):
+ d = dict(*args, **kwargs)
+
+ # we overload setattr so set this manually
self.__dict__['validator'] = validator
try:
- self.__dict__['validator'](kwargs)
+ self.validator(d)
except ValidationError:
raise ValueError()
else:
- self.__dict__['raw'] = kwargs
+ dict.__init__(self, d)
self.__dict__['changes'] = {}
@@ -46,27 +50,57 @@ def model_factory(schema):
except KeyError:
raise AttributeError(key)
- def __getitem__(self, key):
- return self.__dict__['raw'][key]
-
def __setitem__(self, key, value):
- mutation = copy.deepcopy(self.__dict__['raw'])
+ mutation = dict(self.items())
mutation[key] = value
try:
- self.__dict__['validator'](mutation)
+ self.validator(mutation)
except ValidationError:
raise InvalidOperation()
- self.__dict__['raw'] = mutation
+
+ dict.__setitem__(self, key, value)
+
self.__dict__['changes'][key] = value
def __setattr__(self, key, value):
self.__setitem__(key, value)
+ def clear(self):
+ raise InvalidOperation()
+
+ def pop(self, key, default=None):
+ raise InvalidOperation()
+
+ def popitem(self):
+ raise InvalidOperation()
+
+ def __delitem__(self, key):
+ raise InvalidOperation()
+
+ # NOTE(termie): This is kind of the opposite of what copy usually does
+ def copy(self):
+ return copy.deepcopy(dict(self))
+
+ def update(self, other):
+ mutation = dict(self.items())
+ mutation.update(other)
+ try:
+ self.validator(mutation)
+ except ValidationError:
+ raise InvalidOperation()
+ dict.update(self, other)
+
def iteritems(self):
- return copy.deepcopy(self.__dict__['raw']).iteritems()
+ return copy.deepcopy(dict(self)).iteritems()
def items(self):
- return copy.deepcopy(self.__dict__['raw']).items()
+ return copy.deepcopy(dict(self)).items()
+
+ def itervalues(self):
+ return copy.deepcopy(dict(self)).itervalues()
+
+ def values(self):
+ return copy.deepcopy(dict(self)).values()
@property
def changes(self):