1414from aws_dbesdk_dynamodb_test_vectors .waiting_boto3_ddb_client import WaitingLocalDynamoClient
1515
1616from 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.
3075# if they are not added, the Table will accept the string as-is.
3176known_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,
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.
87132
88133# KeyConditionExpression strings expect Keys, not Attrs.
89134known_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
94139class 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 )
0 commit comments