<NME> model.py
<BEF> # Copyright 2012 Brian Waldon
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Self-validating model for arbitrary objects"""

import copy
import warnings

import jsonpatch
import jsonschema

from . import exceptions


class Model(dict):
    def __init__(self, *args, **kwargs):
        # we overload setattr so set this manually
        d = dict(*args, **kwargs)

        try:
            self.validate(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 __setitem__(self, key, value):
        mutation = dict(self.items())
        mutation[key] = value
        mutation[key] = value
        try:
            self.validate(mutation)
        except exceptions.ValidationError:
            msg = "Unable to set '%s' to '%s'" % (key, value)
            raise exceptions.InvalidOperation(msg)

        dict.__setitem__(self, key, value)

    def __delitem__(self, key):
        mutation = dict(self.items())
        del mutation[key]
        try:
        del mutation[key]
        try:
            self.validate(mutation)
        except exceptions.ValidationError:
            msg = "Unable to delete attribute '%s'" % (key)
            raise exceptions.InvalidOperation(msg)

        dict.__delitem__(self, key)
            return self.__getitem__(key)
        except KeyError:
            raise AttributeError(key)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __delattr__(self, key):
        self.__delitem__(key)

    # BEGIN dict compatibility methods

    def clear(self):
        raise exceptions.InvalidOperation()

    def pop(self, key, default=None):
        raise exceptions.InvalidOperation()

    def popitem(self):
        raise exceptions.InvalidOperation()

    def copy(self):
        return copy.deepcopy(dict(self))

    def __copy__(self):
        return self.copy()

    def __deepcopy__(self, memo):
        return copy.deepcopy(dict(self), memo)

    def update(self, other):
        mutation = dict(self.items())
        mutation.update(other)
        try:
            self.validate(mutation)
        except exceptions.ValidationError as exc:
            raise exceptions.InvalidOperation(str(exc))
        dict.update(self, other)

    def items(self):
        return copy.deepcopy(dict(self)).items()

    def values(self):
        return copy.deepcopy(dict(self)).values()

    # END dict compatibility methods

    @property
    def patch(self):
        """Return a jsonpatch object representing the delta"""
        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):
        """Apply a JSON schema to an object"""
        try:
            self.validator_instance.validate(obj)

        except jsonschema.ValidationError as exc:
            raise exceptions.ValidationError(str(exc))

<MSG> Merge pull request #11 from isethi/validation

Add description to exception messages
<DFF> @@ -44,8 +44,9 @@ class Model(dict):
         mutation[key] = value
         try:
             self.validate(mutation)
-        except exceptions.ValidationError:
-            msg = "Unable to set '%s' to '%s'" % (key, value)
+        except exceptions.ValidationError as exc:
+            msg = ("Unable to set '%s' to '%s'. Reason: %s"
+                   % (key, value, str(exc)))
             raise exceptions.InvalidOperation(msg)
 
         dict.__setitem__(self, key, value)
@@ -57,8 +58,9 @@ class Model(dict):
         del mutation[key]
         try:
             self.validate(mutation)
-        except exceptions.ValidationError:
-            msg = "Unable to delete attribute '%s'" % (key)
+        except exceptions.ValidationError as exc:
+            msg = ("Unable to delete attribute '%s'. Reason: %s"
+                   % (key, str(exc)))
             raise exceptions.InvalidOperation(msg)
 
         dict.__delitem__(self, key)

Merge pull request #11 from isethi/validation