Skip to content

Commit a02c71c

Browse files
author
Lucas McDonald
committed
sync
1 parent 1b0bdff commit a02c71c

File tree

9 files changed

+3038
-0
lines changed

9 files changed

+3038
-0
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
from aws_cryptography_internal_dynamodb.smithygenerated.com_amazonaws_dynamodb.boto3_conversions import (
2+
InternalBoto3DynamoDBFormatConverter,
3+
)
4+
from boto3.dynamodb.types import TypeDeserializer
5+
6+
7+
class ClientShapeToResourceShapeConverter:
8+
9+
def __init__(self, delete_table_name=True):
10+
self.delete_table_name = delete_table_name
11+
self.boto3_converter = InternalBoto3DynamoDBFormatConverter(
12+
item_handler=TypeDeserializer().deserialize, condition_handler=self.condition_handler
13+
)
14+
15+
def condition_handler(self, expression_key, request):
16+
"""Returns the input condition/names/values as-is."""
17+
# Conditions do not need to be converted from strings to boto3 Attrs.
18+
# Resources accept either strings or Attrs.
19+
condition = request[expression_key]
20+
21+
# This conversion in client_to_resource does not update neither
22+
# ExpressionAttributeNames nor ExpressionAttributeValues.
23+
# However, resource_to_client condition_handler may add new
24+
# ExpressionAttributeNames and ExpressionAttributeValues.
25+
# Smithy-generated code expects condition_handlers to return
26+
# ExpressionAttributeNames and ExpressionAttributeValues.
27+
try:
28+
names = request["ExpressionAttributeNames"]
29+
except KeyError:
30+
names = {}
31+
32+
try:
33+
values = request["ExpressionAttributeValues"]
34+
except KeyError:
35+
values = {}
36+
return condition, names, values
37+
38+
def put_item_request(self, put_item_request):
39+
out = self.boto3_converter.PutItemInput(put_item_request)
40+
# put_item requests on a boto3.resource.Table do not have a table name.
41+
if self.delete_table_name:
42+
del out["TableName"]
43+
return out
44+
45+
def put_item_response(self, put_item_response):
46+
return self.boto3_converter.PutItemOutput(put_item_response)
47+
48+
def get_item_request(self, get_item_request):
49+
out = self.boto3_converter.GetItemInput(get_item_request)
50+
# get_item requests on a boto3.resource.Table do not have a table name.
51+
if self.delete_table_name:
52+
del out["TableName"]
53+
return out
54+
55+
def get_item_response(self, get_item_response):
56+
return self.boto3_converter.GetItemOutput(get_item_response)
57+
58+
def query_request(self, query_request):
59+
out = self.boto3_converter.QueryInput(query_request)
60+
# query requests on a boto3.resource.Table do not have a table name.
61+
if self.delete_table_name:
62+
del out["TableName"]
63+
return out
64+
65+
def query_response(self, query_response):
66+
return self.boto3_converter.QueryOutput(query_response)
67+
68+
def scan_request(self, scan_request):
69+
out = self.boto3_converter.ScanInput(scan_request)
70+
# scan requests on a boto3.resource.Table do not have a table name.
71+
if self.delete_table_name:
72+
del out["TableName"]
73+
return out
74+
75+
def delete_item_request(self, delete_item_request):
76+
out = self.boto3_converter.DeleteItemInput(delete_item_request)
77+
# delete_item requests on a boto3.resource.Table do not have a table name.
78+
if self.delete_table_name:
79+
del out["TableName"]
80+
return out
81+
82+
def update_item_request(self, update_item_request):
83+
out = self.boto3_converter.UpdateItemInput(update_item_request)
84+
# update_item requests on a boto3.resource.Table do not have a table name.
85+
if self.delete_table_name:
86+
del out["TableName"]
87+
return out
88+
89+
def scan_response(self, scan_response):
90+
return self.boto3_converter.ScanOutput(scan_response)
91+
92+
def transact_get_items_request(self, transact_get_items_request):
93+
return self.boto3_converter.TransactGetItemsInput(transact_get_items_request)
94+
95+
def transact_get_items_response(self, transact_get_items_response):
96+
return self.boto3_converter.TransactGetItemsOutput(transact_get_items_response)
97+
98+
def transact_write_items_request(self, transact_write_items_request):
99+
return self.boto3_converter.TransactWriteItemsInput(transact_write_items_request)
100+
101+
def transact_write_items_response(self, transact_write_items_response):
102+
return self.boto3_converter.TransactWriteItemsOutput(transact_write_items_response)
103+
104+
def batch_get_item_request(self, batch_get_item_request):
105+
return self.boto3_converter.BatchGetItemInput(batch_get_item_request)
106+
107+
def batch_get_item_response(self, batch_get_item_response):
108+
return self.boto3_converter.BatchGetItemOutput(batch_get_item_response)
109+
110+
def batch_write_item_request(self, batch_write_item_request):
111+
return self.boto3_converter.BatchWriteItemInput(batch_write_item_request)
112+
113+
def batch_write_item_response(self, batch_write_item_response):
114+
return self.boto3_converter.BatchWriteItemOutput(batch_write_item_response)
115+
116+
def update_item_response(self, update_item_response):
117+
return self.boto3_converter.UpdateItemOutput(update_item_response)
118+
119+
def batch_execute_statement_request(self, batch_execute_statement_request):
120+
return self.boto3_converter.BatchExecuteStatementInput(batch_execute_statement_request)
121+
122+
def batch_execute_statement_response(self, batch_execute_statement_response):
123+
return self.boto3_converter.BatchExecuteStatementOutput(batch_execute_statement_response)
124+
125+
def delete_item_response(self, delete_item_response):
126+
return self.boto3_converter.DeleteItemOutput(delete_item_response)
127+
128+
def execute_statement_request(self, execute_statement_request):
129+
return self.boto3_converter.ExecuteStatementInput(execute_statement_request)
130+
131+
def execute_statement_response(self, execute_statement_response):
132+
return self.boto3_converter.ExecuteStatementOutput(execute_statement_response)
133+
134+
def execute_transaction_request(self, execute_transaction_request):
135+
return self.boto3_converter.ExecuteTransactionInput(execute_transaction_request)
136+
137+
def execute_transaction_response(self, execute_transaction_response):
138+
return self.boto3_converter.ExecuteTransactionOutput(execute_transaction_response)
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
import re
4+
5+
from boto3.dynamodb.conditions import AttributeBase, BuiltConditionExpression, ConditionBase, Key
6+
from boto3.exceptions import (
7+
DynamoDBNeedsConditionError,
8+
DynamoDBNeedsKeyConditionError,
9+
)
10+
11+
ATTR_NAME_REGEX = re.compile(r"[^.\[\]]+(?![^\[]*\])")
12+
13+
14+
class InternalDBESDKDynamoDBConditionExpressionBuilder:
15+
"""This class is used to build condition expressions with placeholders"""
16+
17+
def __init__(self):
18+
self._name_count = 0
19+
self._value_count = 0
20+
self._name_placeholder = "n"
21+
self._value_placeholder = "v"
22+
23+
def _get_name_placeholder(self):
24+
return "#" + self._name_placeholder + str(self._name_count)
25+
26+
def _get_value_placeholder(self):
27+
return ":" + self._value_placeholder + str(self._value_count)
28+
29+
def reset(self):
30+
"""Resets the placeholder name and values"""
31+
self._name_count = 0
32+
self._value_count = 0
33+
34+
def build_expression(
35+
self, condition, attribute_name_placeholders, attribute_value_placeholders, is_key_condition=False
36+
):
37+
"""
38+
Builds the condition expression and the dictionary of placeholders.
39+
40+
:type condition: ConditionBase
41+
:param condition: A condition to be built into a condition expression
42+
string with any necessary placeholders.
43+
44+
:type is_key_condition: Boolean
45+
:param is_key_condition: True if the expression is for a
46+
KeyConditionExpression. False otherwise.
47+
48+
:rtype: (string, dict, dict)
49+
:returns: Will return a string representing the condition with
50+
placeholders inserted where necessary, a dictionary of
51+
placeholders for attribute names, and a dictionary of
52+
placeholders for attribute values. Here is a sample return value:
53+
54+
('#n0 = :v0', {'#n0': 'myattribute'}, {':v1': 'myvalue'})
55+
"""
56+
if not isinstance(condition, ConditionBase):
57+
raise DynamoDBNeedsConditionError(condition)
58+
condition_expression = self._build_expression(
59+
condition,
60+
attribute_name_placeholders,
61+
attribute_value_placeholders,
62+
is_key_condition=is_key_condition,
63+
)
64+
print(f"BuiltConditionExpression {condition_expression=}")
65+
return BuiltConditionExpression(
66+
condition_expression=condition_expression,
67+
attribute_name_placeholders=attribute_name_placeholders,
68+
attribute_value_placeholders=attribute_value_placeholders,
69+
)
70+
71+
def _build_expression(
72+
self,
73+
condition,
74+
attribute_name_placeholders,
75+
attribute_value_placeholders,
76+
is_key_condition,
77+
):
78+
expression_dict = condition.get_expression()
79+
replaced_values = []
80+
for value in expression_dict["values"]:
81+
# Build the necessary placeholders for that value.
82+
# Placeholders are built for both attribute names and values.
83+
replaced_value = self._build_expression_component(
84+
value,
85+
attribute_name_placeholders,
86+
attribute_value_placeholders,
87+
condition.has_grouped_values,
88+
is_key_condition,
89+
)
90+
replaced_values.append(replaced_value)
91+
# Fill out the expression using the operator and the
92+
# values that have been replaced with placeholders.
93+
return expression_dict["format"].format(*replaced_values, operator=expression_dict["operator"])
94+
95+
def _build_expression_component(
96+
self,
97+
value,
98+
attribute_name_placeholders,
99+
attribute_value_placeholders,
100+
has_grouped_values,
101+
is_key_condition,
102+
):
103+
# Continue to recurse if the value is a ConditionBase in order
104+
# to extract out all parts of the expression.
105+
if isinstance(value, ConditionBase):
106+
return self._build_expression(
107+
value,
108+
attribute_name_placeholders,
109+
attribute_value_placeholders,
110+
is_key_condition,
111+
)
112+
# If it is not a ConditionBase, we can recurse no further.
113+
# So we check if it is an attribute and add placeholders for
114+
# its name
115+
elif isinstance(value, AttributeBase):
116+
if is_key_condition and not isinstance(value, Key):
117+
raise DynamoDBNeedsKeyConditionError(
118+
f"Attribute object {value.name} is of type {type(value)}. "
119+
f"KeyConditionExpression only supports Attribute objects "
120+
f"of type Key"
121+
)
122+
return self._build_name_placeholder(value, attribute_name_placeholders)
123+
# If it is anything else, we treat it as a value and thus placeholders
124+
# are needed for the value.
125+
else:
126+
return self._build_value_placeholder(value, attribute_value_placeholders, has_grouped_values)
127+
128+
def _build_name_placeholder(self, value, attribute_name_placeholders):
129+
attribute_name = value.name
130+
# Figure out which parts of the attribute name that needs replacement.
131+
attribute_name_parts = ATTR_NAME_REGEX.findall(attribute_name)
132+
133+
# Add a temporary placeholder for each of these parts.
134+
placeholder_format = ATTR_NAME_REGEX.sub("%s", attribute_name)
135+
str_format_args = []
136+
for part in attribute_name_parts:
137+
# If the the name is already an AttributeName, use it. Don't make a new placeholder.
138+
if part in attribute_name_placeholders:
139+
str_format_args.append(part)
140+
else:
141+
name_placeholder = self._get_name_placeholder()
142+
self._name_count += 1
143+
str_format_args.append(name_placeholder)
144+
# Add the placeholder and value to dictionary of name placeholders.
145+
attribute_name_placeholders[name_placeholder] = part
146+
# Replace the temporary placeholders with the designated placeholders.
147+
return placeholder_format % tuple(str_format_args)
148+
149+
def _build_value_placeholder(self, value, attribute_value_placeholders, has_grouped_values=False):
150+
print(f"{attribute_value_placeholders=}")
151+
# If the values are grouped, we need to add a placeholder for
152+
# each element inside of the actual value.
153+
154+
# Also, you can define a grouped value with a colon here.
155+
# If it's a colon, it's not a grouped value for the sake of this logic.
156+
# Treat it as an "else" case.
157+
if has_grouped_values:
158+
placeholder_list = []
159+
# If it's a pre-defined grouped attribute, don't attempt to unpack it as if it were
160+
for v in value:
161+
print(f"v1 {v=}")
162+
# If the value is already an AttributeValue, reuse it. Don't make a new placeholder.
163+
if v in attribute_value_placeholders:
164+
print("in")
165+
placeholder_list.append(v)
166+
else:
167+
print("not in")
168+
value_placeholder = self._get_value_placeholder()
169+
self._value_count += 1
170+
placeholder_list.append(value_placeholder)
171+
attribute_value_placeholders[value_placeholder] = v
172+
# Assuming the values are grouped by parenthesis.
173+
# IN is the currently the only one that uses this so it maybe
174+
# needed to be changed in future.
175+
return "(" + ", ".join(placeholder_list) + ")"
176+
# Otherwise, treat the value as a single value that needs only
177+
# one placeholder.
178+
else:
179+
print(f"v2 {value=}")
180+
# If the value is already an AttributeValue, reuse it. Don't make a new placeholder.
181+
if value in attribute_value_placeholders:
182+
print("in")
183+
return value
184+
else:
185+
print("not in")
186+
value_placeholder = self._get_value_placeholder()
187+
self._value_count += 1
188+
attribute_value_placeholders[value_placeholder] = value
189+
return value_placeholder

0 commit comments

Comments
 (0)