From 98a44c660136b7089cf3e8ed95254b475ab4d085 Mon Sep 17 00:00:00 2001 From: Rob Date: Tue, 27 Nov 2012 11:16:13 -0500 Subject: [PATCH] Break up core pieces into separate modules - Create model.py to hold the core Model class - Create exceptions.py to hold all custom exceptions --- .gitignore | 2 + warlock/__init__.py | 2 +- warlock/core.py | 114 +++--------------------------------------- warlock/exceptions.py | 9 ++++ warlock/model.py | 108 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 128 insertions(+), 107 deletions(-) create mode 100644 warlock/exceptions.py create mode 100644 warlock/model.py diff --git a/.gitignore b/.gitignore index 59ae20d..cce7553 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ build/ dist/ warlock.egg-info/ .tox/ +*.swp +*.pyc diff --git a/warlock/__init__.py b/warlock/__init__.py index 400c081..0d8560e 100644 --- a/warlock/__init__.py +++ b/warlock/__init__.py @@ -2,4 +2,4 @@ # pylint: disable=W0611 from warlock.core import model_factory -from warlock.core import InvalidOperation +from warlock.exceptions import InvalidOperation diff --git a/warlock/core.py b/warlock/core.py index 9c3cdf7..5717399 100644 --- a/warlock/core.py +++ b/warlock/core.py @@ -3,18 +3,12 @@ import copy import jsonschema -import jsonpatch + +import model +import exceptions -class InvalidOperation(RuntimeError): - pass - - -class ValidationError(ValueError): - pass - - -def model_factory(schema): +def model_factory(schema, base_class=model.Model): """Generate a model class based on the provided JSON Schema :param schema: dict representing valid JSON schema @@ -26,104 +20,12 @@ def model_factory(schema): try: jsonschema.validate(obj, schema) except jsonschema.ValidationError as exc: - raise ValidationError(str(exc)) - - class Model(dict): - """Self-validating model for arbitrary objects""" + raise exceptions.ValidationError(str(exc)) + class Model(base_class): def __init__(self, *args, **kwargs): - d = dict(*args, **kwargs) - - # we overload setattr so set this manually - self.__dict__['validator'] = validator - try: - self.validator(d) - except ValidationError as exc: - raise ValueError(str(exc)) - else: - dict.__init__(self, d) - - self.__dict__['changes'] = {} - self.__dict__['__original__'] = copy.deepcopy(d) - - def __getattr__(self, key): - try: - return self.__getitem__(key) - except KeyError: - raise AttributeError(key) - - def __setitem__(self, key, value): - mutation = dict(self.items()) - mutation[key] = value - try: - self.validator(mutation) - except ValidationError: - msg = "Unable to set '%s' to '%s'" % (key, value) - raise InvalidOperation(msg) - - 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): - mutation = dict(self.items()) - del mutation[key] - try: - self.validator(mutation) - except ValidationError: - msg = "Unable to delete attribute '%s'" % (key) - raise InvalidOperation(msg) - - dict.__delitem__(self, key) - - def __delattr__(self, key): - self.__delitem__(key) - - # 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 as exc: - raise InvalidOperation(str(exc)) - dict.update(self, other) - - def iteritems(self): - return copy.deepcopy(dict(self)).iteritems() - - def items(self): - 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): - return copy.deepcopy(self.__dict__['changes']) - - @property - def patch(self): - original = self.__dict__['__original__'] - return jsonpatch.make_patch(original, dict(self)).to_string() + kwargs.setdefault('validator', validator) + base_class.__init__(self, *args, **kwargs) Model.__name__ = str(schema['name']) return Model diff --git a/warlock/exceptions.py b/warlock/exceptions.py new file mode 100644 index 0000000..01d72da --- /dev/null +++ b/warlock/exceptions.py @@ -0,0 +1,9 @@ +""" List of errors used in warlock """ + + +class InvalidOperation(RuntimeError): + pass + + +class ValidationError(ValueError): + pass diff --git a/warlock/model.py b/warlock/model.py new file mode 100644 index 0000000..6dff70a --- /dev/null +++ b/warlock/model.py @@ -0,0 +1,108 @@ +"""Self-validating model for arbitrary objects""" + +import copy + +import jsonpatch + +import exceptions + + +class Model(dict): + def __init__(self, *args, **kwargs): + # Load the validator from the kwargs + #self.__dict__['validator'] = kwargs.pop('validator', self.default_validator) + + # we overload setattr so set this manually + d = dict(*args, **kwargs) + + try: + self.validator(d) + except exceptions.ValidationError as exc: + raise ValueError(str(exc)) + else: + dict.__init__(self, d) + + self.__dict__['changes'] = {} + self.__dict__['__original__'] = copy.deepcopy(d) + + def __getattr__(self, key): + try: + return self.__getitem__(key) + except KeyError: + raise AttributeError(key) + + def __setitem__(self, key, value): + mutation = dict(self.items()) + mutation[key] = value + try: + self.validator(mutation) + except exceptions.ValidationError: + msg = "Unable to set '%s' to '%s'" % (key, value) + raise exceptions.InvalidOperation(msg) + + dict.__setitem__(self, key, value) + + self.__dict__['changes'][key] = value + + def __setattr__(self, key, value): + self.__setitem__(key, value) + + def clear(self): + raise exceptions.InvalidOperation() + + def pop(self, key, default=None): + raise exceptions.InvalidOperation() + + def popitem(self): + raise exceptions.InvalidOperation() + + def __delitem__(self, key): + mutation = dict(self.items()) + del mutation[key] + try: + self.validator(mutation) + except exceptions.ValidationError: + msg = "Unable to delete attribute '%s'" % (key) + raise exceptions.InvalidOperation(msg) + + dict.__delitem__(self, key) + + def __delattr__(self, key): + self.__delitem__(key) + + # 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 exceptions.ValidationError as exc: + raise exceptions.InvalidOperation(str(exc)) + dict.update(self, other) + + def iteritems(self): + return copy.deepcopy(dict(self)).iteritems() + + def items(self): + 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): + return copy.deepcopy(self.__dict__['changes']) + + @property + def patch(self): + original = self.__dict__['__original__'] + return jsonpatch.make_patch(original, dict(self)).to_string() + + def default_validator(self, *args, **kwargs): + return True