Spaces:
Running
Running
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"). You | |
# may not use this file except in compliance with the License. A copy of | |
# the License is located at | |
# | |
# https://aws.amazon.com/apache2.0/ | |
# | |
# or in the "license" file accompanying this file. This file is | |
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF | |
# ANY KIND, either express or implied. See the License for the specific | |
# language governing permissions and limitations under the License. | |
import copy | |
from boto3.compat import collections_abc | |
from boto3.docs.utils import DocumentModifiedShape | |
from boto3.dynamodb.conditions import ConditionBase, ConditionExpressionBuilder | |
from boto3.dynamodb.types import TypeDeserializer, TypeSerializer | |
def register_high_level_interface(base_classes, **kwargs): | |
base_classes.insert(0, DynamoDBHighLevelResource) | |
class _ForgetfulDict(dict): | |
"""A dictionary that discards any items set on it. For use as `memo` in | |
`copy.deepcopy()` when every instance of a repeated object in the deepcopied | |
data structure should result in a separate copy. | |
""" | |
def __setitem__(self, key, value): | |
pass | |
def copy_dynamodb_params(params, **kwargs): | |
return copy.deepcopy(params, memo=_ForgetfulDict()) | |
class DynamoDBHighLevelResource: | |
def __init__(self, *args, **kwargs): | |
super().__init__(*args, **kwargs) | |
# Apply handler that creates a copy of the user provided dynamodb | |
# item such that it can be modified. | |
self.meta.client.meta.events.register( | |
'provide-client-params.dynamodb', | |
copy_dynamodb_params, | |
unique_id='dynamodb-create-params-copy', | |
) | |
self._injector = TransformationInjector() | |
# Apply the handler that generates condition expressions including | |
# placeholders. | |
self.meta.client.meta.events.register( | |
'before-parameter-build.dynamodb', | |
self._injector.inject_condition_expressions, | |
unique_id='dynamodb-condition-expression', | |
) | |
# Apply the handler that serializes the request from python | |
# types to dynamodb types. | |
self.meta.client.meta.events.register( | |
'before-parameter-build.dynamodb', | |
self._injector.inject_attribute_value_input, | |
unique_id='dynamodb-attr-value-input', | |
) | |
# Apply the handler that deserializes the response from dynamodb | |
# types to python types. | |
self.meta.client.meta.events.register( | |
'after-call.dynamodb', | |
self._injector.inject_attribute_value_output, | |
unique_id='dynamodb-attr-value-output', | |
) | |
# Apply the documentation customizations to account for | |
# the transformations. | |
attr_value_shape_docs = DocumentModifiedShape( | |
'AttributeValue', | |
new_type='valid DynamoDB type', | |
new_description=( | |
'- The value of the attribute. The valid value types are ' | |
'listed in the ' | |
':ref:`DynamoDB Reference Guide<ref_valid_dynamodb_types>`.' | |
), | |
new_example_value=( | |
'\'string\'|123|Binary(b\'bytes\')|True|None|set([\'string\'])' | |
'|set([123])|set([Binary(b\'bytes\')])|[]|{}' | |
), | |
) | |
key_expression_shape_docs = DocumentModifiedShape( | |
'KeyExpression', | |
new_type=( | |
'condition from :py:class:`boto3.dynamodb.conditions.Key` ' | |
'method' | |
), | |
new_description=( | |
'The condition(s) a key(s) must meet. Valid conditions are ' | |
'listed in the ' | |
':ref:`DynamoDB Reference Guide<ref_dynamodb_conditions>`.' | |
), | |
new_example_value='Key(\'mykey\').eq(\'myvalue\')', | |
) | |
con_expression_shape_docs = DocumentModifiedShape( | |
'ConditionExpression', | |
new_type=( | |
'condition from :py:class:`boto3.dynamodb.conditions.Attr` ' | |
'method' | |
), | |
new_description=( | |
'The condition(s) an attribute(s) must meet. Valid conditions ' | |
'are listed in the ' | |
':ref:`DynamoDB Reference Guide<ref_dynamodb_conditions>`.' | |
), | |
new_example_value='Attr(\'myattribute\').eq(\'myvalue\')', | |
) | |
self.meta.client.meta.events.register( | |
'docs.*.dynamodb.*.complete-section', | |
attr_value_shape_docs.replace_documentation_for_matching_shape, | |
unique_id='dynamodb-attr-value-docs', | |
) | |
self.meta.client.meta.events.register( | |
'docs.*.dynamodb.*.complete-section', | |
key_expression_shape_docs.replace_documentation_for_matching_shape, | |
unique_id='dynamodb-key-expression-docs', | |
) | |
self.meta.client.meta.events.register( | |
'docs.*.dynamodb.*.complete-section', | |
con_expression_shape_docs.replace_documentation_for_matching_shape, | |
unique_id='dynamodb-cond-expression-docs', | |
) | |
class TransformationInjector: | |
"""Injects the transformations into the user provided parameters.""" | |
def __init__( | |
self, | |
transformer=None, | |
condition_builder=None, | |
serializer=None, | |
deserializer=None, | |
): | |
self._transformer = transformer | |
if transformer is None: | |
self._transformer = ParameterTransformer() | |
self._condition_builder = condition_builder | |
if condition_builder is None: | |
self._condition_builder = ConditionExpressionBuilder() | |
self._serializer = serializer | |
if serializer is None: | |
self._serializer = TypeSerializer() | |
self._deserializer = deserializer | |
if deserializer is None: | |
self._deserializer = TypeDeserializer() | |
def inject_condition_expressions(self, params, model, **kwargs): | |
"""Injects the condition expression transformation into the parameters | |
This injection includes transformations for ConditionExpression shapes | |
and KeyExpression shapes. It also handles any placeholder names and | |
values that are generated when transforming the condition expressions. | |
""" | |
self._condition_builder.reset() | |
generated_names = {} | |
generated_values = {} | |
# Create and apply the Condition Expression transformation. | |
transformation = ConditionExpressionTransformation( | |
self._condition_builder, | |
placeholder_names=generated_names, | |
placeholder_values=generated_values, | |
is_key_condition=False, | |
) | |
self._transformer.transform( | |
params, model.input_shape, transformation, 'ConditionExpression' | |
) | |
# Create and apply the Key Condition Expression transformation. | |
transformation = ConditionExpressionTransformation( | |
self._condition_builder, | |
placeholder_names=generated_names, | |
placeholder_values=generated_values, | |
is_key_condition=True, | |
) | |
self._transformer.transform( | |
params, model.input_shape, transformation, 'KeyExpression' | |
) | |
expr_attr_names_input = 'ExpressionAttributeNames' | |
expr_attr_values_input = 'ExpressionAttributeValues' | |
# Now that all of the condition expression transformation are done, | |
# update the placeholder dictionaries in the request. | |
if expr_attr_names_input in params: | |
params[expr_attr_names_input].update(generated_names) | |
else: | |
if generated_names: | |
params[expr_attr_names_input] = generated_names | |
if expr_attr_values_input in params: | |
params[expr_attr_values_input].update(generated_values) | |
else: | |
if generated_values: | |
params[expr_attr_values_input] = generated_values | |
def inject_attribute_value_input(self, params, model, **kwargs): | |
"""Injects DynamoDB serialization into parameter input""" | |
self._transformer.transform( | |
params, | |
model.input_shape, | |
self._serializer.serialize, | |
'AttributeValue', | |
) | |
def inject_attribute_value_output(self, parsed, model, **kwargs): | |
"""Injects DynamoDB deserialization into responses""" | |
if model.output_shape is not None: | |
self._transformer.transform( | |
parsed, | |
model.output_shape, | |
self._deserializer.deserialize, | |
'AttributeValue', | |
) | |
class ConditionExpressionTransformation: | |
"""Provides a transformation for condition expressions | |
The ``ParameterTransformer`` class can call this class directly | |
to transform the condition expressions in the parameters provided. | |
""" | |
def __init__( | |
self, | |
condition_builder, | |
placeholder_names, | |
placeholder_values, | |
is_key_condition=False, | |
): | |
self._condition_builder = condition_builder | |
self._placeholder_names = placeholder_names | |
self._placeholder_values = placeholder_values | |
self._is_key_condition = is_key_condition | |
def __call__(self, value): | |
if isinstance(value, ConditionBase): | |
# Create a conditional expression string with placeholders | |
# for the provided condition. | |
built_expression = self._condition_builder.build_expression( | |
value, is_key_condition=self._is_key_condition | |
) | |
self._placeholder_names.update( | |
built_expression.attribute_name_placeholders | |
) | |
self._placeholder_values.update( | |
built_expression.attribute_value_placeholders | |
) | |
return built_expression.condition_expression | |
# Use the user provided value if it is not a ConditonBase object. | |
return value | |
class ParameterTransformer: | |
"""Transforms the input to and output from botocore based on shape""" | |
def transform(self, params, model, transformation, target_shape): | |
"""Transforms the dynamodb input to or output from botocore | |
It applies a specified transformation whenever a specific shape name | |
is encountered while traversing the parameters in the dictionary. | |
:param params: The parameters structure to transform. | |
:param model: The operation model. | |
:param transformation: The function to apply the parameter | |
:param target_shape: The name of the shape to apply the | |
transformation to | |
""" | |
self._transform_parameters(model, params, transformation, target_shape) | |
def _transform_parameters( | |
self, model, params, transformation, target_shape | |
): | |
type_name = model.type_name | |
if type_name in ('structure', 'map', 'list'): | |
getattr(self, f'_transform_{type_name}')( | |
model, params, transformation, target_shape | |
) | |
def _transform_structure( | |
self, model, params, transformation, target_shape | |
): | |
if not isinstance(params, collections_abc.Mapping): | |
return | |
for param in params: | |
if param in model.members: | |
member_model = model.members[param] | |
member_shape = member_model.name | |
if member_shape == target_shape: | |
params[param] = transformation(params[param]) | |
else: | |
self._transform_parameters( | |
member_model, | |
params[param], | |
transformation, | |
target_shape, | |
) | |
def _transform_map(self, model, params, transformation, target_shape): | |
if not isinstance(params, collections_abc.Mapping): | |
return | |
value_model = model.value | |
value_shape = value_model.name | |
for key, value in params.items(): | |
if value_shape == target_shape: | |
params[key] = transformation(value) | |
else: | |
self._transform_parameters( | |
value_model, params[key], transformation, target_shape | |
) | |
def _transform_list(self, model, params, transformation, target_shape): | |
if not isinstance(params, collections_abc.MutableSequence): | |
return | |
member_model = model.member | |
member_shape = member_model.name | |
for i, item in enumerate(params): | |
if member_shape == target_shape: | |
params[i] = transformation(item) | |
else: | |
self._transform_parameters( | |
member_model, params[i], transformation, target_shape | |
) | |