Commits

Max Noel committed d1ad794

* "magic" (datetime/list/dict) values are now supported as keys. Note that they're not yet handled in query/scan conditions.

Comments (0)

Files changed (2)

dynamodb_mapper/model.py

     return schema_type()
 
 
+def _get_default_value(schema_type):
+    """Return a default value matching schema_type:
+
+      - For datetime.datetime, it's 1970-01-01 00:00:00 UTC.
+      - For container types, it's an empty container.
+      - For strings, it's an empty string.
+      - For numbers, it's zero.
+    """
+    if schema_type == datetime.datetime:
+        # Beginning of the UNIX epoch
+        return datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=utc_tz)
+
+    return schema_type()
+
+
 def _python_to_dynamodb(schema_type, value):
     """Convert a Python object to a representation suitable to direct storage
     in DynamoDB, according to a type from a DynamoDBModel schema.
             read is performed. Set to True for strongly consistent reads.
         """
         table = ConnectionBorg().get_table(cls.__table__)
+        # Convert the keys to DynamoDB values.
+        h_type = cls.__schema__[cls.__hash_key__]
+        h_value = _python_to_dynamodb(h_type, hash_key_value)
+        if cls.__range_key__:
+            r_type = cls.__schema__[cls.__range_key__]
+            r_value = _python_to_dynamodb(r_type, range_key_value)
+        else:
+            r_value = None
+
         return cls.from_dict(
             table.get_item(
-                hash_key=hash_key_value,
-                range_key=range_key_value,
+                hash_key=h_value,
+                range_key=r_value,
                 consistent_read=consistent_read))
 
     @classmethod
         """
         borg = ConnectionBorg()
         table = borg.get_table(cls.__table__)
+        # Convert all the keys to DynamoDB values.
+        h_type = cls.__schema__[cls.__hash_key__]
+        if cls.__range_key__:
+            r_type = cls.__schema__[cls.__range_key__]
+            dynamo_keys = [
+                (
+                    _python_to_dynamodb(h_type, h),
+                    _python_to_dynamodb(r_type, r)
+                ) for (h, r) in keys
+            ]
+        else:
+            dynamo_keys = [_python_to_dynamodb(h_type, h) for h in keys]
+
         batch_list = borg.new_batch_list()
-        batch_list.add_batch(table, keys)
+        batch_list.add_batch(table, dynamo_keys)
 
         res = batch_list.submit()
         return [
         :rtype: generator
         """
         table = ConnectionBorg().get_table(cls.__table__)
+        h_type = cls.__schema__[cls.__hash_key__]
+        h_value = _python_to_dynamodb(h_type, hash_key_value)
         return (
             cls.from_dict(d)
             for d in table.query(
-                hash_key_value,
+                h_value,
                 range_key_condition,
                 consistent_read=consistent_read)
         )
         We're supplying this method to avoid the need for extra checks in save.
         """
         for (name, type_) in type(self).__schema__.iteritems():
-            setattr(self, name, type_())
+            setattr(self, name, _get_default_value(type_))
 
     def to_db_dict(self):
         """Return a dict representation of the object according to the class's

dynamodb_mapper/tests/test_model.py

     }
 
 
+# datetime.datetime hash key
+class Patch(DynamoDBModel):
+    __table__ = "patch"
+    __hash_key__ = "datetime"
+    __schema__ = {
+        "datetime": datetime.datetime,
+        "description": unicode,
+    }
+
+
+# Composite key with list hash_key, datetime.datetime range_key
+class GameReport(DynamoDBModel):
+    __table__ = "game_report"
+    __hash_key__ = "player_ids"
+    __range_key__ = "datetime"
+    __schema__ = {
+        "player_ids": list,
+        "datetime": datetime.datetime,
+    }
+
+
 
 class TestConnectionBorg(unittest.TestCase):
     def setUp(self):
         m_table.get_item.assert_called_once_with(
             hash_key=1, range_key="Knee-deep in the dead", consistent_read=False)
 
+    @mock.patch("dynamodb_mapper.model.ConnectionBorg.get_table")
+    def test_get_by_hash_key_magic_types(self, m_get_table):
+        m_table = m_get_table.return_value
+        d = datetime.datetime(2012, 5, 31, 12, 0, 0, tzinfo=utc_tz)
+        d_text = "2012-05-31T12:00:00.000000+0000"
+        m_table.get_item.return_value = {"datetime": d_text}
+
+        Patch.get(d)
+        m_table.get_item.assert_called_once_with(
+            hash_key=d_text, range_key=None, consistent_read=False)
+
+    @mock.patch("dynamodb_mapper.model.ConnectionBorg.get_table")
+    def test_get_by_composite_key_magic_types(self, m_get_table):
+        m_table = m_get_table.return_value
+        d = datetime.datetime(2012, 5, 31, 12, 0, 0, tzinfo=utc_tz)
+        d_text = "2012-05-31T12:00:00.000000+0000"
+        players = ["duke", "doomguy", "blackowicz"]
+        players_text = json.dumps(players)
+        m_table.get_item.return_value = {
+            "player_ids": players_text,
+            "datetime": d_text
+        }
+
+        GameReport.get(players, d)
+        m_table.get_item.assert_called_once_with(
+            hash_key=players_text, range_key=d_text, consistent_read=False)
+
     @mock.patch("dynamodb_mapper.model.Item")
     @mock.patch("dynamodb_mapper.model.boto")
     def test_save_autoincrement_int(self, m_boto, m_item):