Commits

Jean-Tiare Le Bigot committed ee2623f

bring onctuus as validation framework. Called prior to any write

  • Participants
  • Parent commits 057019b

Comments (0)

Files changed (5)

 This section documents all user visible changes included between DynamoDBMapper
 versions 1.8.0 and versions 1.7.1
 
+Additions
+---------
+
+- add ``DynamoDbModel.validate()`` based on Onctuous
+- data are validated prior to any write operations
+
 Changes
 -------
 

File dynamodb_mapper/model.py

 import logging
 from datetime import datetime, timedelta, tzinfo
 
+from onctuous.schema import Schema
+
 import boto
 from boto.dynamodb.item import Item
 from boto.exception import DynamoDBResponseError
         if isinstance(cls.__migrator__, type):
             cls.__migrator__ = cls.__migrator__(cls)
 
+    def validate(self):
+        """Return a ``dict`` of validated fields if validators passes. Otherwise
+        ``InvalidList`` is raised.
+        """
+        # load schema
+        schema = self.__schema__
+        validate = Schema(schema)
+        # load schema data from self
+        data = {str(key):getattr(self, str(key)) for key in schema}
+        # return validated data (or raise)
+        return validate(data)
+
     @classmethod
     def _from_db_dict(cls, raw_data):
         """Build an instance from a dict-like mapping, according to the class's
 
         Overload this method if you need a special serialization semantic
         """
-        out = {}
-        for name in type(self).__schema__:
-            value = _python_to_dynamodb(getattr(self, name))
-            if value is not None:
-                # DynamoDB can't store empty sets/strings, so we must
-                # represent them as missing attributes
-                out[name] = value
-        return out
+        data = self.validate()
+        return {key: _python_to_dynamodb(val) for key, val in data.iteritems() if val or val == 0}
 
     def to_json_dict(self):
         """Return a dict representation of the object, suitable for JSON
             # Compute the index and save the object
             self._save_autoincrement_hash_key(item)
             # Update the primary key so that it reflects what it was saved as.
-            setattr(self, hash_key, item[hash_key])
+            setattr(self, hash_key, autoincrement_int(item[hash_key]))
         # Regular save
         else:
             if raise_on_conflict:
                 raise
 
         # Update Raw_data to reflect DB state on success
-        self._raw_data = self._to_db_dict()
+        self._raw_data = item_data
 
         hash_key_value = getattr(self, hash_key)
         range_key_value = getattr(self, range_key, None) if range_key else None

File dynamodb_mapper/tests/test_model.py

         self.assertEqual(1, d.id)
         self.assertEqual(u"Knee-deep in the Dead", d.name)
 
-    def test_build_from_dict(self):
-        #FIXME: can it be removed as this feature is implicitly tested elsewhere ?
+    def test_build_from_db_dict(self):
         d_dict = {"id": 1, "name": "Knee-deep in the Dead"}
         d = DoomEpisode._from_db_dict(d_dict)
         self.assertEqual(d_dict, d._raw_data)
         self.assertEqual(d_dict["id"], d.id)
         self.assertEqual(d_dict["name"], d.name)
 
-    def test_build_from_dict_missing_attrs(self):
+    def test_build_from_db_dict_autoinc(self):
+        d_dict = {"id": 1, "text": "toto"}
+        d = LogEntry._from_db_dict(d_dict)
+        self.assertEqual(d_dict, d._raw_data)
+        self.assertEqual(d_dict["id"], autoincrement_int(d.id))
+        self.assertEqual(d_dict["text"], d.text)
+
+    def test_build_from_db_dict_missing_attrs(self):
         #FIXME: can it be removed as this feature is implicitly tested elsewhere ?
         d = DoomCampaign._from_db_dict({})
         self.assertEqual(d.id, 0)
         self.assertEqual(d.name, u"")
         self.assertEqual(d.cheats, set())
 
-    def test_build_from_dict_json_list(self):
+    def test_build_from_db_dict_json_list(self):
         #FIXME: can it be removed as this feature is implicitly tested elsewhere ?
         attacks = [
             {'name': 'fireball', 'damage': 10},
         m2 = DoomMonster._from_db_dict({"id": 1})
         self.assertEqual(m2.attacks, [])
 
-    def test_build_from_dict_json_dict(self):
+    def test_build_from_db_dict_json_dict(self):
         #FIXME: can it be removed as this feature is implicitly tested elsewhere ?
         monsters = {
             "cacodemon": [
         self.assertEqual(d["map_id"], 1)
         self.assertEqual(d["monsters"], json.dumps(monsters, sort_keys=True))
 
-
     @mock.patch("dynamodb_mapper.model.Item")
     @mock.patch("dynamodb_mapper.model.boto")
     def test_save_simple_types(self, m_boto, m_item):
     def test_save_sets(self, m_boto, m_item):
         l = DoomCampaign()
         l.id = 1
-        l.name = "Knee-deep in the Dead"
+        l.name = u"Knee-deep in the Dead"
         l.cheats = set(["iddqd", "idkfa", "idclip"])
         l.save()
 
         }
 
         l = LogEntry()
-        l.text = "Everybody's dead, Dave."
+        l.text = u"Everybody's dead, Dave."
         l.save()
 
         m_item.assert_called_with(mock.ANY, attrs={"id": 0, "text": l.text})
         m_item_instance.put.side_effect = err
 
         l = LogEntry()
-        l.text = "Everybody's dead, Dave."
+        l.text = u"Everybody's dead, Dave."
         l.save()
 
         m_item.return_value.__setitem__.assert_called_with("id", 3)
         m_item_instance.put.side_effect = error
 
         l = LogEntry()
-        l.text = "Everybody's dead, Dave."
+        l.text = u"Everybody's dead, Dave."
         self.assertRaises(MaxRetriesExceededError, l.save)
 
         self.assertEqual(m_item_instance.put.call_count, MAX_RETRIES)
         m_item_instance.put.side_effect = error
 
         l = LogEntry()
-        l.text = "Everybody's dead, Dave."
+        l.text = u"Everybody's dead, Dave."
         self.assertRaises(DynamoDBResponseError, l.save)
 
     @mock.patch("dynamodb_mapper.model.Item")
     @mock.patch("dynamodb_mapper.model.boto")
     def test_save_autoincrement_magic_overwrite(self, m_boto, m_item):
         l = LogEntry()
-        l.text = "Everybody's dead, Dave."
-        l.id = MAGIC_KEY
+        l.text = u"Everybody's dead, Dave."
+        l.id = autoincrement_int(MAGIC_KEY)
 
         self.assertRaises(SchemaError, l.save)
 
             None, None, {"__type": "ConditionalCheckFailedException"})
         m_item_instance.put.side_effect = error
 
-        name = "Knee-deep in the Dead"
+        name = u"Knee-deep in the Dead"
         cheats = set(["iddqd", "idkfa"])
 
         c = DoomCampaign()
             None, None, {"__type": "ConditionalCheckFailedException"})
         m_item_instance.put.side_effect = error
 
-        name = "Knee-deep in the Dead"
+        name = u"Knee-deep in the Dead"
         completed = False
 
         c = DoomCampaignStatus()
 
         c = DoomMap()
         c.episode_id = 0
-        c.name = "Knee-deep in the Dead"
+        c.name = u"Knee-deep in the Dead"
 
         self.assertRaises(
             OverwriteError,
 
         c = DoomMap()
         c.episode_id = 0
-        c.name = "Knee-deep in the Dead"
+        c.name = u"Knee-deep in the Dead"
 
         self.assertRaises(DynamoDBResponseError, c.save)
 

File dynamodb_mapper/validators.py

+"""Object mapper for Amazon DynamoDB.
+
+Based in part on mongokit's Document interface.
+
+Released under the GNU LGPL, version 3 or later (see COPYING).
+"""
+
+# The re-branding art :)
+# Btw, I can shamelessly do this as I am the author of both piece of codes.
+from onctuous import *
 requires-dist =
     Sphinx
     boto == 2.6.0
+    onctuous
 
 [files]
 packages =