X-Git-Url: https://git.madduck.net/etc/taskwarrior.git/blobdiff_plain/0110af91cb5bf3804efc35cb50c8430419af4313..808c18288b2de67a5d7f533376e3a3beba229a18:/tasklib/task.py

diff --git a/tasklib/task.py b/tasklib/task.py
index 138ea7c..7b512c5 100644
--- a/tasklib/task.py
+++ b/tasklib/task.py
@@ -28,6 +28,37 @@ class TaskWarriorException(Exception):
     pass
 
 
+class ReadOnlyDictView(object):
+    """
+    Provides simplified read-only view upon dict object.
+    """
+
+    def __init__(self, viewed_dict):
+        self.viewed_dict = viewed_dict
+
+    def __getitem__(self, key):
+        return copy.deepcopy(self.viewed_dict.__getitem__(key))
+
+    def __contains__(self, k):
+        return self.viewed_dict.__contains__(k)
+
+    def __iter__(self):
+        for value in self.viewed_dict:
+            yield copy.deepcopy(value)
+
+    def __len__(self):
+        return len(self.viewed_dict)
+
+    def get(self, key, default=None):
+        return copy.deepcopy(self.viewed_dict.get(key, default))
+
+    def items(self):
+        return [copy.deepcopy(v) for v in self.viewed_dict.items()]
+
+    def values(self):
+        return [copy.deepcopy(v) for v in self.viewed_dict.values()]
+
+
 class SerializingObject(object):
     """
     Common ancestor for TaskResource & TaskFilter, since they both
@@ -44,6 +75,15 @@ class SerializingObject(object):
         not export empty-valued attributes) if the attribute
         is not iterable (e.g. list or set), in which case
         a empty iterable should be used.
+
+    Normalizing methods should hold the following contract:
+      - They are used to validate and normalize the user input.
+        Any attribute value that comes from the user (during Task
+        initialization, assignign values to Task attributes, or
+        filtering by user-provided values of attributes) is first
+        validated and normalized using the normalize_{key} method.
+      - If validation or normalization fails, normalizer is expected
+        to raise ValueError.
     """
 
     def _deserialize(self, key, value):
@@ -390,6 +430,9 @@ class Task(TaskResource):
                           for (key, value) in six.iteritems(kwargs))
         self._original_data = copy.deepcopy(self._data)
 
+        # Provide read only access to the original data
+        self.original = ReadOnlyDictView(self._original_data)
+
     def __unicode__(self):
         return self['description']