Max Noel avatar Max Noel committed dc848e0

* Added expected_values argument to DynamoDBModel.save.

Comments (0)

Files changed (1)

dynamodb_mapper/model.py

     pass
 
 
+class ExpectedValueError(Exception):
+    """Conditional write failure.
+    Raised when the expected_values argument to :meth:`DynamoDBModel.save`
+    doesn't match what is stored in the database (i.e. when somebody changed
+    the DB's version of your object behind your back).
+    """
+    pass
+
+
 class autoincrement_int(int):
     """Dummy int subclass for use in your schemas.
 
             super(json_dict, self).__init__()
 
 
+_JSON_TYPES = frozenset([json_list, json_dict])
+
+
 class ConnectionBorg(object):
     """Borg that handles access to DynamoDB.
 
         out = {}
         for (name, type_) in type(self).__schema__.iteritems():
             value = getattr(self, name)
-            if type_ in [json_list, json_dict]:
+            if type_ in _JSON_TYPES:
                 # json serialization hooks for json_* data types.
                 out[name] = json.dumps(value)
             elif value or value == 0:
                     raise
                 # The max key has changed (concurrent write): retry.
 
-    def save(self, allow_overwrite=True):
+    def save(self, allow_overwrite=True, expected_values=None):
         """Save the object to the database.
 
         This method may be used both to insert a new object in the DB, or to
         :param allow_overwrite: If False, the method will only succeed if this
             object's primary keys don't exist in the database (otherwise,
             :class:`OverwriteError` is raised).
+
+        :param expected_values: dict of expected attribute values -- if any
+            one of these values doesn't match what is in the database
+            (i.e. someone went ahead and modified the object in the DB behind
+            your back), the operation fails and raises
+            :class:`ExpectedValueError`.
         """
+        if expected_values is None:
+            expected_values = {}
+
         cls = type(self)
         schema = cls.__schema__
         hash_key = cls.__hash_key__
             setattr(self, hash_key, item[hash_key])
         else:
             if allow_overwrite:
-                expected_values = None
+                expected_db_values = {}
+                schema = type(self).__schema__
+                for (name, value) in expected_values.iteritems():
+                    type_ = schema[name]
+                    # This bit can probably be factored out with to_db_dict
+                    if type_ in _JSON_TYPES:
+                        expected_db_values[name] = json.dumps(value)
+                    elif value or value == 0:
+                        expected_db_values[name] = value
             else:
                 # Forbid overwrites: do a conditional write on
                 # "this hash_key doesn't exist"
-                expected_values = {hash_key: False}
+                expected_db_values = {hash_key: False}
                 if range_key:
-                    expected_values[range_key] = False
+                    expected_db_values[range_key] = False
             try:
-                item.put(expected_values)
+                item.put(expected_db_values)
             except DynamoDBResponseError as e:
                 if e.error_code == "ConditionalCheckFailedException":
+                    if allow_overwrite:
+                        # Failed conditional write
+                        raise ExpectedValueError(item)
+                    # Forbidden overwrite
                     raise OverwriteError(item)
                 # Unhandled exception
                 raise
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.