Skip to content

Commit b502cd8

Browse files
author
Lucas McDonald
committed
m
1 parent a5adf68 commit b502cd8

File tree

4 files changed

+17
-143
lines changed

4 files changed

+17
-143
lines changed

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

Lines changed: 14 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,16 @@
6262
"contains(One, :oneA)": Attr("One").contains(":oneA"),
6363
"contains(One, :oneB)": Attr("One").contains(":oneB"),
6464
# Hard-coding returning the input string for these cases.
65-
# These conditions test "undocumented behavior" in DynamoDB that can't be easily expressed with boto3 Conditions.
66-
# The "undocumented behavior" is that `contains`' first parameter can be a value,
65+
# These conditions test undocumented behavior in DynamoDB that can't be expressed with boto3 Conditions.
66+
# The undocumented behavior is that `contains`' first parameter can be a value,
6767
# and does not need to be an attribute name.
6868
# DynamoDB documentation names `contains`' first argument as `path`,
6969
# and only ever documents accepting an attribute name for `path`.
7070
# However, testing with an AWS SDK reveals that `path` can be a value;
7171
# i.e. a hardcoded string or an attribute value,
7272
# so this expression is valid.
7373
# But I can't find a way to express this via boto3 Conditions,
74-
# where Contains expects to have some attribute name.
74+
# where Contains requires an attribute name.
7575
# For these strings, do not attempt to convert to boto3 conditions,
7676
# and just return the input string.
7777
# The input string is still passed to the table and tested.
@@ -95,138 +95,6 @@
9595
"Comp1 = :cmp1b": Attr("Comp1").eq(":cmp1b"),
9696
}
9797

98-
99-
def convert_client_expression_to_conditions(expression):
100-
"""
101-
Crypto Tools internal method to convert a DynamoDB filter/key expression to boto3 Resource tokens.
102-
103-
THIS SHOULD NOT BE USED BY ANY EXTERNAL USERS.
104-
This is a basic implementation for simple expressions that will fail with complex expressions.
105-
106-
I have two suggestions for extending this to support more complex expressions:
107-
108-
1) To support one or a few complex expressions, consider extending the existing logic.
109-
110-
2) To support all expressions, consider extending DBESDK for DynamoDB's generated Dafny-Python code.
111-
DBESDK for DynamoDB's generated Dafny-Python code has a DynamoDB filter/conditions expression syntax parser.
112-
113-
Stub code for using the parser from Dafny:
114-
```
115-
from aws_dbesdk_dynamodb.internaldafny.generated.DynamoDBFilterExpr import default__ as filter_expr
116-
import _dafny
117-
from smithy_dafny_standard_library.internaldafny.generated import Wrappers
118-
119-
dafny_expr_token = filter_expr.ParseExpr(
120-
_dafny.Seq(
121-
expression
122-
),
123-
)
124-
```
125-
This will parse a _dafny.Seq of an expression and produce Dafny tokens for the expression.
126-
127-
Reusing this parser and extending it to support boto3 tokens this will involve:
128-
129-
1. Mapping Dafny tokens to boto3 Resource tokens.
130-
(e.g. Dafny class Token_Between -> boto3.dynamodb.conditions.Between)
131-
2. Converting Dafny token grammar to boto3 Resource token grammar.
132-
(e.g.
133-
Dafny: [Token_Between, Token_Open, Token_Attr, Token_And, Token_Attr, Token_Close]
134-
->
135-
boto3: [Between(Attr, Attr)]
136-
)
137-
138-
:param expression: A string of the DynamoDB client expression (e.g., "AttrName = :val").
139-
:return: A boto3.dynamodb.conditions object (Key, Attr, or a combination of them).
140-
"""
141-
142-
# Recursive parser for complex expressions
143-
def parse_expression(expr_tokens):
144-
# simple between
145-
if "BETWEEN" == expr_tokens[1].upper():
146-
attr_name = expr_tokens[0]
147-
value1 = expr_tokens[2]
148-
value2 = expr_tokens[4]
149-
return Key(attr_name).between(value1, value2)
150-
151-
# simple in
152-
elif "IN" == expr_tokens[1].upper():
153-
print(f"IN {expr_tokens=}")
154-
attr_name = expr_tokens[0]
155-
values_in_list = expr_tokens[3:-1]
156-
for i in range(len(values_in_list)):
157-
if values_in_list[i][-1] == ",":
158-
values_in_list[i] = values_in_list[i][:-1]
159-
return Attr(attr_name).is_in(values_in_list)
160-
161-
# simple contains
162-
elif "CONTAINS" == expr_tokens[0].upper():
163-
attr_name = expr_tokens[2]
164-
if attr_name[-1] == ",":
165-
attr_name = attr_name[:-1]
166-
value = expr_tokens[3]
167-
return Attr(attr_name).contains(value)
168-
169-
# simple begins_with
170-
elif "BEGINS_WITH" == expr_tokens[0].upper():
171-
attr_name = expr_tokens[2]
172-
if attr_name[-1] == ",":
173-
attr_name = attr_name[:-1]
174-
value = expr_tokens[3]
175-
return Attr(attr_name).begins_with(value)
176-
177-
# Base case: Single comparison or condition
178-
if "AND" not in [t.upper() for t in expr_tokens] and "OR" not in [t.upper() for t in expr_tokens]:
179-
180-
# simple comparison
181-
attr_name = expr_tokens[0]
182-
operator = expr_tokens[1].upper()
183-
value = expr_tokens[2]
184-
185-
# Map operator to Key or Attr
186-
if operator == "=":
187-
return Key(attr_name).eq(value)
188-
elif operator == "<":
189-
return Key(attr_name).lt(value)
190-
elif operator == "<=":
191-
return Key(attr_name).lte(value)
192-
elif operator == ">":
193-
return Key(attr_name).gt(value)
194-
elif operator == ">=":
195-
return Key(attr_name).gte(value)
196-
elif operator in ("!=", "<>"):
197-
return Attr(attr_name).ne(value)
198-
else:
199-
raise ValueError(f"Unsupported operator: {operator}")
200-
201-
# Recursive case: Logical AND/OR
202-
stack = []
203-
current_expr = []
204-
205-
for token in expr_tokens:
206-
if token.upper() in ("AND", "OR"):
207-
left = parse_expression(current_expr)
208-
current_expr = []
209-
stack.append((left, token)) # Save the left condition and operator
210-
else:
211-
current_expr.append(token)
212-
213-
# Handle the final condition on the right
214-
right = parse_expression(current_expr)
215-
216-
# Combine the stack of conditions
217-
while stack:
218-
left, operator = stack.pop()
219-
if operator.upper() == "AND":
220-
right = And(left, right)
221-
elif operator.upper() == "OR":
222-
right = Or(left, right)
223-
224-
return right
225-
226-
# Tokenize the expression and parse it
227-
tokens = expression.replace("(", " ( ").replace(")", " ) ").split()
228-
return parse_expression(tokens)
229-
23098
# TestVectors-only override of ._flush method:
23199
# persist response in self._response for TestVectors output processing.
232100
def _flush_and_persist_response(self):
@@ -278,7 +146,11 @@ def get_item(self, **kwargs):
278146
return client_output
279147

280148
def batch_write_item(self, **kwargs):
281-
# print(f"batch_write_item {kwargs=}")
149+
table_input = self._client_shape_to_resource_shape_converter.batch_write_item_request(kwargs)
150+
table_output = self._table.batch_write_item(**table_input)
151+
client_output = self._resource_shape_to_client_shape_converter.batch_write_item_response(table_output)
152+
return client_output
153+
282154
# Parse boto3 client.batch_write_item input into table.batch_writer() calls
283155
# client.batch_write_item: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/client/batch_write_item.html
284156
# table.batch_writer(): https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/table/batch_writer.html
@@ -359,11 +231,13 @@ def batch_get_item(self, **kwargs):
359231

360232
def scan(self, **kwargs):
361233
table_input = self._client_shape_to_resource_shape_converter.scan_request(kwargs)
234+
# To exhaustively test Tables,
235+
# convert the string-based KeyConditionExpression and FilterExpression
236+
# into the boto3.conditions.Key and boto3.conditions.Attr resource-formatted queries.
362237
if "KeyConditionExpression" in table_input:
363238
if table_input["KeyConditionExpression"] in known_query_string_to_condition_map:
364239
# Turn the query into the resource-formatted query
365240
query = known_query_string_to_condition_map[table_input["KeyConditionExpression"]]
366-
print(f"{query=}")
367241
table_input["KeyConditionExpression"] = query
368242
if "FilterExpression" in table_input:
369243
if table_input["FilterExpression"] in known_query_string_to_condition_map:
@@ -415,18 +289,18 @@ def transact_write_items(self, **kwargs):
415289

416290
def query(self, **kwargs):
417291
table_input = self._client_shape_to_resource_shape_converter.query_request(kwargs)
418-
print(f"pre maybe transform {table_input=}")
292+
# To exhaustively test Tables,
293+
# convert the string-based KeyConditionExpression and FilterExpression
294+
# into the boto3.conditions.Key and boto3.conditions.Attr resource-formatted queries.
419295
if "KeyConditionExpression" in table_input:
420296
if table_input["KeyConditionExpression"] in known_query_string_to_condition_map:
421297
# Turn the query into the resource-formatted query
422298
query = known_query_string_to_condition_map[table_input["KeyConditionExpression"]]
423-
print(f"{query=}")
424299
table_input["KeyConditionExpression"] = query
425300
if "FilterExpression" in table_input:
426301
if table_input["FilterExpression"] in known_query_string_to_condition_map:
427302
# Turn the query into the resource-formatted query
428303
table_input["FilterExpression"] = known_query_string_to_condition_map[table_input["FilterExpression"]]
429-
print(f"post maybe transform {table_input=}")
430304
table_output = self._table.query(**table_input)
431305
client_output = self._resource_shape_to_client_shape_converter.query_response(table_output)
432306
return client_output

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class DynamoDBFormatToDictFormatWrapper:
3030
3131
Dafny TestVectors provide DynamoDB-formatted items to ItemEncryptors' encrypt_item and decrypt_item methods.
3232
However, the legacy Python DDBEC ItemEncryptor also supports Python dictionary-formatted items.
33-
This class interfaces from Dafny TestVectors' DynamoDB-formatted items
33+
This class transforms Dafny TestVectors' DynamoDB-formatted items
3434
to Python DBESDK's ItemEncryptor's Python dictionary-formatted encryption methods.
3535
"""
3636
def __init__(self, item_encryptor):

submodules/smithy-dafny

0 commit comments

Comments
 (0)