Jean-Tiare Le Bigot avatar Jean-Tiare Le Bigot committed ee2623f

bring onctuus as validation framework. Called prior to any write

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
 -------
 

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

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)
 

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 =
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.