Skip to content

Commit 2bae13b

Browse files
authored
DynamoDB: Fix projection expression with binary attribute (#6816)
1 parent 971e432 commit 2bae13b

File tree

3 files changed

+36
-1
lines changed

3 files changed

+36
-1
lines changed

moto/dynamodb/models/dynamo_type.py

+10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import base64
12
import copy
23
import decimal
34

@@ -195,6 +196,10 @@ def size(self) -> int:
195196

196197
def to_json(self) -> Dict[str, Any]:
197198
# Returns a regular JSON object where the value can still be/contain a DynamoType
199+
if self.is_binary():
200+
# Binary data cannot be represented in JSON
201+
# AWS returns a base64-encoded value - the SDK's then convert that back
202+
return {self.type: base64.b64encode(self.value).decode("utf-8")}
198203
return {self.type: self.value}
199204

200205
def to_regular_json(self) -> Dict[str, Any]:
@@ -212,6 +217,8 @@ def to_regular_json(self) -> Dict[str, Any]:
212217
val.to_regular_json() if isinstance(val, DynamoType) else val
213218
for val in value
214219
]
220+
if self.is_binary():
221+
value = base64.b64decode(value)
215222
return {self.type: value}
216223

217224
def compare(self, range_comparison: str, range_objs: List[Any]) -> bool:
@@ -236,6 +243,9 @@ def is_list(self) -> bool:
236243
def is_map(self) -> bool:
237244
return self.type == DDBType.MAP
238245

246+
def is_binary(self) -> bool:
247+
return self.type == DDBType.BINARY
248+
239249
def same_type(self, other: "DynamoType") -> bool:
240250
return self.type == other.type
241251

moto/dynamodb/models/utilities.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def dynamo_json_dump(dynamo_object: Any) -> str:
1414

1515

1616
def bytesize(val: str) -> int:
17-
return len(val.encode("utf-8"))
17+
return len(val if isinstance(val, bytes) else val.encode("utf-8"))
1818

1919

2020
def find_nested_key(

tests/test_dynamodb/test_dynamodb.py

+25
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import boto3
66
from boto3.dynamodb.conditions import Attr, Key
7+
from boto3.dynamodb.types import Binary
78
import re
89
from moto import mock_dynamodb, settings
910
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
@@ -5727,6 +5728,30 @@ def test_projection_expression_execution_order():
57275728
)
57285729

57295730

5731+
@mock_dynamodb
5732+
def test_projection_expression_with_binary_attr():
5733+
dynamo_resource = boto3.resource("dynamodb", region_name="us-east-1")
5734+
dynamo_resource.create_table(
5735+
TableName="test",
5736+
AttributeDefinitions=[
5737+
{"AttributeName": "pk", "AttributeType": "S"},
5738+
{"AttributeName": "sk", "AttributeType": "S"},
5739+
],
5740+
KeySchema=[
5741+
{"AttributeName": "pk", "KeyType": "HASH"},
5742+
{"AttributeName": "sk", "KeyType": "RANGE"},
5743+
],
5744+
BillingMode="PAY_PER_REQUEST",
5745+
)
5746+
table = dynamo_resource.Table("test")
5747+
table.put_item(Item={"pk": "pk", "sk": "sk", "key": b"value\xbf"})
5748+
assert table.get_item(
5749+
Key={"pk": "pk", "sk": "sk"},
5750+
ExpressionAttributeNames={"#key": "key"},
5751+
ProjectionExpression="#key",
5752+
)["Item"] == {"key": Binary(b"value\xbf")}
5753+
5754+
57305755
@mock_dynamodb
57315756
def test_invalid_projection_expressions():
57325757
table_name = "test-projection-expressions-table"

0 commit comments

Comments
 (0)