Skip to content

Commit 1eb388c

Browse files
authored
Conditional Operations using Condition Expressions (#336)
1 parent d009e91 commit 1eb388c

File tree

6 files changed

+630
-5
lines changed

6 files changed

+630
-5
lines changed

pynamodb/connection/base.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import random
99
import time
1010
import uuid
11+
import warnings
1112
from base64 import b64decode
1213

1314
import six
@@ -42,7 +43,7 @@
4243
TableError, QueryError, PutError, DeleteError, UpdateError, GetError, ScanError, TableDoesNotExist,
4344
VerboseClientError
4445
)
45-
from pynamodb.expressions.condition import Path
46+
from pynamodb.expressions.condition import Condition, Path
4647
from pynamodb.expressions.projection import create_projection_expression
4748
from pynamodb.settings import get_settings_value
4849
from pynamodb.signals import pre_dynamodb_send, post_dynamodb_send
@@ -773,6 +774,7 @@ def delete_item(self,
773774
table_name,
774775
hash_key,
775776
range_key=None,
777+
condition=None,
776778
expected=None,
777779
conditional_operator=None,
778780
return_values=None,
@@ -781,11 +783,16 @@ def delete_item(self,
781783
"""
782784
Performs the DeleteItem operation and returns the result
783785
"""
786+
self._check_condition('condition', condition, expected, conditional_operator)
787+
784788
operation_kwargs = {TABLE_NAME: table_name}
785789
operation_kwargs.update(self.get_identifier_map(table_name, hash_key, range_key))
786790
name_placeholders = {}
787791
expression_attribute_values = {}
788792

793+
if condition is not None:
794+
condition_expression = condition.serialize(name_placeholders, expression_attribute_values)
795+
operation_kwargs[CONDITION_EXPRESSION] = condition_expression
789796
if return_values:
790797
operation_kwargs.update(self.get_return_values_map(return_values))
791798
if return_consumed_capacity:
@@ -813,6 +820,7 @@ def update_item(self,
813820
hash_key,
814821
range_key=None,
815822
attribute_updates=None,
823+
condition=None,
816824
expected=None,
817825
return_consumed_capacity=None,
818826
conditional_operator=None,
@@ -821,11 +829,16 @@ def update_item(self,
821829
"""
822830
Performs the UpdateItem operation
823831
"""
832+
self._check_condition('condition', condition, expected, conditional_operator)
833+
824834
operation_kwargs = {TABLE_NAME: table_name}
825835
operation_kwargs.update(self.get_identifier_map(table_name, hash_key, range_key))
826836
name_placeholders = {}
827837
expression_attribute_values = {}
828838

839+
if condition is not None:
840+
condition_expression = condition.serialize(name_placeholders, expression_attribute_values)
841+
operation_kwargs[CONDITION_EXPRESSION] = condition_expression
829842
if return_consumed_capacity:
830843
operation_kwargs.update(self.get_consumed_capacity_map(return_consumed_capacity))
831844
if return_item_collection_metrics:
@@ -871,6 +884,7 @@ def put_item(self,
871884
hash_key,
872885
range_key=None,
873886
attributes=None,
887+
condition=None,
874888
expected=None,
875889
conditional_operator=None,
876890
return_values=None,
@@ -879,6 +893,8 @@ def put_item(self,
879893
"""
880894
Performs the PutItem operation and returns the result
881895
"""
896+
self._check_condition('condition', condition, expected, conditional_operator)
897+
882898
operation_kwargs = {TABLE_NAME: table_name}
883899
operation_kwargs.update(self.get_identifier_map(table_name, hash_key, range_key, key=ITEM))
884900
name_placeholders = {}
@@ -887,6 +903,9 @@ def put_item(self,
887903
if attributes:
888904
attrs = self.get_item_attribute_map(table_name, attributes)
889905
operation_kwargs[ITEM].update(attrs[ITEM])
906+
if condition is not None:
907+
condition_expression = condition.serialize(name_placeholders, expression_attribute_values)
908+
operation_kwargs[CONDITION_EXPRESSION] = condition_expression
890909
if return_consumed_capacity:
891910
operation_kwargs.update(self.get_consumed_capacity_map(return_consumed_capacity))
892911
if return_item_collection_metrics:
@@ -1360,6 +1379,13 @@ def _get_condition(self, table_name, attribute_name, operator, *values):
13601379
]
13611380
return getattr(Path(attribute_name, attribute_name=True), operator)(*values)
13621381

1382+
def _check_condition(self, name, condition, expected_or_filter, conditional_operator):
1383+
if condition is not None:
1384+
if not isinstance(condition, Condition):
1385+
raise ValueError("'{0}' must be an instance of Condition".format(name))
1386+
if expected_or_filter is not None or conditional_operator is not None:
1387+
raise ValueError("Legacy conditional parameters cannot be used with condition expressions")
1388+
13631389
@staticmethod
13641390
def _reverse_dict(d):
13651391
return dict((v, k) for k, v in six.iteritems(d))

pynamodb/connection/table.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def __init__(self,
3030

3131
def delete_item(self, hash_key,
3232
range_key=None,
33+
condition=None,
3334
expected=None,
3435
conditional_operator=None,
3536
return_values=None,
@@ -42,6 +43,7 @@ def delete_item(self, hash_key,
4243
self.table_name,
4344
hash_key,
4445
range_key=range_key,
46+
condition=condition,
4547
expected=expected,
4648
conditional_operator=conditional_operator,
4749
return_values=return_values,
@@ -52,6 +54,7 @@ def update_item(self,
5254
hash_key,
5355
range_key=None,
5456
attribute_updates=None,
57+
condition=None,
5558
expected=None,
5659
conditional_operator=None,
5760
return_consumed_capacity=None,
@@ -66,6 +69,7 @@ def update_item(self,
6669
hash_key,
6770
range_key=range_key,
6871
attribute_updates=attribute_updates,
72+
condition=condition,
6973
expected=expected,
7074
conditional_operator=conditional_operator,
7175
return_consumed_capacity=return_consumed_capacity,
@@ -75,6 +79,7 @@ def update_item(self,
7579
def put_item(self, hash_key,
7680
range_key=None,
7781
attributes=None,
82+
condition=None,
7883
expected=None,
7984
conditional_operator=None,
8085
return_values=None,
@@ -88,6 +93,7 @@ def put_item(self, hash_key,
8893
hash_key,
8994
range_key=range_key,
9095
attributes=attributes,
96+
condition=condition,
9197
expected=expected,
9298
conditional_operator=conditional_operator,
9399
return_values=return_values,

pynamodb/models.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ def __repr__(self):
316316
msg = "{0}<{1}>".format(self.Meta.table_name, serialized.get(HASH))
317317
return six.u(msg)
318318

319-
def delete(self, conditional_operator=None, **expected_values):
319+
def delete(self, condition=None, conditional_operator=None, **expected_values):
320320
"""
321321
Deletes this object from dynamodb
322322
"""
@@ -325,9 +325,10 @@ def delete(self, conditional_operator=None, **expected_values):
325325
if len(expected_values):
326326
kwargs.update(expected=self._build_expected_values(expected_values, DELETE_FILTER_OPERATOR_MAP))
327327
kwargs.update(conditional_operator=conditional_operator)
328+
kwargs.update(condition=condition)
328329
return self._get_connection().delete_item(*args, **kwargs)
329330

330-
def update_item(self, attribute, value=None, action=None, conditional_operator=None, **expected_values):
331+
def update_item(self, attribute, value=None, action=None, condition=None, conditional_operator=None, **expected_values):
331332
"""
332333
Updates an item using the UpdateItem operation.
333334
@@ -365,6 +366,7 @@ def update_item(self, attribute, value=None, action=None, conditional_operator=N
365366
kwargs[pythonic(ATTR_UPDATES)][attribute_cls.attr_name][VALUE] = {ATTR_TYPE_MAP[attribute_cls.attr_type]: value}
366367
kwargs[pythonic(RETURN_VALUES)] = ALL_NEW
367368
kwargs.update(conditional_operator=conditional_operator)
369+
kwargs.update(condition=condition)
368370
data = self._get_connection().update_item(
369371
*args,
370372
**kwargs
@@ -378,7 +380,7 @@ def update_item(self, attribute, value=None, action=None, conditional_operator=N
378380
setattr(self, attr_name, attr.deserialize(value.get(ATTR_TYPE_MAP[attr.attr_type])))
379381
return data
380382

381-
def update(self, attributes, conditional_operator=None, **expected_values):
383+
def update(self, attributes, condition=None, conditional_operator=None, **expected_values):
382384
"""
383385
Updates an item using the UpdateItem operation.
384386
@@ -415,6 +417,7 @@ def update(self, attributes, conditional_operator=None, **expected_values):
415417

416418
kwargs[pythonic(ATTR_UPDATES)][attribute_cls.attr_name] = attr_values
417419

420+
kwargs.update(condition=condition)
418421
data = self._get_connection().update_item(*args, **kwargs)
419422
self._throttle.add_record(data.get(CONSUMED_CAPACITY))
420423
for name, value in data[ATTRIBUTES].items():
@@ -424,7 +427,7 @@ def update(self, attributes, conditional_operator=None, **expected_values):
424427
setattr(self, attr_name, attr.deserialize(value.get(ATTR_TYPE_MAP[attr.attr_type])))
425428
return data
426429

427-
def save(self, conditional_operator=None, **expected_values):
430+
def save(self, condition=None, conditional_operator=None, **expected_values):
428431
"""
429432
Save this object to dynamodb
430433
"""
@@ -433,6 +436,7 @@ def save(self, conditional_operator=None, **expected_values):
433436
if len(expected_values):
434437
kwargs.update(expected=self._build_expected_values(expected_values, PUT_FILTER_OPERATOR_MAP))
435438
kwargs.update(conditional_operator=conditional_operator)
439+
kwargs.update(condition=condition)
436440
data = self._get_connection().put_item(*args, **kwargs)
437441
if isinstance(data, dict):
438442
self._throttle.add_record(data.get(CONSUMED_CAPACITY))

0 commit comments

Comments
 (0)