Skip to content

Commit 0627380

Browse files
authored
fix: DynamoDB key & filter expressions attribute overwrite (#2615)
* fix: DynamoDB key & filter expressions overwrite Signed-off-by: Anton Kukushkin <[email protected]> * Empty names/values fixes Signed-off-by: Anton Kukushkin <[email protected]> --------- Signed-off-by: Anton Kukushkin <[email protected]>
1 parent 7e8d572 commit 0627380

File tree

2 files changed

+67
-6
lines changed

2 files changed

+67
-6
lines changed

awswrangler/dynamodb/_read.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,12 @@ def _convert_condition_base_to_expression(
440440
key_condition_expression: ConditionBase, is_key_condition: bool, serializer: TypeSerializer
441441
) -> dict[str, Any]:
442442
builder = ConditionExpressionBuilder()
443+
444+
# Use different namespaces for key and filter conditions
445+
if is_key_condition:
446+
builder._name_placeholder = "kn"
447+
builder._value_placeholder = "kv"
448+
443449
expression = builder.build_expression(key_condition_expression, is_key_condition=is_key_condition)
444450

445451
return _ExpressionTuple(
@@ -692,8 +698,15 @@ def read_items( # noqa: PLR0912
692698
key_condition_expression, is_key_condition=True, serializer=serializer
693699
)
694700
kwargs["KeyConditionExpression"] = expression_tuple.condition_expression
695-
kwargs["ExpressionAttributeNames"] = expression_tuple.attribute_name_placeholders
696-
kwargs["ExpressionAttributeValues"] = expression_tuple.attribute_value_placeholders
701+
702+
kwargs["ExpressionAttributeNames"] = {
703+
**kwargs.get("ExpressionAttributeNames", {}),
704+
**expression_tuple.attribute_name_placeholders,
705+
}
706+
kwargs["ExpressionAttributeValues"] = {
707+
**kwargs.get("ExpressionAttributeValues", {}),
708+
**expression_tuple.attribute_value_placeholders,
709+
}
697710

698711
if filter_expression:
699712
if isinstance(filter_expression, str):
@@ -703,15 +716,28 @@ def read_items( # noqa: PLR0912
703716
filter_expression, is_key_condition=False, serializer=serializer
704717
)
705718
kwargs["FilterExpression"] = expression_tuple.condition_expression
706-
kwargs["ExpressionAttributeNames"] = expression_tuple.attribute_name_placeholders
707-
kwargs["ExpressionAttributeValues"] = expression_tuple.attribute_value_placeholders
719+
720+
kwargs["ExpressionAttributeNames"] = {
721+
**kwargs.get("ExpressionAttributeNames", {}),
722+
**expression_tuple.attribute_name_placeholders,
723+
}
724+
kwargs["ExpressionAttributeValues"] = {
725+
**kwargs.get("ExpressionAttributeValues", {}),
726+
**expression_tuple.attribute_value_placeholders,
727+
}
708728

709729
if columns:
710730
kwargs["ProjectionExpression"] = ", ".join(columns)
711731
if expression_attribute_names:
712-
kwargs["ExpressionAttributeNames"] = expression_attribute_names
732+
kwargs["ExpressionAttributeNames"] = {
733+
**kwargs.get("ExpressionAttributeNames", {}),
734+
**expression_attribute_names,
735+
}
713736
if expression_attribute_values:
714-
kwargs["ExpressionAttributeValues"] = _serialize_item(expression_attribute_values, serializer)
737+
kwargs["ExpressionAttributeValues"] = {
738+
**kwargs.get("ExpressionAttributeValues", {}),
739+
**_serialize_item(expression_attribute_values, serializer),
740+
}
715741
if max_items_evaluated:
716742
kwargs["Limit"] = max_items_evaluated
717743

tests/unit/test_dynamodb.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,3 +729,38 @@ def test_deserialization_full_scan(params: dict[str, Any], dynamodb_table: str)
729729
assert items_df.iloc[0]["par1"] == "foo"
730730
assert items_df.iloc[1]["par0"] == 1
731731
assert items_df.iloc[1]["par1"] == "bar"
732+
733+
734+
@pytest.mark.parametrize(
735+
"params",
736+
[
737+
{
738+
"KeySchema": [{"AttributeName": "par0", "KeyType": "HASH"}, {"AttributeName": "par1", "KeyType": "RANGE"}],
739+
"AttributeDefinitions": [
740+
{"AttributeName": "par0", "AttributeType": "N"},
741+
{"AttributeName": "par1", "AttributeType": "S"},
742+
],
743+
}
744+
],
745+
)
746+
@pytest.mark.parametrize("use_threads", [True, False])
747+
def test_read_items_expressions(params: dict[str, Any], dynamodb_table: str, use_threads: bool) -> None:
748+
df = pd.DataFrame(
749+
{
750+
"par0": [1, 1, 2],
751+
"par1": ["a", "b", "c"],
752+
"operator": ["Ali", "Sarah", "Eido"],
753+
"volume": [100, 200, 300],
754+
"compliant": [True, False, False],
755+
}
756+
)
757+
wr.dynamodb.put_df(df=df, table_name=dynamodb_table)
758+
759+
# KeyConditionExpression as Key
760+
df2 = wr.dynamodb.read_items(
761+
table_name=dynamodb_table,
762+
key_condition_expression=(Key("par0").eq(1) & Key("par1").eq("b")),
763+
filter_expression=Attr("operator").eq("Sarah"),
764+
use_threads=use_threads,
765+
)
766+
assert df2.shape == (1, len(df.columns))

0 commit comments

Comments
 (0)