DynamoDB: Improve validation (#6986)

This commit is contained in:
Bert Blommers 2023-11-04 09:37:32 -01:00 committed by GitHub
parent 87f816f24f
commit 9136030ecf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 222 additions and 85 deletions

View File

@ -10,7 +10,7 @@ from moto.dynamodb.parsing.reserved_keywords import ReservedKeywords
def get_filter_expression( def get_filter_expression(
expr: Optional[str], expr: Optional[str],
names: Optional[Dict[str, str]], names: Optional[Dict[str, str]],
values: Optional[Dict[str, str]], values: Optional[Dict[str, Dict[str, str]]],
) -> Union["Op", "Func"]: ) -> Union["Op", "Func"]:
""" """
Parse a filter expression into an Op. Parse a filter expression into an Op.
@ -145,7 +145,7 @@ class ConditionExpressionParser:
self, self,
condition_expression: Optional[str], condition_expression: Optional[str],
expression_attribute_names: Optional[Dict[str, str]], expression_attribute_names: Optional[Dict[str, str]],
expression_attribute_values: Optional[Dict[str, str]], expression_attribute_values: Optional[Dict[str, Dict[str, str]]],
): ):
self.condition_expression = condition_expression self.condition_expression = condition_expression
self.expression_attribute_names = expression_attribute_names self.expression_attribute_names = expression_attribute_names
@ -423,7 +423,7 @@ class ConditionExpressionParser:
children=[], children=[],
) )
def _lookup_expression_attribute_value(self, name: str) -> str: def _lookup_expression_attribute_value(self, name: str) -> Dict[str, str]:
return self.expression_attribute_values[name] # type: ignore[index] return self.expression_attribute_values[name] # type: ignore[index]
def _lookup_expression_attribute_name(self, name: str) -> str: def _lookup_expression_attribute_name(self, name: str) -> str:

View File

@ -368,3 +368,9 @@ class TransactWriteSingleOpException(MockValidationException):
class SerializationException(DynamodbException): class SerializationException(DynamodbException):
def __init__(self, msg: str): def __init__(self, msg: str):
super().__init__(error_type="SerializationException", message=msg) super().__init__(error_type="SerializationException", message=msg)
class UnknownKeyType(MockValidationException):
def __init__(self, key_type: str, position: str):
msg = f"1 validation error detected: Value '{key_type}' at '{position}' failed to satisfy constraint: Member must satisfy enum value set: [HASH, RANGE]"
super().__init__(msg)

View File

@ -317,7 +317,7 @@ class DynamoDBBackend(BaseBackend):
projection_expressions: Optional[List[List[str]]], projection_expressions: Optional[List[List[str]]],
index_name: Optional[str] = None, index_name: Optional[str] = None,
expr_names: Optional[Dict[str, str]] = None, expr_names: Optional[Dict[str, str]] = None,
expr_values: Optional[Dict[str, str]] = None, expr_values: Optional[Dict[str, Dict[str, str]]] = None,
filter_expression: Optional[str] = None, filter_expression: Optional[str] = None,
**filter_kwargs: Any, **filter_kwargs: Any,
) -> Tuple[List[Item], int, Optional[Dict[str, Any]]]: ) -> Tuple[List[Item], int, Optional[Dict[str, Any]]]:

View File

@ -1,5 +1,5 @@
from enum import Enum from enum import Enum
from typing import Any, List, Dict, Tuple, Optional from typing import Any, List, Dict, Tuple, Optional, Union
from moto.dynamodb.exceptions import MockValidationException from moto.dynamodb.exceptions import MockValidationException
from moto.utilities.tokenizer import GenericTokenizer from moto.utilities.tokenizer import GenericTokenizer
@ -19,7 +19,7 @@ def get_key(schema: List[Dict[str, str]], key_type: str) -> Optional[str]:
def parse_expression( def parse_expression(
key_condition_expression: str, key_condition_expression: str,
expression_attribute_values: Dict[str, str], expression_attribute_values: Dict[str, Dict[str, str]],
expression_attribute_names: Dict[str, str], expression_attribute_names: Dict[str, str],
schema: List[Dict[str, str]], schema: List[Dict[str, str]],
) -> Tuple[Dict[str, Any], Optional[str], List[Dict[str, Any]]]: ) -> Tuple[Dict[str, Any], Optional[str], List[Dict[str, Any]]]:
@ -35,7 +35,7 @@ def parse_expression(
current_stage: Optional[EXPRESSION_STAGES] = None current_stage: Optional[EXPRESSION_STAGES] = None
current_phrase = "" current_phrase = ""
key_name = comparison = "" key_name = comparison = ""
key_values = [] key_values: List[Union[Dict[str, str], str]] = []
results: List[Tuple[str, str, Any]] = [] results: List[Tuple[str, str, Any]] = []
tokenizer = GenericTokenizer(key_condition_expression) tokenizer = GenericTokenizer(key_condition_expression)
for crnt_char in tokenizer: for crnt_char in tokenizer:

View File

@ -13,6 +13,7 @@ from moto.dynamodb.parsing.reserved_keywords import ReservedKeywords
from .exceptions import ( from .exceptions import (
MockValidationException, MockValidationException,
ResourceNotFoundException, ResourceNotFoundException,
UnknownKeyType,
) )
from moto.dynamodb.models import dynamodb_backends, Table, DynamoDBBackend from moto.dynamodb.models import dynamodb_backends, Table, DynamoDBBackend
from moto.dynamodb.models.utilities import dynamo_json_dump from moto.dynamodb.models.utilities import dynamo_json_dump
@ -242,21 +243,42 @@ class DynamoHandler(BaseResponse):
sse_spec = body.get("SSESpecification") sse_spec = body.get("SSESpecification")
# getting the schema # getting the schema
key_schema = body["KeySchema"] key_schema = body["KeySchema"]
for idx, _key in enumerate(key_schema, start=1):
key_type = _key["KeyType"]
if key_type not in ["HASH", "RANGE"]:
raise UnknownKeyType(
key_type=key_type, position=f"keySchema.{idx}.member.keyType"
)
# getting attribute definition # getting attribute definition
attr = body["AttributeDefinitions"] attr = body["AttributeDefinitions"]
# getting the indexes
# getting/validating the indexes
global_indexes = body.get("GlobalSecondaryIndexes") global_indexes = body.get("GlobalSecondaryIndexes")
if global_indexes == []: if global_indexes == []:
raise MockValidationException( raise MockValidationException(
"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 []
for idx, g_idx in enumerate(global_indexes, start=1):
for idx2, _key in enumerate(g_idx["KeySchema"], start=1):
key_type = _key["KeyType"]
if key_type not in ["HASH", "RANGE"]:
position = f"globalSecondaryIndexes.{idx}.member.keySchema.{idx2}.member.keyType"
raise UnknownKeyType(key_type=key_type, position=position)
local_secondary_indexes = body.get("LocalSecondaryIndexes") local_secondary_indexes = body.get("LocalSecondaryIndexes")
if local_secondary_indexes == []: if local_secondary_indexes == []:
raise MockValidationException( raise MockValidationException(
"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 []
for idx, g_idx in enumerate(local_secondary_indexes, start=1):
for idx2, _key in enumerate(g_idx["KeySchema"], start=1):
key_type = _key["KeyType"]
if key_type not in ["HASH", "RANGE"]:
position = f"localSecondaryIndexes.{idx}.member.keySchema.{idx2}.member.keyType"
raise UnknownKeyType(key_type=key_type, position=position)
# Verify AttributeDefinitions list all # Verify AttributeDefinitions list all
expected_attrs = [] expected_attrs = []
expected_attrs.extend([key["AttributeName"] for key in key_schema]) expected_attrs.extend([key["AttributeName"] for key in key_schema])
@ -462,7 +484,7 @@ class DynamoHandler(BaseResponse):
# expression # expression
condition_expression = self.body.get("ConditionExpression") condition_expression = self.body.get("ConditionExpression")
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._get_expr_attr_values()
if condition_expression: if condition_expression:
overwrite = False overwrite = False
@ -650,7 +672,7 @@ class DynamoHandler(BaseResponse):
projection_expression = self._get_projection_expression() projection_expression = self._get_projection_expression()
expression_attribute_names = self.body.get("ExpressionAttributeNames", {}) expression_attribute_names = self.body.get("ExpressionAttributeNames", {})
filter_expression = self._get_filter_expression() filter_expression = self._get_filter_expression()
expression_attribute_values = self.body.get("ExpressionAttributeValues", {}) expression_attribute_values = self._get_expr_attr_values()
projection_expressions = self._adjust_projection_expression( projection_expressions = self._adjust_projection_expression(
projection_expression, expression_attribute_names projection_expression, expression_attribute_names
@ -776,7 +798,7 @@ class DynamoHandler(BaseResponse):
filters[attribute_name] = (comparison_operator, comparison_values) filters[attribute_name] = (comparison_operator, comparison_values)
filter_expression = self._get_filter_expression() filter_expression = self._get_filter_expression()
expression_attribute_values = self.body.get("ExpressionAttributeValues", {}) expression_attribute_values = self._get_expr_attr_values()
expression_attribute_names = self.body.get("ExpressionAttributeNames", {}) expression_attribute_names = self.body.get("ExpressionAttributeNames", {})
projection_expression = self._get_projection_expression() projection_expression = self._get_projection_expression()
exclusive_start_key = self.body.get("ExclusiveStartKey") exclusive_start_key = self.body.get("ExclusiveStartKey")
@ -824,7 +846,7 @@ class DynamoHandler(BaseResponse):
# expression # expression
condition_expression = self.body.get("ConditionExpression") condition_expression = self.body.get("ConditionExpression")
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._get_expr_attr_values()
item = self.dynamodb_backend.delete_item( item = self.dynamodb_backend.delete_item(
name, name,
@ -879,7 +901,7 @@ class DynamoHandler(BaseResponse):
# expression # expression
condition_expression = self.body.get("ConditionExpression") condition_expression = self.body.get("ConditionExpression")
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._get_expr_attr_values()
item = self.dynamodb_backend.update_item( item = self.dynamodb_backend.update_item(
name, name,
@ -920,6 +942,15 @@ class DynamoHandler(BaseResponse):
) )
return dynamo_json_dump(item_dict) return dynamo_json_dump(item_dict)
def _get_expr_attr_values(self) -> Dict[str, Dict[str, str]]:
values = self.body.get("ExpressionAttributeValues", {})
for key in values.keys():
if not key.startswith(":"):
raise MockValidationException(
f'ExpressionAttributeValues contains invalid key: Syntax error; key: "{key}"'
)
return values
def _build_updated_new_attributes(self, original: Any, changed: Any) -> Any: def _build_updated_new_attributes(self, original: Any, changed: Any) -> Any:
if type(changed) != type(original): if type(changed) != type(original):
return changed return changed

View File

@ -5,7 +5,7 @@ from moto import mock_dynamodb
from uuid import uuid4 from uuid import uuid4
def dynamodb_aws_verified(func): def dynamodb_aws_verified(create_table: bool = True):
""" """
Function that is verified to work against AWS. Function that is verified to work against AWS.
Can be run against AWS at any time by setting: Can be run against AWS at any time by setting:
@ -19,39 +19,47 @@ def dynamodb_aws_verified(func):
- Delete the table - Delete the table
""" """
@wraps(func) def inner(func):
def pagination_wrapper(): @wraps(func)
client = boto3.client("dynamodb", region_name="us-east-1") def pagination_wrapper():
table_name = "t" + str(uuid4())[0:6] client = boto3.client("dynamodb", region_name="us-east-1")
table_name = "t" + str(uuid4())[0:6]
allow_aws_request = ( allow_aws_request = (
os.environ.get("MOTO_TEST_ALLOW_AWS_REQUEST", "false").lower() == "true" os.environ.get("MOTO_TEST_ALLOW_AWS_REQUEST", "false").lower() == "true"
) )
if allow_aws_request: if allow_aws_request:
print(f"Test {func} will create DynamoDB Table {table_name}") if create_table:
resp = create_table_and_test(table_name, client) print(f"Test {func} will create DynamoDB Table {table_name}")
else: return create_table_and_test(table_name, client)
with mock_dynamodb(): else:
resp = create_table_and_test(table_name, client) return func()
return resp else:
with mock_dynamodb():
if create_table:
return create_table_and_test(table_name, client)
else:
return func()
def create_table_and_test(table_name, client): def create_table_and_test(table_name, client):
client.create_table( client.create_table(
TableName=table_name, TableName=table_name,
KeySchema=[{"AttributeName": "pk", "KeyType": "HASH"}], KeySchema=[{"AttributeName": "pk", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "pk", "AttributeType": "S"}], AttributeDefinitions=[{"AttributeName": "pk", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 5}, ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 5},
Tags=[{"Key": "environment", "Value": "moto_tests"}], Tags=[{"Key": "environment", "Value": "moto_tests"}],
) )
waiter = client.get_waiter("table_exists") waiter = client.get_waiter("table_exists")
waiter.wait(TableName=table_name) waiter.wait(TableName=table_name)
try: try:
resp = func(table_name) resp = func(table_name)
finally: finally:
### CLEANUP ### ### CLEANUP ###
client.delete_table(TableName=table_name) client.delete_table(TableName=table_name)
return resp return resp
return pagination_wrapper return pagination_wrapper
return inner

View File

@ -1117,7 +1117,7 @@ def test_query_with_missing_expression_attribute():
@pytest.mark.aws_verified @pytest.mark.aws_verified
@dynamodb_aws_verified @dynamodb_aws_verified()
def test_update_item_returns_old_item(table_name=None): def test_update_item_returns_old_item(table_name=None):
dynamodb = boto3.resource("dynamodb", region_name="us-east-1") dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
table = dynamodb.Table(table_name) table = dynamodb.Table(table_name)
@ -1164,3 +1164,36 @@ def test_update_item_returns_old_item(table_name=None):
"lock": {"M": {"acquired_at": {"N": "123"}}}, "lock": {"M": {"acquired_at": {"N": "123"}}},
"pk": {"S": "mark"}, "pk": {"S": "mark"},
} }
@pytest.mark.aws_verified
@dynamodb_aws_verified()
def test_scan_with_missing_value(table_name=None):
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
table = dynamodb.Table(table_name)
with pytest.raises(ClientError) as exc:
table.scan(
FilterExpression="attr = loc",
# Missing ':'
ExpressionAttributeValues={"loc": "sth"},
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== 'ExpressionAttributeValues contains invalid key: Syntax error; key: "loc"'
)
with pytest.raises(ClientError) as exc:
table.query(
KeyConditionExpression="attr = loc",
# Missing ':'
ExpressionAttributeValues={"loc": "sth"},
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== 'ExpressionAttributeValues contains invalid key: Syntax error; key: "loc"'
)

View File

@ -3686,7 +3686,7 @@ def test_transact_write_items_put():
@pytest.mark.aws_verified @pytest.mark.aws_verified
@dynamodb_aws_verified @dynamodb_aws_verified()
def test_transact_write_items_put_conditional_expressions(table_name=None): def test_transact_write_items_put_conditional_expressions(table_name=None):
dynamodb = boto3.client("dynamodb", region_name="us-east-1") dynamodb = boto3.client("dynamodb", region_name="us-east-1")
@ -3731,7 +3731,7 @@ def test_transact_write_items_put_conditional_expressions(table_name=None):
@pytest.mark.aws_verified @pytest.mark.aws_verified
@dynamodb_aws_verified @dynamodb_aws_verified()
def test_transact_write_items_failure__return_item(table_name=None): def test_transact_write_items_failure__return_item(table_name=None):
dynamodb = boto3.client("dynamodb", region_name="us-east-1") dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.put_item(TableName=table_name, Item={"pk": {"S": "foo2"}}) dynamodb.put_item(TableName=table_name, Item={"pk": {"S": "foo2"}})

View File

@ -3,9 +3,10 @@ from botocore.exceptions import ClientError
from datetime import datetime from datetime import datetime
import pytest import pytest
from moto import mock_dynamodb, settings from moto import mock_dynamodb
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
from moto.dynamodb.models import dynamodb_backends
from . import dynamodb_aws_verified
@mock_dynamodb @mock_dynamodb
@ -399,36 +400,94 @@ def test_create_table_with_ssespecification__custom_kms_key():
assert actual["SSEDescription"]["KMSMasterKeyArn"] == "custom-kms-key" assert actual["SSEDescription"]["KMSMasterKeyArn"] == "custom-kms-key"
@mock_dynamodb @pytest.mark.aws_verified
@dynamodb_aws_verified(create_table=False)
def test_create_table__specify_non_key_column(): def test_create_table__specify_non_key_column():
client = boto3.client("dynamodb", "us-east-2") dynamodb = boto3.client("dynamodb", region_name="us-east-1")
client.create_table( with pytest.raises(ClientError) as exc:
TableName="tab", dynamodb.create_table(
KeySchema=[ TableName="unknown-key-type",
{"AttributeName": "PK", "KeyType": "HASH"}, KeySchema=[
{"AttributeName": "SomeColumn", "KeyType": "N"}, {"AttributeName": "pk", "KeyType": "HASH"},
], {"AttributeName": "sk", "KeyType": "SORT"},
BillingMode="PAY_PER_REQUEST", ],
AttributeDefinitions=[ AttributeDefinitions=[
{"AttributeName": "PK", "AttributeType": "S"}, {"AttributeName": "pk", "AttributeType": "S"},
{"AttributeName": "SomeColumn", "AttributeType": "N"}, {"AttributeName": "sk", "AttributeType": "S"},
], ],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "1 validation error detected: Value 'SORT' at 'keySchema.2.member.keyType' failed to satisfy constraint: Member must satisfy enum value set: [HASH, RANGE]"
) )
actual = client.describe_table(TableName="tab")["Table"] # Verify we get the same message for Global Secondary Indexes
assert actual["KeySchema"] == [ with pytest.raises(ClientError) as exc:
{"AttributeName": "PK", "KeyType": "HASH"}, dynamodb.create_table(
{"AttributeName": "SomeColumn", "KeyType": "N"}, TableName="unknown-key-type",
] KeySchema=[
{"AttributeName": "pk", "KeyType": "HASH"},
{"AttributeName": "sk", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "pk", "AttributeType": "S"},
{"AttributeName": "sk", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
GlobalSecondaryIndexes=[
{
"IndexName": "TestGSI",
# Note that the attributes are not declared, which is also invalid
# But AWS trips over the KeyType=SORT first
"KeySchema": [
{"AttributeName": "n/a", "KeyType": "HASH"},
{"AttributeName": "sth", "KeyType": "SORT"},
],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 5,
"WriteCapacityUnits": 5,
},
}
],
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "1 validation error detected: Value 'SORT' at 'globalSecondaryIndexes.1.member.keySchema.2.member.keyType' failed to satisfy constraint: Member must satisfy enum value set: [HASH, RANGE]"
)
if not settings.TEST_SERVER_MODE: # Verify we get the same message for Local Secondary Indexes
ddb = dynamodb_backends[ACCOUNT_ID]["us-east-2"] with pytest.raises(ClientError) as exc:
assert {"AttributeName": "PK", "AttributeType": "S"} in ddb.tables["tab"].attr dynamodb.create_table(
assert {"AttributeName": "SomeColumn", "AttributeType": "N"} in ddb.tables[ TableName="unknown-key-type",
"tab" KeySchema=[
].attr {"AttributeName": "pk", "KeyType": "HASH"},
# It should recognize PK is the Hash Key {"AttributeName": "sk", "KeyType": "RANGE"},
assert ddb.tables["tab"].hash_key_attr == "PK" ],
# It should recognize that SomeColumn is not a Range Key AttributeDefinitions=[
assert ddb.tables["tab"].has_range_key is False {"AttributeName": "pk", "AttributeType": "S"},
assert ddb.tables["tab"].range_key_names == [] {"AttributeName": "sk", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
LocalSecondaryIndexes=[
{
"IndexName": "test_lsi",
"KeySchema": [
{"AttributeName": "pk", "KeyType": "HASH"},
{"AttributeName": "lsi_range_key", "KeyType": "SORT"},
],
"Projection": {"ProjectionType": "ALL"},
}
],
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "1 validation error detected: Value 'SORT' at 'localSecondaryIndexes.1.member.keySchema.2.member.keyType' failed to satisfy constraint: Member must satisfy enum value set: [HASH, RANGE]"
)

View File

@ -25,7 +25,7 @@ def create_items(table_name):
@pytest.mark.aws_verified @pytest.mark.aws_verified
@dynamodb_aws_verified @dynamodb_aws_verified()
def test_execute_statement_select_star(table_name=None): def test_execute_statement_select_star(table_name=None):
client = boto3.client("dynamodb", "us-east-1") client = boto3.client("dynamodb", "us-east-1")
create_items(table_name) create_items(table_name)
@ -35,7 +35,7 @@ def test_execute_statement_select_star(table_name=None):
@pytest.mark.aws_verified @pytest.mark.aws_verified
@dynamodb_aws_verified @dynamodb_aws_verified()
def test_execute_statement_select_attr(table_name=None): def test_execute_statement_select_attr(table_name=None):
client = boto3.client("dynamodb", "us-east-1") client = boto3.client("dynamodb", "us-east-1")
create_items(table_name) create_items(table_name)
@ -47,7 +47,7 @@ def test_execute_statement_select_attr(table_name=None):
@pytest.mark.aws_verified @pytest.mark.aws_verified
@dynamodb_aws_verified @dynamodb_aws_verified()
def test_execute_statement_with_quoted_table(table_name=None): def test_execute_statement_with_quoted_table(table_name=None):
client = boto3.client("dynamodb", "us-east-1") client = boto3.client("dynamodb", "us-east-1")
create_items(table_name) create_items(table_name)
@ -57,7 +57,7 @@ def test_execute_statement_with_quoted_table(table_name=None):
@pytest.mark.aws_verified @pytest.mark.aws_verified
@dynamodb_aws_verified @dynamodb_aws_verified()
def test_execute_statement_with_parameter(table_name=None): def test_execute_statement_with_parameter(table_name=None):
client = boto3.client("dynamodb", "us-east-1") client = boto3.client("dynamodb", "us-east-1")
create_items(table_name) create_items(table_name)
@ -77,7 +77,7 @@ def test_execute_statement_with_parameter(table_name=None):
@pytest.mark.aws_verified @pytest.mark.aws_verified
@dynamodb_aws_verified @dynamodb_aws_verified()
def test_execute_statement_with_no_results(table_name=None): def test_execute_statement_with_no_results(table_name=None):
client = boto3.client("dynamodb", "us-east-1") client = boto3.client("dynamodb", "us-east-1")
create_items(table_name) create_items(table_name)
@ -201,7 +201,7 @@ class TestBatchExecuteStatement(TestCase):
@pytest.mark.aws_verified @pytest.mark.aws_verified
@dynamodb_aws_verified @dynamodb_aws_verified()
def test_execute_statement_with_all_clauses(table_name=None): def test_execute_statement_with_all_clauses(table_name=None):
dynamodb_client = boto3.client("dynamodb", "us-east-1") dynamodb_client = boto3.client("dynamodb", "us-east-1")