From b890c8aa3045a270b71a9237ce0ae76cd8a9bc1b Mon Sep 17 00:00:00 2001 From: Liam Cato Date: Tue, 19 Mar 2024 16:23:22 +0000 Subject: [PATCH] Fix inject_condition_expressions transform for nested inputs such as transact_write_items --- boto3/dynamodb/transform.py | 30 ++++++++++------ tests/unit/dynamodb/test_transform.py | 49 ++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/boto3/dynamodb/transform.py b/boto3/dynamodb/transform.py index 3944f3151f..1d447614e9 100644 --- a/boto3/dynamodb/transform.py +++ b/boto3/dynamodb/transform.py @@ -258,15 +258,11 @@ def __call__(self, value): built_expression = self._condition_builder.build_expression( value, is_key_condition=self._is_key_condition ) - - self._placeholder_names.update( - built_expression.attribute_name_placeholders - ) - self._placeholder_values.update( - built_expression.attribute_value_placeholders - ) - - return built_expression.condition_expression + return { + "PARAM_NAME_PLACEHOLDER": built_expression.condition_expression, + "ExpressionAttributeNames": built_expression.attribute_name_placeholders, + "ExpressionAttributeValues": built_expression.attribute_value_placeholders + } # Use the user provided value if it is not a ConditonBase object. return value @@ -302,12 +298,24 @@ def _transform_structure( ): if not isinstance(params, collections_abc.Mapping): return - for param in params: + for param in copy.deepcopy(params): if param in model.members: member_model = model.members[param] member_shape = member_model.name if member_shape == target_shape: - params[param] = transformation(params[param]) + new_value = transformation(params[param]) + if isinstance(new_value, dict): + if "PARAM_NAME_PLACEHOLDER" in new_value: + new_value[param] = new_value.pop("PARAM_NAME_PLACEHOLDER") + for key, value in new_value.items(): + if isinstance(value, dict) and key in params and isinstance(params[key], dict): + params[key].update(value) + else: + params[key] = value + else: + params[param] = new_value + else: + params[param] = new_value else: self._transform_parameters( member_model, diff --git a/tests/unit/dynamodb/test_transform.py b/tests/unit/dynamodb/test_transform.py index 0144186b56..f4bc31718c 100644 --- a/tests/unit/dynamodb/test_transform.py +++ b/tests/unit/dynamodb/test_transform.py @@ -42,13 +42,33 @@ def setup_models(self): 'name': 'SampleOperation', 'input': {'shape': 'SampleOperationInputOutput'}, 'output': {'shape': 'SampleOperationInputOutput'}, - } + }, + 'SampleListOperation': { + 'name': 'SampleListOperation', + 'input': {'shape': 'SampleOperationListInputOutput'}, + 'output': {'shape': 'SampleOperationListInputOutput'}, + }, + 'SampleOperationStructList': { + 'name': 'SampleOperationStructListInputOutput', + 'input': {'shape': 'SampleOperationStructListInputOutput'}, + 'output': {'shape': 'SampleOperationStructListInputOutput'}, + }, }, 'shapes': { 'SampleOperationInputOutput': { 'type': 'structure', 'members': {}, }, + 'SampleOperationListInputOutput': { + 'type': 'list', + 'member': {'shape': 'SampleOperationInputOutput'}, + }, + 'SampleOperationStructListInputOutput': { + 'type': 'structure', + 'members': { + 'InputList': {'shape': 'SampleOperationListInputOutput'}, + }, + }, 'String': {'type': 'string'}, }, } @@ -571,6 +591,33 @@ def test_key_and_attr_conditon_expression_with_placeholders(self): }, } + def test_key_and_attr_condition_expression_nested_inside_list(self): + self.operation_model = OperationModel(self.json_model['operations']["SampleOperationStructList"], self.service_model) + params = {"InputList":[{ + 'KeyCondition': Key('foo').eq('bar'), + 'AttrCondition': Attr('biz').eq('baz'), + 'ExpressionAttributeNames': {'#a': 'b'}, + 'ExpressionAttributeValues': {':c': 'd'}, + }]} + self.injector.inject_condition_expressions( + params, self.operation_model + ) + assert params == {"InputList":[{ + 'KeyCondition': '#n1 = :v1', + 'AttrCondition': '#n0 = :v0', + 'ExpressionAttributeNames': { + '#n0': 'biz', + '#n1': 'foo', + '#a': 'b' + }, + 'ExpressionAttributeValues': { + ':v0': 'baz', + ':v1': 'bar', + ':c': 'd' + }, + }]} + + class TestCopyDynamoDBParams(unittest.TestCase): def test_copy_dynamodb_params(self):