DynamoDB - extend TransactWriteItems error handling (#5012)
This commit is contained in:
parent
a0eb48d588
commit
f8f6f6f1ee
@ -1,15 +1,24 @@
|
|||||||
|
import json
|
||||||
|
from moto.core.exceptions import JsonRESTError
|
||||||
from moto.dynamodb.limits import HASH_KEY_MAX_LENGTH, RANGE_KEY_MAX_LENGTH
|
from moto.dynamodb.limits import HASH_KEY_MAX_LENGTH, RANGE_KEY_MAX_LENGTH
|
||||||
|
|
||||||
|
|
||||||
class InvalidIndexNameError(ValueError):
|
class DynamodbException(JsonRESTError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MockValidationException(ValueError):
|
class MockValidationException(DynamodbException):
|
||||||
|
error_type = "com.amazonaws.dynamodb.v20111205#ValidationException"
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
|
super().__init__(MockValidationException.error_type, message=message)
|
||||||
self.exception_msg = message
|
self.exception_msg = message
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidIndexNameError(MockValidationException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidUpdateExpressionInvalidDocumentPath(MockValidationException):
|
class InvalidUpdateExpressionInvalidDocumentPath(MockValidationException):
|
||||||
invalid_update_expression_msg = (
|
invalid_update_expression_msg = (
|
||||||
"The document path provided in the update expression is invalid for update"
|
"The document path provided in the update expression is invalid for update"
|
||||||
@ -183,19 +192,37 @@ class IncorrectDataType(MockValidationException):
|
|||||||
super().__init__(self.inc_data_type_msg)
|
super().__init__(self.inc_data_type_msg)
|
||||||
|
|
||||||
|
|
||||||
class ConditionalCheckFailed(ValueError):
|
class ConditionalCheckFailed(DynamodbException):
|
||||||
msg = "The conditional request failed"
|
error_type = "com.amazonaws.dynamodb.v20111205#ConditionalCheckFailedException"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, msg=None):
|
||||||
super().__init__(self.msg)
|
super().__init__(
|
||||||
|
ConditionalCheckFailed.error_type, msg or "The conditional request failed"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TransactionCanceledException(ValueError):
|
class TransactionCanceledException(DynamodbException):
|
||||||
cancel_reason_msg = "Transaction cancelled, please refer cancellation reasons for specific reasons [{}]"
|
cancel_reason_msg = "Transaction cancelled, please refer cancellation reasons for specific reasons [{}]"
|
||||||
|
error_type = "com.amazonaws.dynamodb.v20120810#TransactionCanceledException"
|
||||||
|
|
||||||
def __init__(self, errors):
|
def __init__(self, errors):
|
||||||
msg = self.cancel_reason_msg.format(", ".join([str(err) for err in errors]))
|
msg = self.cancel_reason_msg.format(
|
||||||
super().__init__(msg)
|
", ".join([str(code) for code, _ in errors])
|
||||||
|
)
|
||||||
|
super().__init__(
|
||||||
|
error_type=TransactionCanceledException.error_type, message=msg
|
||||||
|
)
|
||||||
|
reasons = [
|
||||||
|
{"Code": code, "Message": message} if code else {"Code": "None"}
|
||||||
|
for code, message in errors
|
||||||
|
]
|
||||||
|
self.description = json.dumps(
|
||||||
|
{
|
||||||
|
"__type": TransactionCanceledException.error_type,
|
||||||
|
"CancellationReasons": reasons,
|
||||||
|
"Message": msg,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MultipleTransactionsException(MockValidationException):
|
class MultipleTransactionsException(MockValidationException):
|
||||||
@ -240,3 +267,46 @@ class TooManyAddClauses(InvalidUpdateExpression):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(self.msg)
|
super().__init__(self.msg)
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceNotFoundException(JsonRESTError):
|
||||||
|
def __init__(self, msg=None):
|
||||||
|
err = "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
|
||||||
|
super().__init__(err, msg or "Requested resource not found")
|
||||||
|
|
||||||
|
|
||||||
|
class TableNotFoundException(JsonRESTError):
|
||||||
|
def __init__(self, name):
|
||||||
|
err = "com.amazonaws.dynamodb.v20111205#TableNotFoundException"
|
||||||
|
msg = "Table not found: {}".format(name)
|
||||||
|
super().__init__(err, msg)
|
||||||
|
|
||||||
|
|
||||||
|
class SourceTableNotFoundException(JsonRESTError):
|
||||||
|
def __init__(self, source_table_name):
|
||||||
|
er = "com.amazonaws.dynamodb.v20111205#SourceTableNotFoundException"
|
||||||
|
super().__init__(er, "Source table not found: %s" % source_table_name)
|
||||||
|
|
||||||
|
|
||||||
|
class BackupNotFoundException(JsonRESTError):
|
||||||
|
def __init__(self, backup_arn):
|
||||||
|
er = "com.amazonaws.dynamodb.v20111205#BackupNotFoundException"
|
||||||
|
super().__init__(er, "Backup not found: %s" % backup_arn)
|
||||||
|
|
||||||
|
|
||||||
|
class TableAlreadyExistsException(JsonRESTError):
|
||||||
|
def __init__(self, target_table_name):
|
||||||
|
er = "com.amazonaws.dynamodb.v20111205#TableAlreadyExistsException"
|
||||||
|
super().__init__(er, "Table already exists: %s" % target_table_name)
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceInUseException(JsonRESTError):
|
||||||
|
def __init__(self):
|
||||||
|
er = "com.amazonaws.dynamodb.v20111205#ResourceInUseException"
|
||||||
|
super().__init__(er, "Resource in use")
|
||||||
|
|
||||||
|
|
||||||
|
class StreamAlreadyEnabledException(JsonRESTError):
|
||||||
|
def __init__(self):
|
||||||
|
er = "com.amazonaws.dynamodb.v20111205#ResourceInUseException"
|
||||||
|
super().__init__(er, "Cannot enable stream")
|
||||||
|
@ -25,6 +25,14 @@ from moto.dynamodb.exceptions import (
|
|||||||
InvalidAttributeTypeError,
|
InvalidAttributeTypeError,
|
||||||
MultipleTransactionsException,
|
MultipleTransactionsException,
|
||||||
TooManyTransactionsException,
|
TooManyTransactionsException,
|
||||||
|
TableNotFoundException,
|
||||||
|
ResourceNotFoundException,
|
||||||
|
SourceTableNotFoundException,
|
||||||
|
TableAlreadyExistsException,
|
||||||
|
BackupNotFoundException,
|
||||||
|
ResourceInUseException,
|
||||||
|
StreamAlreadyEnabledException,
|
||||||
|
MockValidationException,
|
||||||
)
|
)
|
||||||
from moto.dynamodb.models.utilities import bytesize
|
from moto.dynamodb.models.utilities import bytesize
|
||||||
from moto.dynamodb.models.dynamo_type import DynamoType
|
from moto.dynamodb.models.dynamo_type import DynamoType
|
||||||
@ -648,7 +656,7 @@ class Table(CloudFormationModel):
|
|||||||
overwrite=False,
|
overwrite=False,
|
||||||
):
|
):
|
||||||
if self.hash_key_attr not in item_attrs.keys():
|
if self.hash_key_attr not in item_attrs.keys():
|
||||||
raise KeyError(
|
raise MockValidationException(
|
||||||
"One or more parameter values were invalid: Missing the key "
|
"One or more parameter values were invalid: Missing the key "
|
||||||
+ self.hash_key_attr
|
+ self.hash_key_attr
|
||||||
+ " in the item"
|
+ " in the item"
|
||||||
@ -656,7 +664,7 @@ class Table(CloudFormationModel):
|
|||||||
hash_value = DynamoType(item_attrs.get(self.hash_key_attr))
|
hash_value = DynamoType(item_attrs.get(self.hash_key_attr))
|
||||||
if self.has_range_key:
|
if self.has_range_key:
|
||||||
if self.range_key_attr not in item_attrs.keys():
|
if self.range_key_attr not in item_attrs.keys():
|
||||||
raise KeyError(
|
raise MockValidationException(
|
||||||
"One or more parameter values were invalid: Missing the key "
|
"One or more parameter values were invalid: Missing the key "
|
||||||
+ self.range_key_attr
|
+ self.range_key_attr
|
||||||
+ " in the item"
|
+ " in the item"
|
||||||
@ -725,7 +733,7 @@ class Table(CloudFormationModel):
|
|||||||
|
|
||||||
def get_item(self, hash_key, range_key=None, projection_expression=None):
|
def get_item(self, hash_key, range_key=None, projection_expression=None):
|
||||||
if self.has_range_key and not range_key:
|
if self.has_range_key and not range_key:
|
||||||
raise ValueError(
|
raise MockValidationException(
|
||||||
"Table has a range key, but no range key was passed into get_item"
|
"Table has a range key, but no range key was passed into get_item"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
@ -780,7 +788,7 @@ class Table(CloudFormationModel):
|
|||||||
all_indexes = self.all_indexes()
|
all_indexes = self.all_indexes()
|
||||||
indexes_by_name = dict((i.name, i) for i in all_indexes)
|
indexes_by_name = dict((i.name, i) for i in all_indexes)
|
||||||
if index_name not in indexes_by_name:
|
if index_name not in indexes_by_name:
|
||||||
raise ValueError(
|
raise MockValidationException(
|
||||||
"Invalid index: %s for table: %s. Available indexes are: %s"
|
"Invalid index: %s for table: %s. Available indexes are: %s"
|
||||||
% (index_name, self.name, ", ".join(indexes_by_name.keys()))
|
% (index_name, self.name, ", ".join(indexes_by_name.keys()))
|
||||||
)
|
)
|
||||||
@ -791,7 +799,9 @@ class Table(CloudFormationModel):
|
|||||||
key for key in index.schema if key["KeyType"] == "HASH"
|
key for key in index.schema if key["KeyType"] == "HASH"
|
||||||
][0]
|
][0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise ValueError("Missing Hash Key. KeySchema: %s" % index.name)
|
raise MockValidationException(
|
||||||
|
"Missing Hash Key. KeySchema: %s" % index.name
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
index_range_key = [
|
index_range_key = [
|
||||||
@ -904,11 +914,13 @@ class Table(CloudFormationModel):
|
|||||||
def all_indexes(self):
|
def all_indexes(self):
|
||||||
return (self.global_indexes or []) + (self.indexes or [])
|
return (self.global_indexes or []) + (self.indexes or [])
|
||||||
|
|
||||||
def get_index(self, index_name, err=None):
|
def get_index(self, index_name, error_if_not=False):
|
||||||
all_indexes = self.all_indexes()
|
all_indexes = self.all_indexes()
|
||||||
indexes_by_name = dict((i.name, i) for i in all_indexes)
|
indexes_by_name = dict((i.name, i) for i in all_indexes)
|
||||||
if err and index_name not in indexes_by_name:
|
if error_if_not and index_name not in indexes_by_name:
|
||||||
raise err
|
raise InvalidIndexNameError(
|
||||||
|
"The table does not have the specified index: %s" % index_name
|
||||||
|
)
|
||||||
return indexes_by_name[index_name]
|
return indexes_by_name[index_name]
|
||||||
|
|
||||||
def has_idx_items(self, index_name):
|
def has_idx_items(self, index_name):
|
||||||
@ -938,10 +950,7 @@ class Table(CloudFormationModel):
|
|||||||
scanned_count = 0
|
scanned_count = 0
|
||||||
|
|
||||||
if index_name:
|
if index_name:
|
||||||
err = InvalidIndexNameError(
|
self.get_index(index_name, error_if_not=True)
|
||||||
"The table does not have the specified index: %s" % index_name
|
|
||||||
)
|
|
||||||
self.get_index(index_name, err)
|
|
||||||
items = self.has_idx_items(index_name)
|
items = self.has_idx_items(index_name)
|
||||||
else:
|
else:
|
||||||
items = self.all_items()
|
items = self.all_items()
|
||||||
@ -1182,12 +1191,14 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
|
|
||||||
def create_table(self, name, **params):
|
def create_table(self, name, **params):
|
||||||
if name in self.tables:
|
if name in self.tables:
|
||||||
return None
|
raise ResourceInUseException
|
||||||
table = Table(name, region=self.region_name, **params)
|
table = Table(name, region=self.region_name, **params)
|
||||||
self.tables[name] = table
|
self.tables[name] = table
|
||||||
return table
|
return table
|
||||||
|
|
||||||
def delete_table(self, name):
|
def delete_table(self, name):
|
||||||
|
if name not in self.tables:
|
||||||
|
raise ResourceNotFoundException
|
||||||
return self.tables.pop(name, None)
|
return self.tables.pop(name, None)
|
||||||
|
|
||||||
def describe_endpoints(self):
|
def describe_endpoints(self):
|
||||||
@ -1211,11 +1222,10 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def list_tags_of_resource(self, table_arn):
|
def list_tags_of_resource(self, table_arn):
|
||||||
required_table = None
|
|
||||||
for table in self.tables:
|
for table in self.tables:
|
||||||
if self.tables[table].table_arn == table_arn:
|
if self.tables[table].table_arn == table_arn:
|
||||||
required_table = self.tables[table]
|
return self.tables[table].tags
|
||||||
return required_table.tags
|
raise ResourceNotFoundException
|
||||||
|
|
||||||
def list_tables(self, limit, exclusive_start_table_name):
|
def list_tables(self, limit, exclusive_start_table_name):
|
||||||
all_tables = list(self.tables.keys())
|
all_tables = list(self.tables.keys())
|
||||||
@ -1240,7 +1250,7 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
return tables, None
|
return tables, None
|
||||||
|
|
||||||
def describe_table(self, name):
|
def describe_table(self, name):
|
||||||
table = self.tables[name]
|
table = self.get_table(name)
|
||||||
return table.describe(base_key="Table")
|
return table.describe(base_key="Table")
|
||||||
|
|
||||||
def update_table(
|
def update_table(
|
||||||
@ -1281,7 +1291,7 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
stream_specification.get("StreamEnabled")
|
stream_specification.get("StreamEnabled")
|
||||||
or stream_specification.get("StreamViewType")
|
or stream_specification.get("StreamViewType")
|
||||||
) and table.latest_stream_label:
|
) and table.latest_stream_label:
|
||||||
raise ValueError("Table already has stream enabled")
|
raise StreamAlreadyEnabledException
|
||||||
table.set_stream_specification(stream_specification)
|
table.set_stream_specification(stream_specification)
|
||||||
return table
|
return table
|
||||||
|
|
||||||
@ -1338,9 +1348,7 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
expression_attribute_values=None,
|
expression_attribute_values=None,
|
||||||
overwrite=False,
|
overwrite=False,
|
||||||
):
|
):
|
||||||
table = self.tables.get(table_name)
|
table = self.get_table(table_name)
|
||||||
if not table:
|
|
||||||
return None
|
|
||||||
return table.put_item(
|
return table.put_item(
|
||||||
item_attrs,
|
item_attrs,
|
||||||
expected,
|
expected,
|
||||||
@ -1377,22 +1385,37 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
if table.hash_key_attr not in keys or (
|
if table.hash_key_attr not in keys or (
|
||||||
table.has_range_key and table.range_key_attr not in keys
|
table.has_range_key and table.range_key_attr not in keys
|
||||||
):
|
):
|
||||||
raise ValueError(
|
# "Table has a range key, but no range key was passed into get_item"
|
||||||
"Table has a range key, but no range key was passed into get_item"
|
raise MockValidationException("Validation Exception")
|
||||||
)
|
|
||||||
hash_key = DynamoType(keys[table.hash_key_attr])
|
hash_key = DynamoType(keys[table.hash_key_attr])
|
||||||
range_key = (
|
range_key = (
|
||||||
DynamoType(keys[table.range_key_attr]) if table.has_range_key else None
|
DynamoType(keys[table.range_key_attr]) if table.has_range_key else None
|
||||||
)
|
)
|
||||||
return hash_key, range_key
|
return hash_key, range_key
|
||||||
|
|
||||||
|
def get_schema(self, table_name, index_name):
|
||||||
|
table = self.get_table(table_name)
|
||||||
|
if index_name:
|
||||||
|
all_indexes = (table.global_indexes or []) + (table.indexes or [])
|
||||||
|
indexes_by_name = dict((i.name, i) for i in all_indexes)
|
||||||
|
if index_name not in indexes_by_name:
|
||||||
|
raise ResourceNotFoundException(
|
||||||
|
"Invalid index: {} for table: {}. Available indexes are: {}".format(
|
||||||
|
index_name, table_name, ", ".join(indexes_by_name.keys())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return indexes_by_name[index_name].schema
|
||||||
|
else:
|
||||||
|
return table.schema
|
||||||
|
|
||||||
def get_table(self, table_name):
|
def get_table(self, table_name):
|
||||||
|
if table_name not in self.tables:
|
||||||
|
raise ResourceNotFoundException()
|
||||||
return self.tables.get(table_name)
|
return self.tables.get(table_name)
|
||||||
|
|
||||||
def get_item(self, table_name, keys, projection_expression=None):
|
def get_item(self, table_name, keys, projection_expression=None):
|
||||||
table = self.get_table(table_name)
|
table = self.get_table(table_name)
|
||||||
if not table:
|
|
||||||
raise ValueError("No table found")
|
|
||||||
hash_key, range_key = self.get_keys_value(table, keys)
|
hash_key, range_key = self.get_keys_value(table, keys)
|
||||||
return table.get_item(hash_key, range_key, projection_expression)
|
return table.get_item(hash_key, range_key, projection_expression)
|
||||||
|
|
||||||
@ -1412,9 +1435,7 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
filter_expression=None,
|
filter_expression=None,
|
||||||
**filter_kwargs,
|
**filter_kwargs,
|
||||||
):
|
):
|
||||||
table = self.tables.get(table_name)
|
table = self.get_table(table_name)
|
||||||
if not table:
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
hash_key = DynamoType(hash_key_dict)
|
hash_key = DynamoType(hash_key_dict)
|
||||||
range_values = [DynamoType(range_value) for range_value in range_value_dicts]
|
range_values = [DynamoType(range_value) for range_value in range_value_dicts]
|
||||||
@ -1448,9 +1469,7 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
index_name,
|
index_name,
|
||||||
projection_expression,
|
projection_expression,
|
||||||
):
|
):
|
||||||
table = self.tables.get(table_name)
|
table = self.get_table(table_name)
|
||||||
if not table:
|
|
||||||
return None, None, None
|
|
||||||
|
|
||||||
scan_filters = {}
|
scan_filters = {}
|
||||||
for key, (comparison_operator, comparison_values) in filters.items():
|
for key, (comparison_operator, comparison_values) in filters.items():
|
||||||
@ -1582,8 +1601,6 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
condition_expression=None,
|
condition_expression=None,
|
||||||
):
|
):
|
||||||
table = self.get_table(table_name)
|
table = self.get_table(table_name)
|
||||||
if not table:
|
|
||||||
return None
|
|
||||||
|
|
||||||
hash_value, range_value = self.get_keys_value(table, key)
|
hash_value, range_value = self.get_keys_value(table, key)
|
||||||
item = table.get_item(hash_value, range_value)
|
item = table.get_item(hash_value, range_value)
|
||||||
@ -1719,25 +1736,31 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
errors.append(None)
|
errors.append((None, None))
|
||||||
except MultipleTransactionsException:
|
except MultipleTransactionsException:
|
||||||
# Rollback to the original state, and reraise the error
|
# Rollback to the original state, and reraise the error
|
||||||
self.tables = original_table_state
|
self.tables = original_table_state
|
||||||
raise MultipleTransactionsException()
|
raise MultipleTransactionsException()
|
||||||
except Exception as e: # noqa: E722 Do not use bare except
|
except Exception as e: # noqa: E722 Do not use bare except
|
||||||
errors.append(type(e).__name__)
|
errors.append((type(e).__name__, e.message))
|
||||||
if any(errors):
|
if set(errors) != set([(None, None)]):
|
||||||
# Rollback to the original state, and reraise the errors
|
# Rollback to the original state, and reraise the errors
|
||||||
self.tables = original_table_state
|
self.tables = original_table_state
|
||||||
raise TransactionCanceledException(errors)
|
raise TransactionCanceledException(errors)
|
||||||
|
|
||||||
def describe_continuous_backups(self, table_name):
|
def describe_continuous_backups(self, table_name):
|
||||||
|
try:
|
||||||
table = self.get_table(table_name)
|
table = self.get_table(table_name)
|
||||||
|
except ResourceNotFoundException:
|
||||||
|
raise TableNotFoundException(table_name)
|
||||||
|
|
||||||
return table.continuous_backups
|
return table.continuous_backups
|
||||||
|
|
||||||
def update_continuous_backups(self, table_name, point_in_time_spec):
|
def update_continuous_backups(self, table_name, point_in_time_spec):
|
||||||
|
try:
|
||||||
table = self.get_table(table_name)
|
table = self.get_table(table_name)
|
||||||
|
except ResourceNotFoundException:
|
||||||
|
raise TableNotFoundException(table_name)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
point_in_time_spec["PointInTimeRecoveryEnabled"]
|
point_in_time_spec["PointInTimeRecoveryEnabled"]
|
||||||
@ -1759,6 +1782,8 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
return table.continuous_backups
|
return table.continuous_backups
|
||||||
|
|
||||||
def get_backup(self, backup_arn):
|
def get_backup(self, backup_arn):
|
||||||
|
if backup_arn not in self.backups:
|
||||||
|
raise BackupNotFoundException(backup_arn)
|
||||||
return self.backups.get(backup_arn)
|
return self.backups.get(backup_arn)
|
||||||
|
|
||||||
def list_backups(self, table_name):
|
def list_backups(self, table_name):
|
||||||
@ -1768,9 +1793,10 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
return backups
|
return backups
|
||||||
|
|
||||||
def create_backup(self, table_name, backup_name):
|
def create_backup(self, table_name, backup_name):
|
||||||
|
try:
|
||||||
table = self.get_table(table_name)
|
table = self.get_table(table_name)
|
||||||
if table is None:
|
except ResourceNotFoundException:
|
||||||
raise KeyError()
|
raise TableNotFoundException(table_name)
|
||||||
backup = Backup(self, backup_name, table)
|
backup = Backup(self, backup_name, table)
|
||||||
self.backups[backup.arn] = backup
|
self.backups[backup.arn] = backup
|
||||||
return backup
|
return backup
|
||||||
@ -1791,11 +1817,8 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
|
|
||||||
def restore_table_from_backup(self, target_table_name, backup_arn):
|
def restore_table_from_backup(self, target_table_name, backup_arn):
|
||||||
backup = self.get_backup(backup_arn)
|
backup = self.get_backup(backup_arn)
|
||||||
if backup is None:
|
if target_table_name in self.tables:
|
||||||
raise KeyError()
|
raise TableAlreadyExistsException(target_table_name)
|
||||||
existing_table = self.get_table(target_table_name)
|
|
||||||
if existing_table is not None:
|
|
||||||
raise ValueError()
|
|
||||||
new_table = RestoredTable(
|
new_table = RestoredTable(
|
||||||
target_table_name, region=self.region_name, backup=backup
|
target_table_name, region=self.region_name, backup=backup
|
||||||
)
|
)
|
||||||
@ -1808,12 +1831,12 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
copy all items from the source without respect to other arguments.
|
copy all items from the source without respect to other arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
source = self.get_table(source_table_name)
|
source = self.get_table(source_table_name)
|
||||||
if source is None:
|
except ResourceNotFoundException:
|
||||||
raise KeyError()
|
raise SourceTableNotFoundException(source_table_name)
|
||||||
existing_table = self.get_table(target_table_name)
|
if target_table_name in self.tables:
|
||||||
if existing_table is not None:
|
raise TableAlreadyExistsException(target_table_name)
|
||||||
raise ValueError()
|
|
||||||
new_table = RestoredPITTable(
|
new_table = RestoredPITTable(
|
||||||
target_table_name, region=self.region_name, source=source
|
target_table_name, region=self.region_name, source=source
|
||||||
)
|
)
|
||||||
|
@ -8,9 +8,9 @@ from functools import wraps
|
|||||||
from moto.core.responses import BaseResponse
|
from moto.core.responses import BaseResponse
|
||||||
from moto.core.utils import camelcase_to_underscores, amz_crc32, amzn_request_id
|
from moto.core.utils import camelcase_to_underscores, amz_crc32, amzn_request_id
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
InvalidIndexNameError,
|
|
||||||
MockValidationException,
|
MockValidationException,
|
||||||
TransactionCanceledException,
|
ResourceNotFoundException,
|
||||||
|
ConditionalCheckFailed,
|
||||||
)
|
)
|
||||||
from moto.dynamodb.models import dynamodb_backends, dynamo_json_dump
|
from moto.dynamodb.models import dynamodb_backends, dynamo_json_dump
|
||||||
|
|
||||||
@ -111,13 +111,6 @@ class DynamoHandler(BaseResponse):
|
|||||||
if match:
|
if match:
|
||||||
return match.split(".")[1]
|
return match.split(".")[1]
|
||||||
|
|
||||||
def error(self, type_, message, status=400):
|
|
||||||
return (
|
|
||||||
status,
|
|
||||||
self.response_headers,
|
|
||||||
dynamo_json_dump({"__type": type_, "message": message}),
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dynamodb_backend(self):
|
def dynamodb_backend(self):
|
||||||
"""
|
"""
|
||||||
@ -165,19 +158,16 @@ class DynamoHandler(BaseResponse):
|
|||||||
# check billing mode and get the throughput
|
# check billing mode and get the throughput
|
||||||
if "BillingMode" in body.keys() and body["BillingMode"] == "PAY_PER_REQUEST":
|
if "BillingMode" in body.keys() and body["BillingMode"] == "PAY_PER_REQUEST":
|
||||||
if "ProvisionedThroughput" in body.keys():
|
if "ProvisionedThroughput" in body.keys():
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ValidationException"
|
raise MockValidationException(
|
||||||
return self.error(
|
"ProvisionedThroughput cannot be specified when BillingMode is PAY_PER_REQUEST"
|
||||||
er,
|
|
||||||
"ProvisionedThroughput cannot be specified when BillingMode is PAY_PER_REQUEST",
|
|
||||||
)
|
)
|
||||||
throughput = None
|
throughput = None
|
||||||
billing_mode = "PAY_PER_REQUEST"
|
billing_mode = "PAY_PER_REQUEST"
|
||||||
else: # Provisioned (default billing mode)
|
else: # Provisioned (default billing mode)
|
||||||
throughput = body.get("ProvisionedThroughput")
|
throughput = body.get("ProvisionedThroughput")
|
||||||
if throughput is None:
|
if throughput is None:
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
"ValidationException",
|
"One or more parameter values were invalid: ReadCapacityUnits and WriteCapacityUnits must both be specified when BillingMode is PROVISIONED"
|
||||||
"One or more parameter values were invalid: ReadCapacityUnits and WriteCapacityUnits must both be specified when BillingMode is PROVISIONED",
|
|
||||||
)
|
)
|
||||||
billing_mode = "PROVISIONED"
|
billing_mode = "PROVISIONED"
|
||||||
# getting ServerSideEncryption details
|
# getting ServerSideEncryption details
|
||||||
@ -189,16 +179,14 @@ class DynamoHandler(BaseResponse):
|
|||||||
# getting the indexes
|
# getting the indexes
|
||||||
global_indexes = body.get("GlobalSecondaryIndexes")
|
global_indexes = body.get("GlobalSecondaryIndexes")
|
||||||
if global_indexes == []:
|
if global_indexes == []:
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
"ValidationException",
|
"One or more parameter values were invalid: List of GlobalSecondaryIndexes is empty"
|
||||||
"One or more parameter values were invalid: List of GlobalSecondaryIndexes is empty",
|
|
||||||
)
|
)
|
||||||
global_indexes = global_indexes or []
|
global_indexes = global_indexes or []
|
||||||
local_secondary_indexes = body.get("LocalSecondaryIndexes")
|
local_secondary_indexes = body.get("LocalSecondaryIndexes")
|
||||||
if local_secondary_indexes == []:
|
if local_secondary_indexes == []:
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
"ValidationException",
|
"One or more parameter values were invalid: List of LocalSecondaryIndexes is empty"
|
||||||
"One or more parameter values were invalid: List of LocalSecondaryIndexes is empty",
|
|
||||||
)
|
)
|
||||||
local_secondary_indexes = local_secondary_indexes or []
|
local_secondary_indexes = local_secondary_indexes or []
|
||||||
# Verify AttributeDefinitions list all
|
# Verify AttributeDefinitions list all
|
||||||
@ -241,76 +229,62 @@ class DynamoHandler(BaseResponse):
|
|||||||
sse_specification=sse_spec,
|
sse_specification=sse_spec,
|
||||||
tags=tags,
|
tags=tags,
|
||||||
)
|
)
|
||||||
if table is not None:
|
|
||||||
return dynamo_json_dump(table.describe())
|
return dynamo_json_dump(table.describe())
|
||||||
else:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ResourceInUseException"
|
|
||||||
return self.error(er, "Resource in use")
|
|
||||||
|
|
||||||
def _throw_attr_error(self, actual_attrs, expected_attrs, indexes):
|
def _throw_attr_error(self, actual_attrs, expected_attrs, indexes):
|
||||||
def dump_list(list_):
|
def dump_list(list_):
|
||||||
return str(list_).replace("'", "")
|
return str(list_).replace("'", "")
|
||||||
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ValidationException"
|
|
||||||
err_head = "One or more parameter values were invalid: "
|
err_head = "One or more parameter values were invalid: "
|
||||||
if len(actual_attrs) > len(expected_attrs):
|
if len(actual_attrs) > len(expected_attrs):
|
||||||
if indexes:
|
if indexes:
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
er,
|
|
||||||
err_head
|
err_head
|
||||||
+ "Some AttributeDefinitions are not used. AttributeDefinitions: "
|
+ "Some AttributeDefinitions are not used. AttributeDefinitions: "
|
||||||
+ dump_list(actual_attrs)
|
+ dump_list(actual_attrs)
|
||||||
+ ", keys used: "
|
+ ", keys used: "
|
||||||
+ dump_list(expected_attrs),
|
+ dump_list(expected_attrs)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
er,
|
|
||||||
err_head
|
err_head
|
||||||
+ "Number of attributes in KeySchema does not exactly match number of attributes defined in AttributeDefinitions",
|
+ "Number of attributes in KeySchema does not exactly match number of attributes defined in AttributeDefinitions"
|
||||||
)
|
)
|
||||||
elif len(actual_attrs) < len(expected_attrs):
|
elif len(actual_attrs) < len(expected_attrs):
|
||||||
if indexes:
|
if indexes:
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
er,
|
|
||||||
err_head
|
err_head
|
||||||
+ "Some index key attributes are not defined in AttributeDefinitions. Keys: "
|
+ "Some index key attributes are not defined in AttributeDefinitions. Keys: "
|
||||||
+ dump_list(list(set(expected_attrs) - set(actual_attrs)))
|
+ dump_list(list(set(expected_attrs) - set(actual_attrs)))
|
||||||
+ ", AttributeDefinitions: "
|
+ ", AttributeDefinitions: "
|
||||||
+ dump_list(actual_attrs),
|
+ dump_list(actual_attrs)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
er, "Invalid KeySchema: Some index key attribute have no definition"
|
"Invalid KeySchema: Some index key attribute have no definition"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if indexes:
|
if indexes:
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
er,
|
|
||||||
err_head
|
err_head
|
||||||
+ "Some index key attributes are not defined in AttributeDefinitions. Keys: "
|
+ "Some index key attributes are not defined in AttributeDefinitions. Keys: "
|
||||||
+ dump_list(list(set(expected_attrs) - set(actual_attrs)))
|
+ dump_list(list(set(expected_attrs) - set(actual_attrs)))
|
||||||
+ ", AttributeDefinitions: "
|
+ ", AttributeDefinitions: "
|
||||||
+ dump_list(actual_attrs),
|
+ dump_list(actual_attrs)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
er,
|
|
||||||
err_head
|
err_head
|
||||||
+ "Some index key attributes are not defined in AttributeDefinitions. Keys: "
|
+ "Some index key attributes are not defined in AttributeDefinitions. Keys: "
|
||||||
+ dump_list(expected_attrs)
|
+ dump_list(expected_attrs)
|
||||||
+ ", AttributeDefinitions: "
|
+ ", AttributeDefinitions: "
|
||||||
+ dump_list(actual_attrs),
|
+ dump_list(actual_attrs)
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete_table(self):
|
def delete_table(self):
|
||||||
name = self.body["TableName"]
|
name = self.body["TableName"]
|
||||||
table = self.dynamodb_backend.delete_table(name)
|
table = self.dynamodb_backend.delete_table(name)
|
||||||
if table is not None:
|
|
||||||
return dynamo_json_dump(table.describe())
|
return dynamo_json_dump(table.describe())
|
||||||
else:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
|
|
||||||
return self.error(er, "Requested resource not found")
|
|
||||||
|
|
||||||
def describe_endpoints(self):
|
def describe_endpoints(self):
|
||||||
response = {"Endpoints": self.dynamodb_backend.describe_endpoints()}
|
response = {"Endpoints": self.dynamodb_backend.describe_endpoints()}
|
||||||
@ -329,7 +303,6 @@ class DynamoHandler(BaseResponse):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
def list_tags_of_resource(self):
|
def list_tags_of_resource(self):
|
||||||
try:
|
|
||||||
table_arn = self.body["ResourceArn"]
|
table_arn = self.body["ResourceArn"]
|
||||||
all_tags = self.dynamodb_backend.list_tags_of_resource(table_arn)
|
all_tags = self.dynamodb_backend.list_tags_of_resource(table_arn)
|
||||||
all_tag_keys = [tag["Key"] for tag in all_tags]
|
all_tag_keys = [tag["Key"] for tag in all_tags]
|
||||||
@ -346,9 +319,6 @@ class DynamoHandler(BaseResponse):
|
|||||||
if next_marker:
|
if next_marker:
|
||||||
return json.dumps({"Tags": tags_resp, "NextToken": next_marker})
|
return json.dumps({"Tags": tags_resp, "NextToken": next_marker})
|
||||||
return json.dumps({"Tags": tags_resp})
|
return json.dumps({"Tags": tags_resp})
|
||||||
except AttributeError:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
|
|
||||||
return self.error(er, "Requested resource not found")
|
|
||||||
|
|
||||||
def update_table(self):
|
def update_table(self):
|
||||||
name = self.body["TableName"]
|
name = self.body["TableName"]
|
||||||
@ -357,7 +327,6 @@ class DynamoHandler(BaseResponse):
|
|||||||
throughput = self.body.get("ProvisionedThroughput", None)
|
throughput = self.body.get("ProvisionedThroughput", None)
|
||||||
billing_mode = self.body.get("BillingMode", None)
|
billing_mode = self.body.get("BillingMode", None)
|
||||||
stream_spec = self.body.get("StreamSpecification", None)
|
stream_spec = self.body.get("StreamSpecification", None)
|
||||||
try:
|
|
||||||
table = self.dynamodb_backend.update_table(
|
table = self.dynamodb_backend.update_table(
|
||||||
name=name,
|
name=name,
|
||||||
attr_definitions=attr_definitions,
|
attr_definitions=attr_definitions,
|
||||||
@ -367,18 +336,11 @@ class DynamoHandler(BaseResponse):
|
|||||||
stream_spec=stream_spec,
|
stream_spec=stream_spec,
|
||||||
)
|
)
|
||||||
return dynamo_json_dump(table.describe())
|
return dynamo_json_dump(table.describe())
|
||||||
except ValueError:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ResourceInUseException"
|
|
||||||
return self.error(er, "Cannot enable stream")
|
|
||||||
|
|
||||||
def describe_table(self):
|
def describe_table(self):
|
||||||
name = self.body["TableName"]
|
name = self.body["TableName"]
|
||||||
try:
|
|
||||||
table = self.dynamodb_backend.describe_table(name)
|
table = self.dynamodb_backend.describe_table(name)
|
||||||
return dynamo_json_dump(table)
|
return dynamo_json_dump(table)
|
||||||
except KeyError:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
|
|
||||||
return self.error(er, "Requested resource not found")
|
|
||||||
|
|
||||||
@include_consumed_capacity()
|
@include_consumed_capacity()
|
||||||
def put_item(self):
|
def put_item(self):
|
||||||
@ -387,8 +349,7 @@ class DynamoHandler(BaseResponse):
|
|||||||
return_values = self.body.get("ReturnValues", "NONE")
|
return_values = self.body.get("ReturnValues", "NONE")
|
||||||
|
|
||||||
if return_values not in ("ALL_OLD", "NONE"):
|
if return_values not in ("ALL_OLD", "NONE"):
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ValidationException"
|
raise MockValidationException("Return values set to invalid value")
|
||||||
return self.error(er, "Return values set to invalid value")
|
|
||||||
|
|
||||||
if put_has_empty_keys(item, self.dynamodb_backend.get_table(name)):
|
if put_has_empty_keys(item, self.dynamodb_backend.get_table(name)):
|
||||||
return get_empty_str_error()
|
return get_empty_str_error()
|
||||||
@ -415,7 +376,6 @@ class DynamoHandler(BaseResponse):
|
|||||||
if condition_expression:
|
if condition_expression:
|
||||||
overwrite = False
|
overwrite = False
|
||||||
|
|
||||||
try:
|
|
||||||
result = self.dynamodb_backend.put_item(
|
result = self.dynamodb_backend.put_item(
|
||||||
name,
|
name,
|
||||||
item,
|
item,
|
||||||
@ -425,26 +385,13 @@ class DynamoHandler(BaseResponse):
|
|||||||
expression_attribute_values,
|
expression_attribute_values,
|
||||||
overwrite,
|
overwrite,
|
||||||
)
|
)
|
||||||
except MockValidationException as mve:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ValidationException"
|
|
||||||
return self.error(er, mve.exception_msg)
|
|
||||||
except KeyError as ke:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ValidationException"
|
|
||||||
return self.error(er, ke.args[0])
|
|
||||||
except ValueError as ve:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ConditionalCheckFailedException"
|
|
||||||
return self.error(er, str(ve))
|
|
||||||
|
|
||||||
if result:
|
|
||||||
item_dict = result.to_json()
|
item_dict = result.to_json()
|
||||||
if return_values == "ALL_OLD":
|
if return_values == "ALL_OLD":
|
||||||
item_dict["Attributes"] = existing_attributes
|
item_dict["Attributes"] = existing_attributes
|
||||||
else:
|
else:
|
||||||
item_dict.pop("Attributes", None)
|
item_dict.pop("Attributes", None)
|
||||||
return dynamo_json_dump(item_dict)
|
return dynamo_json_dump(item_dict)
|
||||||
else:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
|
|
||||||
return self.error(er, "Requested resource not found")
|
|
||||||
|
|
||||||
def batch_write_item(self):
|
def batch_write_item(self):
|
||||||
table_batches = self.body["RequestItems"]
|
table_batches = self.body["RequestItems"]
|
||||||
@ -455,15 +402,10 @@ class DynamoHandler(BaseResponse):
|
|||||||
request = list(table_request.values())[0]
|
request = list(table_request.values())[0]
|
||||||
if request_type == "PutRequest":
|
if request_type == "PutRequest":
|
||||||
item = request["Item"]
|
item = request["Item"]
|
||||||
res = self.dynamodb_backend.put_item(table_name, item)
|
self.dynamodb_backend.put_item(table_name, item)
|
||||||
if not res:
|
|
||||||
return self.error(
|
|
||||||
"com.amazonaws.dynamodb.v20111205#ResourceNotFoundException",
|
|
||||||
"Requested resource not found",
|
|
||||||
)
|
|
||||||
elif request_type == "DeleteRequest":
|
elif request_type == "DeleteRequest":
|
||||||
keys = request["Key"]
|
keys = request["Key"]
|
||||||
item = self.dynamodb_backend.delete_item(table_name, keys)
|
self.dynamodb_backend.delete_item(table_name, keys)
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
"ConsumedCapacity": [
|
"ConsumedCapacity": [
|
||||||
@ -483,36 +425,26 @@ class DynamoHandler(BaseResponse):
|
|||||||
@include_consumed_capacity(0.5)
|
@include_consumed_capacity(0.5)
|
||||||
def get_item(self):
|
def get_item(self):
|
||||||
name = self.body["TableName"]
|
name = self.body["TableName"]
|
||||||
table = self.dynamodb_backend.get_table(name)
|
self.dynamodb_backend.get_table(name)
|
||||||
if table is None:
|
|
||||||
return self.error(
|
|
||||||
"com.amazonaws.dynamodb.v20120810#ResourceNotFoundException",
|
|
||||||
"Requested resource not found",
|
|
||||||
)
|
|
||||||
key = self.body["Key"]
|
key = self.body["Key"]
|
||||||
projection_expression = self.body.get("ProjectionExpression")
|
projection_expression = self.body.get("ProjectionExpression")
|
||||||
expression_attribute_names = self.body.get("ExpressionAttributeNames")
|
expression_attribute_names = self.body.get("ExpressionAttributeNames")
|
||||||
if expression_attribute_names == {}:
|
if expression_attribute_names == {}:
|
||||||
if projection_expression is None:
|
if projection_expression is None:
|
||||||
er = "ValidationException"
|
raise MockValidationException(
|
||||||
return self.error(
|
"ExpressionAttributeNames can only be specified when using expressions"
|
||||||
er,
|
|
||||||
"ExpressionAttributeNames can only be specified when using expressions",
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
er = "ValidationException"
|
raise MockValidationException(
|
||||||
return self.error(er, "ExpressionAttributeNames must not be empty")
|
"ExpressionAttributeNames must not be empty"
|
||||||
|
)
|
||||||
|
|
||||||
expression_attribute_names = expression_attribute_names or {}
|
expression_attribute_names = expression_attribute_names or {}
|
||||||
projection_expression = self._adjust_projection_expression(
|
projection_expression = self._adjust_projection_expression(
|
||||||
projection_expression, expression_attribute_names
|
projection_expression, expression_attribute_names
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
|
||||||
item = self.dynamodb_backend.get_item(name, key, projection_expression)
|
item = self.dynamodb_backend.get_item(name, key, projection_expression)
|
||||||
except ValueError:
|
|
||||||
er = "com.amazon.coral.validate#ValidationException"
|
|
||||||
return self.error(er, "Validation Exception")
|
|
||||||
if item:
|
if item:
|
||||||
item_dict = item.describe_attrs(attributes=None)
|
item_dict = item.describe_attrs(attributes=None)
|
||||||
return dynamo_json_dump(item_dict)
|
return dynamo_json_dump(item_dict)
|
||||||
@ -529,27 +461,26 @@ class DynamoHandler(BaseResponse):
|
|||||||
# Scenario 1: We're requesting more than a 100 keys from a single table
|
# Scenario 1: We're requesting more than a 100 keys from a single table
|
||||||
for table_name, table_request in table_batches.items():
|
for table_name, table_request in table_batches.items():
|
||||||
if len(table_request["Keys"]) > 100:
|
if len(table_request["Keys"]) > 100:
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
"com.amazonaws.dynamodb.v20111205#ValidationException",
|
|
||||||
"1 validation error detected: Value at 'requestItems."
|
"1 validation error detected: Value at 'requestItems."
|
||||||
+ table_name
|
+ table_name
|
||||||
+ ".member.keys' failed to satisfy constraint: Member must have length less than or equal to 100",
|
+ ".member.keys' failed to satisfy constraint: Member must have length less than or equal to 100"
|
||||||
)
|
)
|
||||||
# Scenario 2: We're requesting more than a 100 keys across all tables
|
# Scenario 2: We're requesting more than a 100 keys across all tables
|
||||||
nr_of_keys_across_all_tables = sum(
|
nr_of_keys_across_all_tables = sum(
|
||||||
[len(req["Keys"]) for _, req in table_batches.items()]
|
[len(req["Keys"]) for _, req in table_batches.items()]
|
||||||
)
|
)
|
||||||
if nr_of_keys_across_all_tables > 100:
|
if nr_of_keys_across_all_tables > 100:
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
"com.amazonaws.dynamodb.v20111205#ValidationException",
|
"Too many items requested for the BatchGetItem call"
|
||||||
"Too many items requested for the BatchGetItem call",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for table_name, table_request in table_batches.items():
|
for table_name, table_request in table_batches.items():
|
||||||
keys = table_request["Keys"]
|
keys = table_request["Keys"]
|
||||||
if self._contains_duplicates(keys):
|
if self._contains_duplicates(keys):
|
||||||
er = "com.amazon.coral.validate#ValidationException"
|
raise MockValidationException(
|
||||||
return self.error(er, "Provided list of item keys contains duplicates")
|
"Provided list of item keys contains duplicates"
|
||||||
|
)
|
||||||
attributes_to_get = table_request.get("AttributesToGet")
|
attributes_to_get = table_request.get("AttributesToGet")
|
||||||
projection_expression = table_request.get("ProjectionExpression")
|
projection_expression = table_request.get("ProjectionExpression")
|
||||||
expression_attribute_names = table_request.get(
|
expression_attribute_names = table_request.get(
|
||||||
@ -562,15 +493,9 @@ class DynamoHandler(BaseResponse):
|
|||||||
|
|
||||||
results["Responses"][table_name] = []
|
results["Responses"][table_name] = []
|
||||||
for key in keys:
|
for key in keys:
|
||||||
try:
|
|
||||||
item = self.dynamodb_backend.get_item(
|
item = self.dynamodb_backend.get_item(
|
||||||
table_name, key, projection_expression
|
table_name, key, projection_expression
|
||||||
)
|
)
|
||||||
except ValueError:
|
|
||||||
return self.error(
|
|
||||||
"com.amazonaws.dynamodb.v20111205#ResourceNotFoundException",
|
|
||||||
"Requested resource not found",
|
|
||||||
)
|
|
||||||
if item:
|
if item:
|
||||||
item_describe = item.describe_attrs(attributes_to_get)
|
item_describe = item.describe_attrs(attributes_to_get)
|
||||||
results["Responses"][table_name].append(item_describe["Item"])
|
results["Responses"][table_name].append(item_describe["Item"])
|
||||||
@ -607,32 +532,11 @@ class DynamoHandler(BaseResponse):
|
|||||||
if key_condition_expression:
|
if key_condition_expression:
|
||||||
value_alias_map = self.body.get("ExpressionAttributeValues", {})
|
value_alias_map = self.body.get("ExpressionAttributeValues", {})
|
||||||
|
|
||||||
table = self.dynamodb_backend.get_table(name)
|
|
||||||
|
|
||||||
# If table does not exist
|
|
||||||
if table is None:
|
|
||||||
return self.error(
|
|
||||||
"com.amazonaws.dynamodb.v20120810#ResourceNotFoundException",
|
|
||||||
"Requested resource not found",
|
|
||||||
)
|
|
||||||
|
|
||||||
index_name = self.body.get("IndexName")
|
index_name = self.body.get("IndexName")
|
||||||
if index_name:
|
schema = self.dynamodb_backend.get_schema(
|
||||||
all_indexes = (table.global_indexes or []) + (table.indexes or [])
|
table_name=name, index_name=index_name
|
||||||
indexes_by_name = dict((i.name, i) for i in all_indexes)
|
|
||||||
if index_name not in indexes_by_name:
|
|
||||||
er = "com.amazonaws.dynamodb.v20120810#ResourceNotFoundException"
|
|
||||||
return self.error(
|
|
||||||
er,
|
|
||||||
"Invalid index: {} for table: {}. Available indexes are: {}".format(
|
|
||||||
index_name, name, ", ".join(indexes_by_name.keys())
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
index = indexes_by_name[index_name].schema
|
|
||||||
else:
|
|
||||||
index = table.schema
|
|
||||||
|
|
||||||
reverse_attribute_lookup = dict(
|
reverse_attribute_lookup = dict(
|
||||||
(v, k) for k, v in self.body.get("ExpressionAttributeNames", {}).items()
|
(v, k) for k, v in self.body.get("ExpressionAttributeNames", {}).items()
|
||||||
)
|
)
|
||||||
@ -642,7 +546,7 @@ class DynamoHandler(BaseResponse):
|
|||||||
" AND ", key_condition_expression, maxsplit=1, flags=re.IGNORECASE
|
" AND ", key_condition_expression, maxsplit=1, flags=re.IGNORECASE
|
||||||
)
|
)
|
||||||
|
|
||||||
index_hash_key = [key for key in index if key["KeyType"] == "HASH"][0]
|
index_hash_key = [key for key in schema if key["KeyType"] == "HASH"][0]
|
||||||
hash_key_var = reverse_attribute_lookup.get(
|
hash_key_var = reverse_attribute_lookup.get(
|
||||||
index_hash_key["AttributeName"], index_hash_key["AttributeName"]
|
index_hash_key["AttributeName"], index_hash_key["AttributeName"]
|
||||||
)
|
)
|
||||||
@ -656,11 +560,10 @@ class DynamoHandler(BaseResponse):
|
|||||||
(None, None),
|
(None, None),
|
||||||
)
|
)
|
||||||
if hash_key_expression is None:
|
if hash_key_expression is None:
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
"ValidationException",
|
|
||||||
"Query condition missed key schema element: {}".format(
|
"Query condition missed key schema element: {}".format(
|
||||||
hash_key_var
|
hash_key_var
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
hash_key_expression = hash_key_expression.strip("()")
|
hash_key_expression = hash_key_expression.strip("()")
|
||||||
expressions.pop(i)
|
expressions.pop(i)
|
||||||
@ -698,11 +601,10 @@ class DynamoHandler(BaseResponse):
|
|||||||
"begins_with"
|
"begins_with"
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
"com.amazonaws.dynamodb.v20111205#ValidationException",
|
|
||||||
"Invalid KeyConditionExpression: Invalid function name; function: {}".format(
|
"Invalid KeyConditionExpression: Invalid function name; function: {}".format(
|
||||||
function_used
|
function_used
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# [range_key, =, x]
|
# [range_key, =, x]
|
||||||
@ -713,14 +615,13 @@ class DynamoHandler(BaseResponse):
|
|||||||
supplied_range_key, supplied_range_key
|
supplied_range_key, supplied_range_key
|
||||||
)
|
)
|
||||||
range_keys = [
|
range_keys = [
|
||||||
k["AttributeName"] for k in index if k["KeyType"] == "RANGE"
|
k["AttributeName"] for k in schema if k["KeyType"] == "RANGE"
|
||||||
]
|
]
|
||||||
if supplied_range_key not in range_keys:
|
if supplied_range_key not in range_keys:
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
"ValidationException",
|
|
||||||
"Query condition missed key schema element: {}".format(
|
"Query condition missed key schema element: {}".format(
|
||||||
range_keys[0]
|
range_keys[0]
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
hash_key_expression = key_condition_expression.strip("()")
|
hash_key_expression = key_condition_expression.strip("()")
|
||||||
@ -728,10 +629,7 @@ class DynamoHandler(BaseResponse):
|
|||||||
range_values = []
|
range_values = []
|
||||||
|
|
||||||
if not re.search("[^<>]=", hash_key_expression):
|
if not re.search("[^<>]=", hash_key_expression):
|
||||||
return self.error(
|
raise MockValidationException("Query key condition not supported")
|
||||||
"com.amazonaws.dynamodb.v20111205#ValidationException",
|
|
||||||
"Query key condition not supported",
|
|
||||||
)
|
|
||||||
hash_key_value_alias = hash_key_expression.split("=")[1].strip()
|
hash_key_value_alias = hash_key_expression.split("=")[1].strip()
|
||||||
# Temporary fix until we get proper KeyConditionExpression function
|
# Temporary fix until we get proper KeyConditionExpression function
|
||||||
hash_key = value_alias_map.get(
|
hash_key = value_alias_map.get(
|
||||||
@ -743,9 +641,8 @@ class DynamoHandler(BaseResponse):
|
|||||||
query_filters = self.body.get("QueryFilter")
|
query_filters = self.body.get("QueryFilter")
|
||||||
|
|
||||||
if not (key_conditions or query_filters):
|
if not (key_conditions or query_filters):
|
||||||
return self.error(
|
raise MockValidationException(
|
||||||
"com.amazonaws.dynamodb.v20111205#ValidationException",
|
"Either KeyConditions or QueryFilter should be present"
|
||||||
"Either KeyConditions or QueryFilter should be present",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if key_conditions:
|
if key_conditions:
|
||||||
@ -759,16 +656,14 @@ class DynamoHandler(BaseResponse):
|
|||||||
if key not in (hash_key_name, range_key_name):
|
if key not in (hash_key_name, range_key_name):
|
||||||
filter_kwargs[key] = value
|
filter_kwargs[key] = value
|
||||||
if hash_key_name is None:
|
if hash_key_name is None:
|
||||||
er = "'com.amazonaws.dynamodb.v20120810#ResourceNotFoundException"
|
raise ResourceNotFoundException
|
||||||
return self.error(er, "Requested resource not found")
|
|
||||||
hash_key = key_conditions[hash_key_name]["AttributeValueList"][0]
|
hash_key = key_conditions[hash_key_name]["AttributeValueList"][0]
|
||||||
if len(key_conditions) == 1:
|
if len(key_conditions) == 1:
|
||||||
range_comparison = None
|
range_comparison = None
|
||||||
range_values = []
|
range_values = []
|
||||||
else:
|
else:
|
||||||
if range_key_name is None and not filter_kwargs:
|
if range_key_name is None and not filter_kwargs:
|
||||||
er = "com.amazon.coral.validate#ValidationException"
|
raise MockValidationException("Validation Exception")
|
||||||
return self.error(er, "Validation Exception")
|
|
||||||
else:
|
else:
|
||||||
range_condition = key_conditions.get(range_key_name)
|
range_condition = key_conditions.get(range_key_name)
|
||||||
if range_condition:
|
if range_condition:
|
||||||
@ -798,9 +693,6 @@ class DynamoHandler(BaseResponse):
|
|||||||
filter_expression=filter_expression,
|
filter_expression=filter_expression,
|
||||||
**filter_kwargs
|
**filter_kwargs
|
||||||
)
|
)
|
||||||
if items is None:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
|
|
||||||
return self.error(er, "Requested resource not found")
|
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"Count": len(items),
|
"Count": len(items),
|
||||||
@ -867,21 +759,8 @@ class DynamoHandler(BaseResponse):
|
|||||||
index_name,
|
index_name,
|
||||||
projection_expression,
|
projection_expression,
|
||||||
)
|
)
|
||||||
except InvalidIndexNameError as err:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ValidationException"
|
|
||||||
return self.error(er, str(err))
|
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ValidationError"
|
raise MockValidationException("Bad Filter Expression: {0}".format(err))
|
||||||
return self.error(er, "Bad Filter Expression: {0}".format(err))
|
|
||||||
except Exception as err:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#InternalFailure"
|
|
||||||
return self.error(er, "Internal error. {0}".format(err))
|
|
||||||
|
|
||||||
# Items should be a list, at least an empty one. Is None if table does not exist.
|
|
||||||
# Should really check this at the beginning
|
|
||||||
if items is None:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
|
|
||||||
return self.error(er, "Requested resource not found")
|
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"Count": len(items),
|
"Count": len(items),
|
||||||
@ -897,14 +776,13 @@ class DynamoHandler(BaseResponse):
|
|||||||
key = self.body["Key"]
|
key = self.body["Key"]
|
||||||
return_values = self.body.get("ReturnValues", "NONE")
|
return_values = self.body.get("ReturnValues", "NONE")
|
||||||
if return_values not in ("ALL_OLD", "NONE"):
|
if return_values not in ("ALL_OLD", "NONE"):
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ValidationException"
|
raise MockValidationException("Return values set to invalid value")
|
||||||
return self.error(er, "Return values set to invalid value")
|
|
||||||
|
|
||||||
table = self.dynamodb_backend.get_table(name)
|
try:
|
||||||
if not table:
|
self.dynamodb_backend.get_table(name)
|
||||||
er = "com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException"
|
except ResourceNotFoundException:
|
||||||
return self.error(
|
raise ConditionalCheckFailed(
|
||||||
er, "A condition specified in the operation could not be evaluated."
|
"A condition specified in the operation could not be evaluated."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Attempt to parse simple ConditionExpressions into an Expected
|
# Attempt to parse simple ConditionExpressions into an Expected
|
||||||
@ -913,7 +791,6 @@ class DynamoHandler(BaseResponse):
|
|||||||
expression_attribute_names = self.body.get("ExpressionAttributeNames", {})
|
expression_attribute_names = self.body.get("ExpressionAttributeNames", {})
|
||||||
expression_attribute_values = self.body.get("ExpressionAttributeValues", {})
|
expression_attribute_values = self.body.get("ExpressionAttributeValues", {})
|
||||||
|
|
||||||
try:
|
|
||||||
item = self.dynamodb_backend.delete_item(
|
item = self.dynamodb_backend.delete_item(
|
||||||
name,
|
name,
|
||||||
key,
|
key,
|
||||||
@ -921,11 +798,6 @@ class DynamoHandler(BaseResponse):
|
|||||||
expression_attribute_values,
|
expression_attribute_values,
|
||||||
condition_expression,
|
condition_expression,
|
||||||
)
|
)
|
||||||
except ValueError:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ConditionalCheckFailedException"
|
|
||||||
return self.error(
|
|
||||||
er, "A condition specified in the operation could not be evaluated."
|
|
||||||
)
|
|
||||||
|
|
||||||
if item and return_values == "ALL_OLD":
|
if item and return_values == "ALL_OLD":
|
||||||
item_dict = item.to_json()
|
item_dict = item.to_json()
|
||||||
@ -941,19 +813,11 @@ class DynamoHandler(BaseResponse):
|
|||||||
update_expression = self.body.get("UpdateExpression", "").strip()
|
update_expression = self.body.get("UpdateExpression", "").strip()
|
||||||
attribute_updates = self.body.get("AttributeUpdates")
|
attribute_updates = self.body.get("AttributeUpdates")
|
||||||
if update_expression and attribute_updates:
|
if update_expression and attribute_updates:
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ValidationException"
|
raise MockValidationException(
|
||||||
return self.error(
|
"Can not use both expression and non-expression parameters in the same request: Non-expression parameters: {AttributeUpdates} Expression parameters: {UpdateExpression}"
|
||||||
er,
|
|
||||||
"Can not use both expression and non-expression parameters in the same request: Non-expression parameters: {AttributeUpdates} Expression parameters: {UpdateExpression}",
|
|
||||||
)
|
)
|
||||||
# We need to copy the item in order to avoid it being modified by the update_item operation
|
# We need to copy the item in order to avoid it being modified by the update_item operation
|
||||||
try:
|
|
||||||
existing_item = copy.deepcopy(self.dynamodb_backend.get_item(name, key))
|
existing_item = copy.deepcopy(self.dynamodb_backend.get_item(name, key))
|
||||||
except ValueError:
|
|
||||||
return self.error(
|
|
||||||
"com.amazonaws.dynamodb.v20111205#ResourceNotFoundException",
|
|
||||||
"Requested resource not found",
|
|
||||||
)
|
|
||||||
if existing_item:
|
if existing_item:
|
||||||
existing_attributes = existing_item.to_json()["Attributes"]
|
existing_attributes = existing_item.to_json()["Attributes"]
|
||||||
else:
|
else:
|
||||||
@ -966,8 +830,7 @@ class DynamoHandler(BaseResponse):
|
|||||||
"UPDATED_OLD",
|
"UPDATED_OLD",
|
||||||
"UPDATED_NEW",
|
"UPDATED_NEW",
|
||||||
):
|
):
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ValidationException"
|
raise MockValidationException("Return values set to invalid value")
|
||||||
return self.error(er, "Return values set to invalid value")
|
|
||||||
|
|
||||||
if "Expected" in self.body:
|
if "Expected" in self.body:
|
||||||
expected = self.body["Expected"]
|
expected = self.body["Expected"]
|
||||||
@ -980,7 +843,6 @@ class DynamoHandler(BaseResponse):
|
|||||||
expression_attribute_names = self.body.get("ExpressionAttributeNames", {})
|
expression_attribute_names = self.body.get("ExpressionAttributeNames", {})
|
||||||
expression_attribute_values = self.body.get("ExpressionAttributeValues", {})
|
expression_attribute_values = self.body.get("ExpressionAttributeValues", {})
|
||||||
|
|
||||||
try:
|
|
||||||
item = self.dynamodb_backend.update_item(
|
item = self.dynamodb_backend.update_item(
|
||||||
name,
|
name,
|
||||||
key,
|
key,
|
||||||
@ -991,15 +853,6 @@ class DynamoHandler(BaseResponse):
|
|||||||
expected=expected,
|
expected=expected,
|
||||||
condition_expression=condition_expression,
|
condition_expression=condition_expression,
|
||||||
)
|
)
|
||||||
except MockValidationException as mve:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ValidationException"
|
|
||||||
return self.error(er, mve.exception_msg)
|
|
||||||
except ValueError:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ConditionalCheckFailedException"
|
|
||||||
return self.error(er, "The conditional request failed")
|
|
||||||
except TypeError:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ValidationException"
|
|
||||||
return self.error(er, "Validation Exception")
|
|
||||||
|
|
||||||
item_dict = item.to_json()
|
item_dict = item.to_json()
|
||||||
item_dict["ConsumedCapacity"] = {"TableName": name, "CapacityUnits": 0.5}
|
item_dict["ConsumedCapacity"] = {"TableName": name, "CapacityUnits": 0.5}
|
||||||
@ -1100,7 +953,7 @@ class DynamoHandler(BaseResponse):
|
|||||||
% TRANSACTION_MAX_ITEMS
|
% TRANSACTION_MAX_ITEMS
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.error("ValidationException", msg)
|
raise MockValidationException(msg)
|
||||||
|
|
||||||
ret_consumed_capacity = self.body.get("ReturnConsumedCapacity", "NONE")
|
ret_consumed_capacity = self.body.get("ReturnConsumedCapacity", "NONE")
|
||||||
consumed_capacity = dict()
|
consumed_capacity = dict()
|
||||||
@ -1109,11 +962,7 @@ class DynamoHandler(BaseResponse):
|
|||||||
|
|
||||||
table_name = transact_item["Get"]["TableName"]
|
table_name = transact_item["Get"]["TableName"]
|
||||||
key = transact_item["Get"]["Key"]
|
key = transact_item["Get"]["Key"]
|
||||||
try:
|
|
||||||
item = self.dynamodb_backend.get_item(table_name, key)
|
item = self.dynamodb_backend.get_item(table_name, key)
|
||||||
except ValueError:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
|
|
||||||
return self.error(er, "Requested resource not found")
|
|
||||||
|
|
||||||
if not item:
|
if not item:
|
||||||
responses.append({})
|
responses.append({})
|
||||||
@ -1145,26 +994,13 @@ class DynamoHandler(BaseResponse):
|
|||||||
|
|
||||||
def transact_write_items(self):
|
def transact_write_items(self):
|
||||||
transact_items = self.body["TransactItems"]
|
transact_items = self.body["TransactItems"]
|
||||||
try:
|
|
||||||
self.dynamodb_backend.transact_write_items(transact_items)
|
self.dynamodb_backend.transact_write_items(transact_items)
|
||||||
except TransactionCanceledException as e:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#TransactionCanceledException"
|
|
||||||
return self.error(er, str(e))
|
|
||||||
except MockValidationException as mve:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#ValidationException"
|
|
||||||
return self.error(er, mve.exception_msg)
|
|
||||||
response = {"ConsumedCapacity": [], "ItemCollectionMetrics": {}}
|
response = {"ConsumedCapacity": [], "ItemCollectionMetrics": {}}
|
||||||
return dynamo_json_dump(response)
|
return dynamo_json_dump(response)
|
||||||
|
|
||||||
def describe_continuous_backups(self):
|
def describe_continuous_backups(self):
|
||||||
name = self.body["TableName"]
|
name = self.body["TableName"]
|
||||||
|
|
||||||
if self.dynamodb_backend.get_table(name) is None:
|
|
||||||
return self.error(
|
|
||||||
"com.amazonaws.dynamodb.v20111205#TableNotFoundException",
|
|
||||||
"Table not found: {}".format(name),
|
|
||||||
)
|
|
||||||
|
|
||||||
response = self.dynamodb_backend.describe_continuous_backups(name)
|
response = self.dynamodb_backend.describe_continuous_backups(name)
|
||||||
|
|
||||||
return json.dumps({"ContinuousBackupsDescription": response})
|
return json.dumps({"ContinuousBackupsDescription": response})
|
||||||
@ -1173,12 +1009,6 @@ class DynamoHandler(BaseResponse):
|
|||||||
name = self.body["TableName"]
|
name = self.body["TableName"]
|
||||||
point_in_time_spec = self.body["PointInTimeRecoverySpecification"]
|
point_in_time_spec = self.body["PointInTimeRecoverySpecification"]
|
||||||
|
|
||||||
if self.dynamodb_backend.get_table(name) is None:
|
|
||||||
return self.error(
|
|
||||||
"com.amazonaws.dynamodb.v20111205#TableNotFoundException",
|
|
||||||
"Table not found: {}".format(name),
|
|
||||||
)
|
|
||||||
|
|
||||||
response = self.dynamodb_backend.update_continuous_backups(
|
response = self.dynamodb_backend.update_continuous_backups(
|
||||||
name, point_in_time_spec
|
name, point_in_time_spec
|
||||||
)
|
)
|
||||||
@ -1196,64 +1026,38 @@ class DynamoHandler(BaseResponse):
|
|||||||
body = self.body
|
body = self.body
|
||||||
table_name = body.get("TableName")
|
table_name = body.get("TableName")
|
||||||
backup_name = body.get("BackupName")
|
backup_name = body.get("BackupName")
|
||||||
try:
|
|
||||||
backup = self.dynamodb_backend.create_backup(table_name, backup_name)
|
backup = self.dynamodb_backend.create_backup(table_name, backup_name)
|
||||||
response = {"BackupDetails": backup.details}
|
response = {"BackupDetails": backup.details}
|
||||||
return dynamo_json_dump(response)
|
return dynamo_json_dump(response)
|
||||||
except KeyError:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#TableNotFoundException"
|
|
||||||
return self.error(er, "Table not found: %s" % table_name)
|
|
||||||
|
|
||||||
def delete_backup(self):
|
def delete_backup(self):
|
||||||
body = self.body
|
body = self.body
|
||||||
backup_arn = body.get("BackupArn")
|
backup_arn = body.get("BackupArn")
|
||||||
try:
|
|
||||||
backup = self.dynamodb_backend.delete_backup(backup_arn)
|
backup = self.dynamodb_backend.delete_backup(backup_arn)
|
||||||
response = {"BackupDescription": backup.description}
|
response = {"BackupDescription": backup.description}
|
||||||
return dynamo_json_dump(response)
|
return dynamo_json_dump(response)
|
||||||
except KeyError:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#BackupNotFoundException"
|
|
||||||
return self.error(er, "Backup not found: %s" % backup_arn)
|
|
||||||
|
|
||||||
def describe_backup(self):
|
def describe_backup(self):
|
||||||
body = self.body
|
body = self.body
|
||||||
backup_arn = body.get("BackupArn")
|
backup_arn = body.get("BackupArn")
|
||||||
try:
|
|
||||||
backup = self.dynamodb_backend.describe_backup(backup_arn)
|
backup = self.dynamodb_backend.describe_backup(backup_arn)
|
||||||
response = {"BackupDescription": backup.description}
|
response = {"BackupDescription": backup.description}
|
||||||
return dynamo_json_dump(response)
|
return dynamo_json_dump(response)
|
||||||
except KeyError:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#BackupNotFoundException"
|
|
||||||
return self.error(er, "Backup not found: %s" % backup_arn)
|
|
||||||
|
|
||||||
def restore_table_from_backup(self):
|
def restore_table_from_backup(self):
|
||||||
body = self.body
|
body = self.body
|
||||||
target_table_name = body.get("TargetTableName")
|
target_table_name = body.get("TargetTableName")
|
||||||
backup_arn = body.get("BackupArn")
|
backup_arn = body.get("BackupArn")
|
||||||
try:
|
|
||||||
restored_table = self.dynamodb_backend.restore_table_from_backup(
|
restored_table = self.dynamodb_backend.restore_table_from_backup(
|
||||||
target_table_name, backup_arn
|
target_table_name, backup_arn
|
||||||
)
|
)
|
||||||
return dynamo_json_dump(restored_table.describe())
|
return dynamo_json_dump(restored_table.describe())
|
||||||
except KeyError:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#BackupNotFoundException"
|
|
||||||
return self.error(er, "Backup not found: %s" % backup_arn)
|
|
||||||
except ValueError:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#TableAlreadyExistsException"
|
|
||||||
return self.error(er, "Table already exists: %s" % target_table_name)
|
|
||||||
|
|
||||||
def restore_table_to_point_in_time(self):
|
def restore_table_to_point_in_time(self):
|
||||||
body = self.body
|
body = self.body
|
||||||
target_table_name = body.get("TargetTableName")
|
target_table_name = body.get("TargetTableName")
|
||||||
source_table_name = body.get("SourceTableName")
|
source_table_name = body.get("SourceTableName")
|
||||||
try:
|
|
||||||
restored_table = self.dynamodb_backend.restore_table_to_point_in_time(
|
restored_table = self.dynamodb_backend.restore_table_to_point_in_time(
|
||||||
target_table_name, source_table_name
|
target_table_name, source_table_name
|
||||||
)
|
)
|
||||||
return dynamo_json_dump(restored_table.describe())
|
return dynamo_json_dump(restored_table.describe())
|
||||||
except KeyError:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#SourceTableNotFoundException"
|
|
||||||
return self.error(er, "Source table not found: %s" % source_table_name)
|
|
||||||
except ValueError:
|
|
||||||
er = "com.amazonaws.dynamodb.v20111205#TableAlreadyExistsException"
|
|
||||||
return self.error(er, "Table already exists: %s" % target_table_name)
|
|
||||||
|
@ -6,7 +6,7 @@ import boto3
|
|||||||
from boto3.dynamodb.conditions import Attr, Key
|
from boto3.dynamodb.conditions import Attr, Key
|
||||||
import re
|
import re
|
||||||
import sure # noqa # pylint: disable=unused-import
|
import sure # noqa # pylint: disable=unused-import
|
||||||
from moto import mock_dynamodb
|
from moto import mock_dynamodb, settings
|
||||||
from moto.dynamodb import dynamodb_backends
|
from moto.dynamodb import dynamodb_backends
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
|
|
||||||
@ -399,10 +399,9 @@ def test_put_item_with_streams():
|
|||||||
"Data": {"M": {"Key1": {"S": "Value1"}, "Key2": {"S": "Value2"}}},
|
"Data": {"M": {"Key1": {"S": "Value1"}, "Key2": {"S": "Value2"}}},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not settings.TEST_SERVER_MODE:
|
||||||
table = dynamodb_backends["us-west-2"].get_table(name)
|
table = dynamodb_backends["us-west-2"].get_table(name)
|
||||||
if not table:
|
|
||||||
# There is no way to access stream data over the API, so this part can't run in server-tests mode.
|
|
||||||
return
|
|
||||||
len(table.stream_shard.items).should.be.equal(1)
|
len(table.stream_shard.items).should.be.equal(1)
|
||||||
stream_record = table.stream_shard.items[0].record
|
stream_record = table.stream_shard.items[0].record
|
||||||
stream_record["eventName"].should.be.equal("INSERT")
|
stream_record["eventName"].should.be.equal("INSERT")
|
||||||
@ -1597,12 +1596,9 @@ def test_bad_scan_filter():
|
|||||||
table = dynamodb.Table("test1")
|
table = dynamodb.Table("test1")
|
||||||
|
|
||||||
# Bad expression
|
# Bad expression
|
||||||
try:
|
with pytest.raises(ClientError) as exc:
|
||||||
table.scan(FilterExpression="client test")
|
table.scan(FilterExpression="client test")
|
||||||
except ClientError as err:
|
exc.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||||
err.response["Error"]["Code"].should.equal("ValidationError")
|
|
||||||
else:
|
|
||||||
raise RuntimeError("Should have raised ResourceInUseException")
|
|
||||||
|
|
||||||
|
|
||||||
@mock_dynamodb
|
@mock_dynamodb
|
||||||
@ -3870,6 +3866,12 @@ def test_transact_write_items_put_conditional_expressions():
|
|||||||
)
|
)
|
||||||
# Assert the exception is correct
|
# Assert the exception is correct
|
||||||
ex.value.response["Error"]["Code"].should.equal("TransactionCanceledException")
|
ex.value.response["Error"]["Code"].should.equal("TransactionCanceledException")
|
||||||
|
reasons = ex.value.response["CancellationReasons"]
|
||||||
|
reasons.should.have.length_of(5)
|
||||||
|
reasons.should.contain(
|
||||||
|
{"Code": "ConditionalCheckFailed", "Message": "The conditional request failed"}
|
||||||
|
)
|
||||||
|
reasons.should.contain({"Code": "None"})
|
||||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
# Assert all are present
|
# Assert all are present
|
||||||
items = dynamodb.scan(TableName="test-table")["Items"]
|
items = dynamodb.scan(TableName="test-table")["Items"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user