From 2a053272ead21e8a4d2a41bda7362c01567b4492 Mon Sep 17 00:00:00 2001 From: Jan Willhaus Date: Sat, 14 Dec 2019 01:21:07 +0100 Subject: Initial overhaul of the Model class --- tests/test_core.py | 32 ++++++++---------------------- warlock/model.py | 58 +++++++++++++++++++++++++++++++++++------------------- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index bc36c92..7623e7d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -16,7 +16,6 @@ import copy import json import os import unittest -import warnings import warlock @@ -140,13 +139,16 @@ class TestCore(unittest.TestCase): mike_3_sub["foo"] = "james" self.assertEqual("mike", mike.sub["foo"]) - def test_forbidden_methods(self): + def test_previously_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) + sweden.clear() + self.assertDictEqual(sweden, {}) + sweden = Country(name="Sweden", population=9379116) + self.assertEqual(sweden.pop("population"), 9379116) + self.assertEqual(sweden, {"name": "Sweden"}) + self.assertEqual(sweden.popitem(), ("name", "Sweden")) + self.assertEqual(sweden, {}) def test_dict_syntax(self): Country = warlock.model_factory(fixture) @@ -168,24 +170,6 @@ class TestCore(unittest.TestCase): delattr(sweden, "name") self.assertRaises(AttributeError, getattr, sweden, "name") - def test_changes(self): - Country = warlock.model_factory(fixture) - sweden = Country(name="Sweden", population=9379116) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - self.assertEqual(sweden.changes, {}) - assert w[0].category == DeprecationWarning - sweden["name"] = "Finland" - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - self.assertEqual(sweden.changes, {"name": "Finland"}) - assert w[0].category == DeprecationWarning - sweden["name"] = "Norway" - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - self.assertEqual(sweden.changes, {"name": "Norway"}) - assert w[0].category == DeprecationWarning - def test_patch_no_changes(self): Country = warlock.model_factory(fixture) sweden = Country(name="Sweden", population=9379116) diff --git a/warlock/model.py b/warlock/model.py index 62eb330..0dafab6 100644 --- a/warlock/model.py +++ b/warlock/model.py @@ -15,7 +15,6 @@ """Self-validating model for arbitrary objects""" import copy -import warnings import jsonpatch import jsonschema @@ -35,7 +34,6 @@ class Model(dict): else: dict.__init__(self, d) - self.__dict__["changes"] = {} self.__dict__["__original__"] = copy.deepcopy(d) def __setitem__(self, key, value): @@ -44,21 +42,19 @@ class Model(dict): try: self.validate(mutation) except exceptions.ValidationError as exc: - msg = "Unable to set '%s' to %r. Reason: %s" % (key, value, str(exc)) + msg = "Unable to set '%s' to %r. Reason: %s" % (key, value, exc) raise exceptions.InvalidOperation(msg) dict.__setitem__(self, key, value) - self.__dict__["changes"][key] = value - def __delitem__(self, key): mutation = dict(self.items()) del mutation[key] try: self.validate(mutation) except exceptions.ValidationError as exc: - msg = "Unable to delete attribute '%s'. Reason: %s" % (key, str(exc)) - raise exceptions.InvalidOperation(msg) + msg = "Unable to delete attribute '%s'. Reason: %s" % (key, exc) + raise exceptions.InvalidOperation(msg) from exc dict.__delitem__(self, key) @@ -77,13 +73,40 @@ class Model(dict): # BEGIN dict compatibility methods def clear(self): - raise exceptions.InvalidOperation() - - def pop(self, key, default=None): - raise exceptions.InvalidOperation() + mutation = dict(self.items()) + keys = list(mutation.keys()) + for key in keys: + del mutation[key] + try: + self.validate(mutation) + except exceptions.ValidationError as exc: + msg = "Unable to clear data. Reason: %s" % (exc,) + raise exceptions.InvalidOperation(msg) + for key in keys: + del self[key] + + def pop(self, key, *args): + try: + value = self.__getitem__(key) + self.__delitem__(key) + except KeyError: + if args: + return args[0] + raise + except exceptions.InvalidOperation as exc: + msg = "Unable to pop '%s'. Reason: %s" % (key, exc.__context__) + raise exceptions.InvalidOperation(msg) + return value def popitem(self): - raise exceptions.InvalidOperation() + item = next(iter(self.items())) + key = item[0] + try: + self.__delitem__(key) + except exceptions.InvalidOperation as exc: + msg = "Unable to pop next item '%s'. Reason: %s" % (item, exc.__context__) + raise exceptions.InvalidOperation(msg) + return item def copy(self): return copy.deepcopy(dict(self)) @@ -117,15 +140,10 @@ class Model(dict): original = self.__dict__["__original__"] return jsonpatch.make_patch(original, dict(self)).to_string() - @property - def changes(self): - """Dumber version of 'patch' method""" - deprecation_msg = "Model.changes will be removed in warlock v2" - warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2) - return copy.deepcopy(self.__dict__["changes"]) - - def validate(self, obj): + def validate(self, obj=None): """Apply a JSON schema to an object""" + if obj is None: + obj = self try: if self.resolver is not None: jsonschema.validate(obj, self.schema, resolver=self.resolver) -- cgit v1.2.1