Skip to content

Commit 2475cd7

Browse files
author
Lucas McDonald
committed
m
1 parent ec6e462 commit 2475cd7

File tree

3 files changed

+102
-37
lines changed

3 files changed

+102
-37
lines changed

DynamoDbEncryption/runtimes/python/src/aws_dbesdk_dynamodb/encrypted/table.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ def _table_operation_logic(
328328
329329
"""
330330
table_input = deepcopy(operation_input)
331+
331332
# EncryptedTable inputs are formatted as standard dictionaries, but DBESDK transformations expect DynamoDB JSON.
332333
# Convert from standard dictionaries to DynamoDB JSON.
333334
input_transform_input = input_resource_to_client_shape_transform_method(table_input)

TestVectors/runtimes/python/src/aws_dbesdk_dynamodb_test_vectors/internaldafny/extern/CreateInterceptedDDBTable.py

Lines changed: 99 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,51 @@
1414
from aws_dbesdk_dynamodb_test_vectors.waiting_boto3_ddb_client import WaitingLocalDynamoClient
1515

1616
from boto3.dynamodb.conditions import Key, Attr
17+
from decimal import Decimal
18+
19+
import json
20+
import os
21+
from typing import Any, Dict
22+
23+
def load_test_data() -> Dict[str, Any]:
24+
"""Load the test data from data.json file."""
25+
# Get the directory of the current file
26+
current_dir = os.getcwd()
27+
# Navigate to the data.json file
28+
data_file = os.path.join(current_dir, 'data.json')
29+
30+
with open(data_file, 'r') as f:
31+
return json.load(f)
32+
33+
expression_attribute_values_from_json = load_test_data()["Values"]
34+
35+
def get_test_value(name) -> Any:
36+
"""
37+
Get a test value from the Values section of data.json.
38+
39+
Args:
40+
name: The name of the value to retrieve (e.g. ":zero", ":one", etc.)
41+
42+
Returns:
43+
The value from the Values section
44+
45+
Raises:
46+
KeyError: If the requested value name is not found
47+
"""
48+
if name not in expression_attribute_values_from_json:
49+
raise KeyError(f"Value '{name}' not found in test data")
50+
value = expression_attribute_values_from_json[name]
51+
if isinstance(value, dict):
52+
if "N" in value:
53+
return Decimal(value["N"])
54+
elif "SS" in value:
55+
return set(value["SS"])
56+
elif "L" in value:
57+
return list(value["L"])
58+
else:
59+
raise ValueError(f"Unknown value type: {value}")
60+
return value
61+
1762

1863
# When querying, DBESDK DDB TestVectors will pass the Table the query as a string.
1964
# The Table could accept this string as-is and process it correctly.
@@ -30,27 +75,27 @@
3075
# if they are not added, the Table will accept the string as-is.
3176
known_filter_expression_string_to_condition_map = {
3277
# "Basic" queries
33-
"RecNum = :zero": Attr("RecNum").eq(":zero"),
34-
"RecNum <= :zero": Attr("RecNum").lte(":zero"),
35-
"RecNum > :zero": Attr("RecNum").gt(":zero"),
36-
"RecNum >= :zero": Attr("RecNum").gte(":zero"),
37-
"RecNum <> :zero": Attr("RecNum").ne(":zero"),
38-
"RecNum = :one": Attr("RecNum").eq(":one"),
39-
"Nine between :zeroD and :three": Attr("Nine").between(":zeroD", ":three"),
40-
"Nine between :nineD and :nine": Attr("Nine").between(":nineD", ":nine"),
41-
"Nine between :nine and :three": Attr("Nine").between(":nine", ":three"),
42-
"Nine between :nine and :nine": Attr("Nine").between(":nine", ":nine"),
43-
"NumberTest = :NumberTest": Attr("NumberTest").eq(":NumberTest"),
44-
"RecNum in (:zero, :one)": Attr("RecNum").is_in([":zero", ":one"]),
45-
"Two = :two": Attr("Two").eq(":two"),
46-
"Two = :two or Three = :three or Four = :four OR Five = :five": Attr("Two").eq(":two") | Attr("Three").eq(":three") | Attr("Four").eq(":four") | Attr("Five").eq(":five"),
47-
"Two = :two and Three = :three and Four = :four and Five = :five": Attr("Two").eq(":two") & Attr("Three").eq(":three") & Attr("Four").eq(":four") & Attr("Five").eq(":five"),
48-
"Two in (:two, :three, :four, :five)": Attr("Two").is_in([":two", ":three", ":four", ":five"]),
49-
"Five in (:two, :three, :four, :five)": Attr("Five").is_in([":two", ":three", ":four", ":five"]),
50-
"Five in (:strset)": Attr("Five").is_in([":strset"]),
51-
"Five in (:strlist)": Attr("Five").is_in([":strlist"]),
52-
"contains(One, :oneA)": Attr("One").contains(":oneA"),
53-
"contains(One, :oneB)": Attr("One").contains(":oneB"),
78+
"RecNum = :zero": Attr("RecNum").eq(get_test_value(":zero")),
79+
"RecNum <= :zero": Attr("RecNum").lte(get_test_value(":zero")),
80+
"RecNum > :zero": Attr("RecNum").gt(get_test_value(":zero")),
81+
"RecNum >= :zero": Attr("RecNum").gte(get_test_value(":zero")),
82+
"RecNum <> :zero": Attr("RecNum").ne(get_test_value(":zero")),
83+
"RecNum = :one": Attr("RecNum").eq(get_test_value(":one")),
84+
"Nine between :zeroD and :three": Attr("Nine").between(get_test_value(":zeroD"), get_test_value(":three")),
85+
"Nine between :nineD and :nine": Attr("Nine").between(get_test_value(":nineD"), get_test_value(":nine")),
86+
"Nine between :nine and :three": Attr("Nine").between(get_test_value(":nine"), get_test_value(":three")),
87+
"Nine between :nine and :nine": Attr("Nine").between(get_test_value(":nine"), get_test_value(":nine")),
88+
"NumberTest = :NumberTest": Attr("NumberTest").eq(get_test_value(":NumberTest")),
89+
"RecNum in (:zero, :one)": Attr("RecNum").is_in([get_test_value(":zero"), get_test_value(":one")]),
90+
"Two = :two": Attr("Two").eq(get_test_value(":two")),
91+
"Two = :two or Three = :three or Four = :four OR Five = :five": Attr("Two").eq(get_test_value(":two")) | Attr("Three").eq(get_test_value(":three")) | Attr("Four").eq(get_test_value(":four")) | Attr("Five").eq(get_test_value(":five")),
92+
"Two = :two and Three = :three and Four = :four and Five = :five": Attr("Two").eq(get_test_value(":two")) & Attr("Three").eq(get_test_value(":three")) & Attr("Four").eq(get_test_value(":four")) & Attr("Five").eq(get_test_value(":five")),
93+
"Two in (:two, :three, :four, :five)": Attr("Two").is_in([get_test_value(":two"), get_test_value(":three"), get_test_value(":four"), get_test_value(":five")]),
94+
"Five in (:two, :three, :four, :five)": Attr("Five").is_in([get_test_value(":two"), get_test_value(":three"), get_test_value(":four"), get_test_value(":five")]),
95+
"Five in (:strset)": Attr("Five").is_in([get_test_value(":strset")]),
96+
"Five in (:strlist)": Attr("Five").is_in([get_test_value(":strlist")]),
97+
"contains(One, :oneA)": Attr("One").contains(get_test_value(":oneA")),
98+
"contains(One, :oneB)": Attr("One").contains(get_test_value(":oneB")),
5499
# Hard-coding returning the input string for these cases.
55100
# These conditions test undocumented behavior in DynamoDB that can't be expressed with boto3 Conditions.
56101
# The undocumented behavior is that `contains`' first parameter can be a value,
@@ -70,14 +115,14 @@
70115
"contains(:strset, One)": "contains(:strset, One)",
71116

72117
# "Complex" queries
73-
"Comp1 := :cmp1a": Attr("Comp1").eq(":cmp1a"),
74-
"begins_with(Comp1, :cmp1c)": Attr("Comp1").begins_with(":cmp1c"),
75-
"cmp1c < Comp1": Attr("cmp1c").lt("Comp1"),
76-
"cmp1c = Comp1": Attr("cmp1c").eq("Comp1"),
77-
"begins_with(Comp1, :cmp1d)": Attr("Comp1").begins_with(":cmp1d"),
78-
"contains(Comp1, :cmp1c)": Attr("Comp1").contains(":cmp1c"),
79-
"contains(Comp1, :cmp1d)": Attr("Comp1").contains(":cmp1d"),
80-
"Comp1 = :cmp1b": Attr("Comp1").eq(":cmp1b"),
118+
"Comp1 := :cmp1a": Attr("Comp1").eq(get_test_value(":cmp1a")),
119+
"begins_with(Comp1, :cmp1c)": Attr("Comp1").begins_with(get_test_value(":cmp1c")),
120+
"cmp1c < Comp1": Attr("cmp1c").lt(get_test_value(":cmp1c")),
121+
"cmp1c = Comp1": Attr("cmp1c").eq(get_test_value(":cmp1c")),
122+
"begins_with(Comp1, :cmp1d)": Attr("Comp1").begins_with(get_test_value(":cmp1d")),
123+
"contains(Comp1, :cmp1c)": Attr("Comp1").contains(get_test_value(":cmp1c")),
124+
"contains(Comp1, :cmp1d)": Attr("Comp1").contains(get_test_value(":cmp1d")),
125+
"Comp1 = :cmp1b": Attr("Comp1").eq(get_test_value(":cmp1b")),
81126

82127
# Another query that can't be translated to boto3 Conditions,
83128
# since attribute values aren't attribute names.
@@ -87,8 +132,8 @@
87132

88133
# KeyConditionExpression strings expect Keys, not Attrs.
89134
known_key_condition_expression_string_to_condition_map = {
90-
"RecNum = :zero": Attr("RecNum").eq(":zero"),
91-
"RecNum = :one": Attr("RecNum").eq(":one"),
135+
"RecNum = :zero": Key("RecNum").eq(get_test_value(":zero")),
136+
"RecNum = :one": Key("RecNum").eq(get_test_value(":one")),
92137
}
93138

94139
class DynamoDBClientWrapperForDynamoDBTable:
@@ -152,17 +197,25 @@ def scan(self, **kwargs):
152197
# into the boto3.conditions.Key and boto3.conditions.Attr resource-formatted queries.
153198
if "KeyConditionExpression" in table_input:
154199
if table_input["KeyConditionExpression"] in known_key_condition_expression_string_to_condition_map:
155-
print(f"Converting {table_input['KeyConditionExpression']=} to {known_key_condition_expression_string_to_condition_map[table_input['KeyConditionExpression']]=}")
156200
table_input["KeyConditionExpression"] = known_key_condition_expression_string_to_condition_map[table_input["KeyConditionExpression"]]
201+
# boto3 Conditions cannot accept any externally-provided ExpressionAttributeValues
202+
# if the KeyConditionExpression is not a string.
203+
# If the KeyConditionExpression was replaced, remove the now-useless ExpressionAttributeValues.
204+
if "ExpressionAttributeValues" in table_input and not isinstance(table_input["KeyConditionExpression"], str):
205+
del table_input["ExpressionAttributeValues"]
157206
else:
158207
# Pass the original string through.
159208
# The table will accept the string as-is.
160209
pass
161210
if "FilterExpression" in table_input:
162211
if table_input["FilterExpression"] in known_filter_expression_string_to_condition_map:
163212
# Turn the query into the resource-formatted query
164-
print(f"Converting {table_input['FilterExpression']=} to {known_filter_expression_string_to_condition_map[table_input['FilterExpression']]=}")
165213
table_input["FilterExpression"] = known_filter_expression_string_to_condition_map[table_input["FilterExpression"]]
214+
# boto3 Conditions cannot accept any externally-provided ExpressionAttributeValues
215+
# if the FilterExpression is not a string.
216+
# If the FilterExpression was replaced, remove the now-useless ExpressionAttributeValues.
217+
if "ExpressionAttributeValues" in table_input and not isinstance(table_input["FilterExpression"], str):
218+
del table_input["ExpressionAttributeValues"]
166219
else:
167220
# Pass the original string through.
168221
# The table will accept the string as-is.
@@ -178,23 +231,33 @@ def transact_write_items(self, **kwargs):
178231
raise NotImplementedError("transact_write_items not supported on table interface; remove tests calling this")
179232

180233
def query(self, **kwargs):
234+
print(f'{kwargs=}')
181235
table_input = self._client_shape_to_resource_shape_converter.query_request(kwargs)
236+
print(f'{table_input=}')
182237
# To exhaustively test Tables,
183238
# convert the string-based KeyConditionExpression and FilterExpression
184239
# into the boto3.conditions.Key and boto3.conditions.Attr resource-formatted queries.
185240
if "KeyConditionExpression" in table_input:
186241
if table_input["KeyConditionExpression"] in known_key_condition_expression_string_to_condition_map:
187-
print(f"Converting {table_input['KeyConditionExpression']=} to {known_key_condition_expression_string_to_condition_map[table_input['KeyConditionExpression']]=}")
188242
table_input["KeyConditionExpression"] = known_key_condition_expression_string_to_condition_map[table_input["KeyConditionExpression"]]
243+
# boto3 Conditions cannot accept any externally-provided ExpressionAttributeValues
244+
# if the KeyConditionExpression is not a string.
245+
# If the KeyConditionExpression was replaced, remove the now-useless ExpressionAttributeValues.
246+
if "ExpressionAttributeValues" in table_input and not isinstance(table_input["KeyConditionExpression"], str):
247+
del table_input["ExpressionAttributeValues"]
189248
else:
190249
# Pass the original string through.
191250
# The table will accept the string as-is.
192251
pass
193252
if "FilterExpression" in table_input:
194253
if table_input["FilterExpression"] in known_filter_expression_string_to_condition_map:
195254
# Turn the query into the resource-formatted query
196-
print(f"Converting {table_input['FilterExpression']=} to {known_filter_expression_string_to_condition_map[table_input['FilterExpression']]=}")
197255
table_input["FilterExpression"] = known_filter_expression_string_to_condition_map[table_input["FilterExpression"]]
256+
# boto3 Conditions cannot accept any externally-provided ExpressionAttributeValues
257+
# if the FilterExpression is not a string.
258+
# If the FilterExpression was replaced, remove the now-useless ExpressionAttributeValues.
259+
if "ExpressionAttributeValues" in table_input and not isinstance(table_input["FilterExpression"], str):
260+
del table_input["ExpressionAttributeValues"]
198261
else:
199262
# Pass the original string through.
200263
# The table will accept the string as-is.
@@ -228,7 +291,7 @@ def CreateInterceptedDDBClient(dafny_encryption_config):
228291
# If needed, >1 table could be supported by setting up an EncryptedTablesManager
229292
raise ValueError(">1 table not supported")
230293
# For TestVectors, use local DynamoDB endpoint
231-
table = boto3.resource('dynamodb', endpoint_url="http://localhost:8000").Table(table_config_names[0])
294+
table = boto3.resource('dynamodb').Table(table_config_names[0])
232295
encrypted_table = EncryptedTable(table = table, encryption_config = native_encryption_config)
233296
wrapped_encrypted_table = DynamoDBClientWrapperForDynamoDBTable(table = encrypted_table, client = boto3_client)
234297
return aws_cryptography_internal_dynamodb.internaldafny.extern.Com_Amazonaws_Dynamodb.default__.DynamoDBClient(wrapped_encrypted_table)

TestVectors/runtimes/python/src/aws_dbesdk_dynamodb_test_vectors/waiting_boto3_ddb_client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ class WaitingLocalDynamoClient:
1010
All other methods besides these are unchanged and will call the boto3 client directly.
1111
"""
1212
def __init__(self):
13-
self._client = boto3.client("dynamodb", endpoint_url="http://localhost:8000")
13+
# self._client = boto3.client("dynamodb", endpoint_url="http://localhost:8000")
14+
self._client = boto3.client("dynamodb")
1415

1516
def __getattr__(self, name):
1617
if hasattr(self._client, name):

0 commit comments

Comments
 (0)