moto/tests/test_dynamodb/test_dynamodb.py

5860 lines
200 KiB
Python

import re
import uuid
from datetime import datetime
from decimal import Decimal
import boto3
import pytest
from boto3.dynamodb.conditions import Attr, Key
from boto3.dynamodb.types import Binary
from botocore.exceptions import ClientError
import moto.dynamodb.comparisons
import moto.dynamodb.models
from moto import mock_dynamodb, settings
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
from moto.dynamodb import dynamodb_backends
from . import dynamodb_aws_verified
@mock_dynamodb
@pytest.mark.parametrize(
"names",
[[], ["TestTable"], ["TestTable1", "TestTable2"]],
ids=["no-table", "one-table", "multiple-tables"],
)
def test_list_tables_boto3(names):
conn = boto3.client("dynamodb", region_name="us-west-2")
for name in names:
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
assert conn.list_tables()["TableNames"] == names
@mock_dynamodb
def test_list_tables_paginated():
conn = boto3.client("dynamodb", region_name="us-west-2")
for name in ["name1", "name2", "name3"]:
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
res = conn.list_tables(Limit=2)
assert res["TableNames"] == ["name1", "name2"]
assert res["LastEvaluatedTableName"] == "name2"
res = conn.list_tables(Limit=1, ExclusiveStartTableName="name1")
assert res["TableNames"] == ["name2"]
assert res["LastEvaluatedTableName"] == "name2"
res = conn.list_tables(ExclusiveStartTableName="name1")
assert res["TableNames"] == ["name2", "name3"]
assert "LastEvaluatedTableName" not in res
@mock_dynamodb
def test_describe_missing_table_boto3():
conn = boto3.client("dynamodb", region_name="us-west-2")
with pytest.raises(ClientError) as ex:
conn.describe_table(TableName="messages")
assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert (
ex.value.response["Error"]["Message"]
== "Requested resource not found: Table: messages not found"
)
@mock_dynamodb
def test_list_table_tags():
name = "TestTable"
conn = boto3.client(
"dynamodb",
region_name="us-west-2",
aws_access_key_id="ak",
aws_secret_access_key="sk",
)
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table_description = conn.describe_table(TableName=name)
arn = table_description["Table"]["TableArn"]
# Tag table
tags = [
{"Key": "TestTag", "Value": "TestValue"},
{"Key": "TestTag2", "Value": "TestValue2"},
]
conn.tag_resource(ResourceArn=arn, Tags=tags)
# Check tags
resp = conn.list_tags_of_resource(ResourceArn=arn)
assert resp["Tags"] == tags
# Remove 1 tag
conn.untag_resource(ResourceArn=arn, TagKeys=["TestTag"])
# Check tags
resp = conn.list_tags_of_resource(ResourceArn=arn)
assert resp["Tags"] == [{"Key": "TestTag2", "Value": "TestValue2"}]
@mock_dynamodb
def test_list_table_tags_empty():
name = "TestTable"
conn = boto3.client(
"dynamodb",
region_name="us-west-2",
aws_access_key_id="ak",
aws_secret_access_key="sk",
)
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table_description = conn.describe_table(TableName=name)
arn = table_description["Table"]["TableArn"]
resp = conn.list_tags_of_resource(ResourceArn=arn)
assert resp["Tags"] == []
@mock_dynamodb
def test_list_table_tags_paginated():
name = "TestTable"
conn = boto3.client(
"dynamodb",
region_name="us-west-2",
aws_access_key_id="ak",
aws_secret_access_key="sk",
)
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table_description = conn.describe_table(TableName=name)
arn = table_description["Table"]["TableArn"]
for i in range(11):
tags = [{"Key": f"TestTag{i}", "Value": "TestValue"}]
conn.tag_resource(ResourceArn=arn, Tags=tags)
resp = conn.list_tags_of_resource(ResourceArn=arn)
assert len(resp["Tags"]) == 10
assert "NextToken" in resp.keys()
resp2 = conn.list_tags_of_resource(ResourceArn=arn, NextToken=resp["NextToken"])
assert len(resp2["Tags"]) == 1
assert "NextToken" not in resp2.keys()
@mock_dynamodb
def test_list_not_found_table_tags():
conn = boto3.client(
"dynamodb",
region_name="us-west-2",
aws_access_key_id="ak",
aws_secret_access_key="sk",
)
arn = "DymmyArn"
try:
conn.list_tags_of_resource(ResourceArn=arn)
except ClientError as exception:
assert exception.response["Error"]["Code"] == "ResourceNotFoundException"
@mock_dynamodb
def test_item_add_empty_string_hash_key_exception():
name = "TestTable"
conn = boto3.client(
"dynamodb",
region_name="us-west-2",
aws_access_key_id="ak",
aws_secret_access_key="sk",
)
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
with pytest.raises(ClientError) as ex:
conn.put_item(
TableName=name,
Item={
"forum_name": {"S": ""},
"subject": {"S": "Check this out!"},
"Body": {"S": "http://url_to_lolcat.gif"},
"SentBy": {"S": "someone@somewhere.edu"},
"ReceivedTime": {"S": "12/9/2011 11:36:03 PM"},
},
)
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert (
ex.value.response["Error"]["Message"]
== "One or more parameter values were invalid: An AttributeValue may not contain an empty string. Key: forum_name"
)
@mock_dynamodb
def test_item_add_empty_string_range_key_exception():
name = "TestTable"
conn = boto3.client(
"dynamodb",
region_name="us-west-2",
aws_access_key_id="ak",
aws_secret_access_key="sk",
)
conn.create_table(
TableName=name,
KeySchema=[
{"AttributeName": "forum_name", "KeyType": "HASH"},
{"AttributeName": "ReceivedTime", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "forum_name", "AttributeType": "S"},
{"AttributeName": "ReceivedTime", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
with pytest.raises(ClientError) as ex:
conn.put_item(
TableName=name,
Item={
"forum_name": {"S": "LOLCat Forum"},
"subject": {"S": "Check this out!"},
"Body": {"S": "http://url_to_lolcat.gif"},
"SentBy": {"S": "someone@somewhere.edu"},
"ReceivedTime": {"S": ""},
},
)
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert (
ex.value.response["Error"]["Message"]
== "One or more parameter values were invalid: An AttributeValue may not contain an empty string. Key: ReceivedTime"
)
@mock_dynamodb
def test_item_add_empty_string_attr_no_exception():
name = "TestTable"
conn = boto3.client(
"dynamodb",
region_name="us-west-2",
aws_access_key_id="ak",
aws_secret_access_key="sk",
)
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
conn.put_item(
TableName=name,
Item={
"forum_name": {"S": "LOLCat Forum"},
"subject": {"S": "Check this out!"},
"Body": {"S": "http://url_to_lolcat.gif"},
"SentBy": {"S": ""},
"ReceivedTime": {"S": "12/9/2011 11:36:03 PM"},
},
)
@mock_dynamodb
def test_update_item_with_empty_string_attr_no_exception():
name = "TestTable"
conn = boto3.client(
"dynamodb",
region_name="us-west-2",
aws_access_key_id="ak",
aws_secret_access_key="sk",
)
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
conn.put_item(
TableName=name,
Item={
"forum_name": {"S": "LOLCat Forum"},
"subject": {"S": "Check this out!"},
"Body": {"S": "http://url_to_lolcat.gif"},
"SentBy": {"S": "test"},
"ReceivedTime": {"S": "12/9/2011 11:36:03 PM"},
},
)
conn.update_item(
TableName=name,
Key={"forum_name": {"S": "LOLCat Forum"}},
UpdateExpression="set Body=:Body",
ExpressionAttributeValues={":Body": {"S": ""}},
)
@mock_dynamodb
def test_query_invalid_table():
conn = boto3.client(
"dynamodb",
region_name="us-west-2",
aws_access_key_id="ak",
aws_secret_access_key="sk",
)
try:
conn.query(
TableName="invalid_table",
KeyConditionExpression="index1 = :partitionkeyval",
ExpressionAttributeValues={":partitionkeyval": {"S": "test"}},
)
except ClientError as exception:
assert exception.response["Error"]["Code"] == "ResourceNotFoundException"
@mock_dynamodb
def test_put_item_with_special_chars():
name = "TestTable"
conn = boto3.client(
"dynamodb",
region_name="us-west-2",
aws_access_key_id="ak",
aws_secret_access_key="sk",
)
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
conn.put_item(
TableName=name,
Item={
"forum_name": {"S": "LOLCat Forum"},
"subject": {"S": "Check this out!"},
"Body": {"S": "http://url_to_lolcat.gif"},
"SentBy": {"S": "test"},
"ReceivedTime": {"S": "12/9/2011 11:36:03 PM"},
'"': {"S": "foo"},
},
)
@mock_dynamodb
def test_put_item_with_streams():
name = "TestTable"
conn = boto3.client(
"dynamodb",
region_name="us-west-2",
aws_access_key_id="ak",
aws_secret_access_key="sk",
)
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
StreamSpecification={
"StreamEnabled": True,
"StreamViewType": "NEW_AND_OLD_IMAGES",
},
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
conn.put_item(
TableName=name,
Item={
"forum_name": {"S": "LOLCat Forum"},
"subject": {"S": "Check this out!"},
"Body": {"S": "http://url_to_lolcat.gif"},
"SentBy": {"S": "test"},
"Data": {"M": {"Key1": {"S": "Value1"}, "Key2": {"S": "Value2"}}},
},
)
result = conn.get_item(TableName=name, Key={"forum_name": {"S": "LOLCat Forum"}})
assert result["Item"] == {
"forum_name": {"S": "LOLCat Forum"},
"subject": {"S": "Check this out!"},
"Body": {"S": "http://url_to_lolcat.gif"},
"SentBy": {"S": "test"},
"Data": {"M": {"Key1": {"S": "Value1"}, "Key2": {"S": "Value2"}}},
}
if not settings.TEST_SERVER_MODE:
table = dynamodb_backends[ACCOUNT_ID]["us-west-2"].get_table(name)
assert len(table.stream_shard.items) == 1
stream_record = table.stream_shard.items[0].record
assert stream_record["eventName"] == "INSERT"
assert stream_record["dynamodb"]["SizeBytes"] == 447
@mock_dynamodb
def test_basic_projection_expression_using_get_item():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
client = boto3.client("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
table = dynamodb.create_table(
TableName="users",
KeySchema=[
{"AttributeName": "forum_name", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "forum_name", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.put_item(
Item={"forum_name": "the-key", "subject": "123", "body": "some test message"}
)
table.put_item(
Item={
"forum_name": "not-the-key",
"subject": "123",
"body": "some other test message",
}
)
result = table.get_item(
Key={"forum_name": "the-key", "subject": "123"},
ProjectionExpression="body, subject",
)
assert result["Item"] == {"subject": "123", "body": "some test message"}
# The projection expression should not remove data from storage
result = table.get_item(Key={"forum_name": "the-key", "subject": "123"})
assert result["Item"] == {
"forum_name": "the-key",
"subject": "123",
"body": "some test message",
}
# Running this against AWS DDB gives an exception so make sure it also fails.:
with pytest.raises(client.exceptions.ClientError):
# botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the GetItem
# operation: "Can not use both expression and non-expression parameters in the same request:
# Non-expression parameters: {AttributesToGet} Expression parameters: {ProjectionExpression}"
table.get_item(
Key={"forum_name": "the-key", "subject": "123"},
ProjectionExpression="body",
AttributesToGet=["body"],
)
@mock_dynamodb
def test_basic_projection_expressions_using_scan():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
dynamodb.create_table(
TableName="users",
KeySchema=[
{"AttributeName": "forum_name", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "forum_name", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.put_item(
Item={"forum_name": "the-key", "subject": "123", "body": "some test message"}
)
table.put_item(
Item={
"forum_name": "not-the-key",
"subject": "123",
"body": "some other test message",
}
)
# Test a scan returning all items
results = table.scan(
FilterExpression=Key("forum_name").eq("the-key"),
ProjectionExpression="body, subject",
)
assert "body" in results["Items"][0]
assert results["Items"][0]["body"] == "some test message"
assert "subject" in results["Items"][0]
table.put_item(
Item={
"forum_name": "the-key",
"subject": "1234",
"body": "yet another test message",
}
)
results = table.scan(
FilterExpression=Key("forum_name").eq("the-key"), ProjectionExpression="body"
)
bodies = [item["body"] for item in results["Items"]]
assert "some test message" in bodies
assert "yet another test message" in bodies
assert "subject" not in results["Items"][0]
assert "forum_name" not in results["Items"][0]
assert "subject" not in results["Items"][1]
assert "forum_name" not in results["Items"][1]
# The projection expression should not remove data from storage
results = table.query(KeyConditionExpression=Key("forum_name").eq("the-key"))
assert "subject" in results["Items"][0]
assert "body" in results["Items"][1]
assert "forum_name" in results["Items"][1]
@mock_dynamodb
def test_nested_projection_expression_using_get_item():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
dynamodb.create_table(
TableName="users",
KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.put_item(
Item={
"forum_name": "key1",
"nested": {
"level1": {"id": "id1", "att": "irrelevant"},
"level2": {"id": "id2", "include": "all"},
"level3": {"id": "irrelevant"},
},
"foo": "bar",
}
)
table.put_item(
Item={
"forum_name": "key2",
"nested": {"id": "id2", "incode": "code2"},
"foo": "bar",
}
)
# Test a get_item returning all items
result = table.get_item(
Key={"forum_name": "key1"},
ProjectionExpression="nested.level1.id, nested.level2",
)["Item"]
assert result == {
"nested": {"level1": {"id": "id1"}, "level2": {"id": "id2", "include": "all"}}
}
# Assert actual data has not been deleted
result = table.get_item(Key={"forum_name": "key1"})["Item"]
assert result == {
"foo": "bar",
"forum_name": "key1",
"nested": {
"level1": {"id": "id1", "att": "irrelevant"},
"level2": {"id": "id2", "include": "all"},
"level3": {"id": "irrelevant"},
},
}
@mock_dynamodb
def test_basic_projection_expressions_using_query():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
dynamodb.create_table(
TableName="users",
KeySchema=[
{"AttributeName": "forum_name", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "forum_name", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.put_item(
Item={"forum_name": "the-key", "subject": "123", "body": "some test message"}
)
table.put_item(
Item={
"forum_name": "not-the-key",
"subject": "123",
"body": "some other test message",
}
)
# Test a query returning all items
result = table.query(
KeyConditionExpression=Key("forum_name").eq("the-key"),
ProjectionExpression="body, subject",
)["Items"][0]
assert "body" in result
assert result["body"] == "some test message"
assert "subject" in result
assert "forum_name" not in result
table.put_item(
Item={
"forum_name": "the-key",
"subject": "1234",
"body": "yet another test message",
}
)
items = table.query(
KeyConditionExpression=Key("forum_name").eq("the-key"),
ProjectionExpression="body",
)["Items"]
assert "body" in items[0]
assert "subject" not in items[0]
assert items[0]["body"] == "some test message"
assert "body" in items[1]
assert "subject" not in items[1]
assert items[1]["body"] == "yet another test message"
# The projection expression should not remove data from storage
items = table.query(KeyConditionExpression=Key("forum_name").eq("the-key"))["Items"]
assert "subject" in items[0]
assert "body" in items[1]
assert "forum_name" in items[1]
@mock_dynamodb
def test_nested_projection_expression_using_query():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
dynamodb.create_table(
TableName="users",
KeySchema=[{"AttributeName": "name", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "name", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.put_item(
Item={
"name": "key1",
"nested": {
"level1": {"id": "id1", "att": "irrelevant"},
"level2": {"id": "id2", "include": "all"},
"level3": {"id": "irrelevant"},
},
"foo": "bar",
}
)
table.put_item(
Item={
"name": "key2",
"nested": {"id": "id2", "incode": "code2"},
"foo": "bar",
}
)
# Test a query returning nested attributes
result = table.query(
KeyConditionExpression=Key("name").eq("key1"),
ProjectionExpression="nested.level1.id, nested.level2",
)
assert result["ScannedCount"] == 1
item = result["Items"][0]
assert "nested" in item
assert item["nested"] == {
"level1": {"id": "id1"},
"level2": {"id": "id2", "include": "all"},
}
assert "foo" not in item
# Assert actual data has not been deleted
result = table.query(KeyConditionExpression=Key("name").eq("key1"))["Items"][0]
assert result == {
"foo": "bar",
"name": "key1",
"nested": {
"level1": {"id": "id1", "att": "irrelevant"},
"level2": {"id": "id2", "include": "all"},
"level3": {"id": "irrelevant"},
},
}
@mock_dynamodb
def test_nested_projection_expression_using_scan():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
dynamodb.create_table(
TableName="users",
KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.put_item(
Item={
"forum_name": "key1",
"nested": {
"level1": {"id": "id1", "att": "irrelevant"},
"level2": {"id": "id2", "include": "all"},
"level3": {"id": "irrelevant"},
},
"foo": "bar",
}
)
table.put_item(
Item={
"forum_name": "key2",
"nested": {"id": "id2", "incode": "code2"},
"foo": "bar",
}
)
# Test a scan
results = table.scan(
FilterExpression=Key("forum_name").eq("key1"),
ProjectionExpression="nested.level1.id, nested.level2",
)["Items"]
assert results == [
{
"nested": {
"level1": {"id": "id1"},
"level2": {"include": "all", "id": "id2"},
}
}
]
# Assert original data is still there
results = table.scan(FilterExpression=Key("forum_name").eq("key1"))["Items"]
assert results == [
{
"forum_name": "key1",
"foo": "bar",
"nested": {
"level1": {"att": "irrelevant", "id": "id1"},
"level2": {"include": "all", "id": "id2"},
"level3": {"id": "irrelevant"},
},
}
]
@mock_dynamodb
def test_basic_projection_expression_using_get_item_with_attr_expression_names():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
table = dynamodb.create_table(
TableName="users",
KeySchema=[
{"AttributeName": "forum_name", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "forum_name", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.put_item(
Item={
"forum_name": "the-key",
"subject": "123",
"body": "some test message",
"attachment": "something",
}
)
table.put_item(
Item={
"forum_name": "not-the-key",
"subject": "123",
"body": "some other test message",
"attachment": "something",
}
)
result = table.get_item(
Key={"forum_name": "the-key", "subject": "123"},
ProjectionExpression="#rl, #rt, subject",
ExpressionAttributeNames={"#rl": "body", "#rt": "attachment"},
)
assert result["Item"] == {
"subject": "123",
"body": "some test message",
"attachment": "something",
}
@mock_dynamodb
def test_basic_projection_expressions_using_query_with_attr_expression_names():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
table = dynamodb.create_table(
TableName="users",
KeySchema=[
{"AttributeName": "forum_name", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "forum_name", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.put_item(
Item={
"forum_name": "the-key",
"subject": "123",
"body": "some test message",
"attachment": "something",
}
)
table.put_item(
Item={
"forum_name": "not-the-key",
"subject": "123",
"body": "some other test message",
"attachment": "something",
}
)
# Test a query returning all items
results = table.query(
KeyConditionExpression=Key("forum_name").eq("the-key"),
ProjectionExpression="#rl, #rt, subject",
ExpressionAttributeNames={"#rl": "body", "#rt": "attachment"},
)
assert results["Items"][0]["body"] == "some test message"
assert results["Items"][0]["subject"] == "123"
assert results["Items"][0]["attachment"] == "something"
@mock_dynamodb
def test_nested_projection_expression_using_get_item_with_attr_expression():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
dynamodb.create_table(
TableName="users",
KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.put_item(
Item={
"forum_name": "key1",
"nested": {
"level1": {"id": "id1", "att": "irrelevant"},
"level.2": {"id": "id2", "include": "all"},
"level3": {
"id": "irrelevant",
"children": [{"Name": "child_a"}, {"Name": "child_b"}],
},
},
"foo": "bar",
}
)
table.put_item(
Item={
"forum_name": "key2",
"nested": {"id": "id2", "incode": "code2"},
"foo": "bar",
}
)
# Test a get_item returning all items
result = table.get_item(
Key={"forum_name": "key1"},
ProjectionExpression="#nst.level1.id, #nst.#lvl2",
ExpressionAttributeNames={"#nst": "nested", "#lvl2": "level.2"},
)["Item"]
assert result == {
"nested": {"level1": {"id": "id1"}, "level.2": {"id": "id2", "include": "all"}}
}
# Assert actual data has not been deleted
result = table.get_item(Key={"forum_name": "key1"})["Item"]
assert result == {
"foo": "bar",
"forum_name": "key1",
"nested": {
"level1": {"id": "id1", "att": "irrelevant"},
"level.2": {"id": "id2", "include": "all"},
"level3": {
"id": "irrelevant",
"children": [{"Name": "child_a"}, {"Name": "child_b"}],
},
},
}
# Test a get_item retrieving children
result = table.get_item(
Key={"forum_name": "key1"},
ProjectionExpression="nested.level3.children[0].Name",
)["Item"]
assert result == {"nested": {"level3": {"children": [{"Name": "child_a"}]}}}
@mock_dynamodb
def test_nested_projection_expression_using_query_with_attr_expression_names():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
dynamodb.create_table(
TableName="users",
KeySchema=[{"AttributeName": "name", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "name", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.put_item(
Item={
"name": "key1",
"nested": {
"level1": {"id": "id1", "att": "irrelevant"},
"level2": {"id": "id2", "include": "all"},
"level3": {"id": "irrelevant"},
},
"foo": "bar",
}
)
table.put_item(
Item={
"name": "key2",
"nested": {"id": "id2", "incode": "code2"},
"foo": "bar",
}
)
# Test a query returning all items
result = table.query(
KeyConditionExpression=Key("name").eq("key1"),
ProjectionExpression="#nst.level1.id, #nst.#lvl2",
ExpressionAttributeNames={"#nst": "nested", "#lvl2": "level2"},
)["Items"][0]
assert result["nested"] == {
"level1": {"id": "id1"},
"level2": {"id": "id2", "include": "all"},
}
assert "foo" not in result
# Assert actual data has not been deleted
result = table.query(KeyConditionExpression=Key("name").eq("key1"))["Items"][0]
assert result == {
"foo": "bar",
"name": "key1",
"nested": {
"level1": {"id": "id1", "att": "irrelevant"},
"level2": {"id": "id2", "include": "all"},
"level3": {"id": "irrelevant"},
},
}
@mock_dynamodb
def test_basic_projection_expressions_using_scan_with_attr_expression_names():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
table = dynamodb.create_table(
TableName="users",
KeySchema=[
{"AttributeName": "forum_name", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "forum_name", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.put_item(
Item={
"forum_name": "the-key",
"subject": "123",
"body": "some test message",
"attachment": "something",
}
)
table.put_item(
Item={
"forum_name": "not-the-key",
"subject": "123",
"body": "some other test message",
"attachment": "something",
}
)
# Test a scan returning all items
results = table.scan(
FilterExpression=Key("forum_name").eq("the-key"),
ProjectionExpression="#rl, #rt, subject",
ExpressionAttributeNames={"#rl": "body", "#rt": "attachment"},
)
assert "body" in results["Items"][0]
assert "attachment" in results["Items"][0]
assert "subject" in results["Items"][0]
assert "form_name" not in results["Items"][0]
# Test without a FilterExpression
results = table.scan(
ProjectionExpression="#rl, #rt, subject",
ExpressionAttributeNames={"#rl": "body", "#rt": "attachment"},
)
assert "body" in results["Items"][0]
assert "attachment" in results["Items"][0]
assert "subject" in results["Items"][0]
assert "form_name" not in results["Items"][0]
@mock_dynamodb
def test_nested_projection_expression_using_scan_with_attr_expression_names():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
dynamodb.create_table(
TableName="users",
KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.put_item(
Item={
"forum_name": "key1",
"nested": {
"level1": {"id": "id1", "att": "irrelevant"},
"level2": {"id": "id2", "include": "all"},
"level3": {"id": "irrelevant"},
},
"foo": "bar",
}
)
table.put_item(
Item={
"forum_name": "key2",
"nested": {"id": "id2", "incode": "code2"},
"foo": "bar",
}
)
# Test a scan
results = table.scan(
FilterExpression=Key("forum_name").eq("key1"),
ProjectionExpression="nested.level1.id, nested.level2",
ExpressionAttributeNames={"#nst": "nested", "#lvl2": "level2"},
)["Items"]
assert results == [
{
"nested": {
"level1": {"id": "id1"},
"level2": {"include": "all", "id": "id2"},
}
}
]
# Assert original data is still there
results = table.scan(FilterExpression=Key("forum_name").eq("key1"))["Items"]
assert results == [
{
"forum_name": "key1",
"foo": "bar",
"nested": {
"level1": {"att": "irrelevant", "id": "id1"},
"level2": {"include": "all", "id": "id2"},
"level3": {"id": "irrelevant"},
},
}
]
@mock_dynamodb
def test_put_empty_item():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
dynamodb.create_table(
AttributeDefinitions=[{"AttributeName": "structure_id", "AttributeType": "S"}],
TableName="test",
KeySchema=[{"AttributeName": "structure_id", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
table = dynamodb.Table("test")
with pytest.raises(ClientError) as ex:
table.put_item(Item={})
assert (
ex.value.response["Error"]["Message"]
== "One or more parameter values were invalid: Missing the key structure_id in the item"
)
assert ex.value.response["Error"]["Code"] == "ValidationException"
@mock_dynamodb
def test_put_item_nonexisting_hash_key():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
dynamodb.create_table(
AttributeDefinitions=[{"AttributeName": "structure_id", "AttributeType": "S"}],
TableName="test",
KeySchema=[{"AttributeName": "structure_id", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
table = dynamodb.Table("test")
with pytest.raises(ClientError) as ex:
table.put_item(Item={"a_terribly_misguided_id_attribute": "abcdef"})
assert (
ex.value.response["Error"]["Message"]
== "One or more parameter values were invalid: Missing the key structure_id in the item"
)
assert ex.value.response["Error"]["Code"] == "ValidationException"
@mock_dynamodb
def test_put_item_nonexisting_range_key():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
dynamodb.create_table(
AttributeDefinitions=[
{"AttributeName": "structure_id", "AttributeType": "S"},
{"AttributeName": "added_at", "AttributeType": "N"},
],
TableName="test",
KeySchema=[
{"AttributeName": "structure_id", "KeyType": "HASH"},
{"AttributeName": "added_at", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
table = dynamodb.Table("test")
with pytest.raises(ClientError) as ex:
table.put_item(Item={"structure_id": "abcdef"})
assert (
ex.value.response["Error"]["Message"]
== "One or more parameter values were invalid: Missing the key added_at in the item"
)
assert ex.value.response["Error"]["Code"] == "ValidationException"
def test_filter_expression():
row1 = moto.dynamodb.models.Item(
hash_key=None,
range_key=None,
attrs={
"Id": {"N": "8"},
"Subs": {"N": "5"},
"Des": {"S": "Some description"},
"KV": {"SS": ["test1", "test2"]},
},
)
row2 = moto.dynamodb.models.Item(
hash_key=None,
range_key=None,
attrs={
"Id": {"N": "8"},
"Subs": {"N": "10"},
"Des": {"S": "A description"},
"KV": {"SS": ["test3", "test4"]},
},
)
# NOT test 1
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"NOT attribute_not_exists(Id)", {}, {}
)
assert filter_expr.expr(row1) is True
# NOT test 2
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"NOT (Id = :v0)", {}, {":v0": {"N": "8"}}
)
assert filter_expr.expr(row1) is False # Id = 8 so should be false
# AND test
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"Id > :v0 AND Subs < :v1", {}, {":v0": {"N": "5"}, ":v1": {"N": "7"}}
)
assert filter_expr.expr(row1) is True
assert filter_expr.expr(row2) is False
# lowercase AND test
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"Id > :v0 and Subs < :v1", {}, {":v0": {"N": "5"}, ":v1": {"N": "7"}}
)
assert filter_expr.expr(row1) is True
assert filter_expr.expr(row2) is False
# OR test
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"Id = :v0 OR Id=:v1", {}, {":v0": {"N": "5"}, ":v1": {"N": "8"}}
)
assert filter_expr.expr(row1) is True
# BETWEEN test
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"Id BETWEEN :v0 AND :v1", {}, {":v0": {"N": "5"}, ":v1": {"N": "10"}}
)
assert filter_expr.expr(row1) is True
# BETWEEN integer test
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"Id BETWEEN :v0 AND :v1", {}, {":v0": {"N": "0"}, ":v1": {"N": "10"}}
)
assert filter_expr.expr(row1) is True
# PAREN test
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"Id = :v0 AND (Subs = :v0 OR Subs = :v1)",
{},
{":v0": {"N": "8"}, ":v1": {"N": "5"}},
)
assert filter_expr.expr(row1) is True
# IN test
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"Id IN (:v0, :v1, :v2)",
{},
{":v0": {"N": "7"}, ":v1": {"N": "8"}, ":v2": {"N": "9"}},
)
assert filter_expr.expr(row1) is True
# attribute function tests (with extra spaces)
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"attribute_exists(Id) AND attribute_not_exists (UnknownAttribute)", {}, {}
)
assert filter_expr.expr(row1) is True
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"attribute_type(Id, :v0)", {}, {":v0": {"S": "N"}}
)
assert filter_expr.expr(row1) is True
# beginswith function test
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"begins_with(Des, :v0)", {}, {":v0": {"S": "Some"}}
)
assert filter_expr.expr(row1) is True
assert filter_expr.expr(row2) is False
# contains function test
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"contains(KV, :v0)", {}, {":v0": {"S": "test1"}}
)
assert filter_expr.expr(row1) is True
assert filter_expr.expr(row2) is False
# size function test
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"size(Des) > size(KV)", {}, {}
)
assert filter_expr.expr(row1) is True
# Expression from @batkuip
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"(#n0 < :v0 AND attribute_not_exists(#n1))",
{"#n0": "Subs", "#n1": "fanout_ts"},
{":v0": {"N": "7"}},
)
assert filter_expr.expr(row1) is True
# Expression from to check contains on string value
filter_expr = moto.dynamodb.comparisons.get_filter_expression(
"contains(#n0, :v0)", {"#n0": "Des"}, {":v0": {"S": "Some"}}
)
assert filter_expr.expr(row1) is True
assert filter_expr.expr(row2) is False
@mock_dynamodb
def test_query_filter():
client = boto3.client("dynamodb", region_name="us-east-1")
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
client.put_item(
TableName="test1",
Item={
"client": {"S": "client1"},
"app": {"S": "app1"},
"nested": {
"M": {
"version": {"S": "version1"},
"contents": {"L": [{"S": "value1"}, {"S": "value2"}]},
}
},
},
)
client.put_item(
TableName="test1",
Item={
"client": {"S": "client1"},
"app": {"S": "app2"},
"nested": {
"M": {
"version": {"S": "version2"},
"contents": {"L": [{"S": "value1"}, {"S": "value2"}]},
}
},
},
)
table = dynamodb.Table("test1")
response = table.query(KeyConditionExpression=Key("client").eq("client1"))
assert response["Count"] == 2
assert response["ScannedCount"] == 2
response = table.query(
KeyConditionExpression=Key("client").eq("client1"),
FilterExpression=Attr("app").eq("app2"),
)
assert response["Count"] == 1
assert response["ScannedCount"] == 2
assert response["Items"][0]["app"] == "app2"
response = table.query(
KeyConditionExpression=Key("client").eq("client1"),
FilterExpression=Attr("app").contains("app"),
)
assert response["Count"] == 2
response = table.query(
KeyConditionExpression=Key("client").eq("client1"),
FilterExpression=Attr("nested.version").contains("version"),
)
assert response["Count"] == 2
response = table.query(
KeyConditionExpression=Key("client").eq("client1"),
FilterExpression=Attr("nested.contents[0]").eq("value1"),
)
assert response["Count"] == 2
@mock_dynamodb
def test_query_filter_overlapping_expression_prefixes():
client = boto3.client("dynamodb", region_name="us-east-1")
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
client.put_item(
TableName="test1",
Item={
"client": {"S": "client1"},
"app": {"S": "app1"},
"nested": {
"M": {
"version": {"S": "version1"},
"contents": {"L": [{"S": "value1"}, {"S": "value2"}]},
}
},
},
)
table = dynamodb.Table("test1")
response = table.query(
KeyConditionExpression=Key("client").eq("client1") & Key("app").eq("app1"),
ProjectionExpression="#1, #10, nested",
ExpressionAttributeNames={"#1": "client", "#10": "app"},
)
assert response["Count"] == 1
assert response["Items"][0] == {
"client": "client1",
"app": "app1",
"nested": {"version": "version1", "contents": ["value1", "value2"]},
}
@mock_dynamodb
def test_scan_filter():
client = boto3.client("dynamodb", region_name="us-east-1")
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
client.put_item(
TableName="test1", Item={"client": {"S": "client1"}, "app": {"S": "app1"}}
)
table = dynamodb.Table("test1")
response = table.scan(FilterExpression=Attr("app").eq("app2"))
assert response["Count"] == 0
response = table.scan(FilterExpression=Attr("app").eq("app1"))
assert response["Count"] == 1
response = table.scan(FilterExpression=Attr("app").ne("app2"))
assert response["Count"] == 1
response = table.scan(FilterExpression=Attr("app").ne("app1"))
assert response["Count"] == 0
@mock_dynamodb
def test_scan_filter2():
client = boto3.client("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "N"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
client.put_item(
TableName="test1", Item={"client": {"S": "client1"}, "app": {"N": "1"}}
)
response = client.scan(
TableName="test1",
Select="ALL_ATTRIBUTES",
FilterExpression="#tb >= :dt",
ExpressionAttributeNames={"#tb": "app"},
ExpressionAttributeValues={":dt": {"N": str(1)}},
)
assert response["Count"] == 1
@mock_dynamodb
def test_scan_filter3():
client = boto3.client("dynamodb", region_name="us-east-1")
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "N"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
client.put_item(
TableName="test1",
Item={"client": {"S": "client1"}, "app": {"N": "1"}, "active": {"BOOL": True}},
)
table = dynamodb.Table("test1")
response = table.scan(FilterExpression=Attr("active").eq(True))
assert response["Count"] == 1
response = table.scan(FilterExpression=Attr("active").ne(True))
assert response["Count"] == 0
response = table.scan(FilterExpression=Attr("active").ne(False))
assert response["Count"] == 1
response = table.scan(FilterExpression=Attr("app").ne(1))
assert response["Count"] == 0
response = table.scan(FilterExpression=Attr("app").ne(2))
assert response["Count"] == 1
@mock_dynamodb
def test_scan_filter4():
client = boto3.client("dynamodb", region_name="us-east-1")
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "N"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
table = dynamodb.Table("test1")
response = table.scan(
FilterExpression=Attr("epoch_ts").lt(7) & Attr("fanout_ts").not_exists()
)
# Just testing
assert response["Count"] == 0
@mock_dynamodb
def test_scan_filter_should_not_return_non_existing_attributes():
table_name = "my-table"
item = {"partitionKey": "pk-2", "my-attr": 42}
# Create table
res = boto3.resource("dynamodb", region_name="us-east-1")
res.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "partitionKey", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "partitionKey", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
table = res.Table(table_name)
# Insert items
table.put_item(Item={"partitionKey": "pk-1"})
table.put_item(Item=item)
# Verify a few operations
# Assert we only find the item that has this attribute
assert table.scan(FilterExpression=Attr("my-attr").lt(43))["Items"] == [item]
assert table.scan(FilterExpression=Attr("my-attr").lte(42))["Items"] == [item]
assert table.scan(FilterExpression=Attr("my-attr").gte(42))["Items"] == [item]
assert table.scan(FilterExpression=Attr("my-attr").gt(41))["Items"] == [item]
# Sanity check that we can't find the item if the FE is wrong
assert table.scan(FilterExpression=Attr("my-attr").gt(43))["Items"] == []
@mock_dynamodb
def test_bad_scan_filter():
client = boto3.client("dynamodb", region_name="us-east-1")
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
table = dynamodb.Table("test1")
# Bad expression
with pytest.raises(ClientError) as exc:
table.scan(FilterExpression="client test")
assert exc.value.response["Error"]["Code"] == "ValidationException"
@mock_dynamodb
def test_scan_with_scanfilter():
table_name = "my-table"
item = {"partitionKey": "pk-2", "my-attr": 42}
client = boto3.client("dynamodb", region_name="us-east-1")
res = boto3.resource("dynamodb", region_name="us-east-1")
res.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "partitionKey", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "partitionKey", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
table = res.Table(table_name)
table.put_item(Item={"partitionKey": "pk-1"})
table.put_item(Item=item)
# ScanFilter: EQ
# The DynamoDB table-resource sends the AttributeValueList in the wrong format
# So this operation never finds any data, in Moto or AWS
table.scan(
ScanFilter={
"my-attr": {"AttributeValueList": [{"N": "42"}], "ComparisonOperator": "EQ"}
}
)
# ScanFilter: EQ
# If we use the boto3-client, we do receive the correct data
items = client.scan(
TableName=table_name,
ScanFilter={
"partitionKey": {
"AttributeValueList": [{"S": "pk-1"}],
"ComparisonOperator": "EQ",
}
},
)["Items"]
assert items == [{"partitionKey": {"S": "pk-1"}}]
# ScanFilter: NONE
# Note that we can use the table-resource here, because we're not using the AttributeValueList
items = table.scan(ScanFilter={"my-attr": {"ComparisonOperator": "NULL"}})["Items"]
assert items == [{"partitionKey": "pk-1"}]
@mock_dynamodb
def test_duplicate_create():
client = boto3.client("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
with pytest.raises(ClientError) as exc:
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
err = exc.value.response["Error"]
assert err["Code"] == "ResourceInUseException"
@mock_dynamodb
def test_delete_table():
client = boto3.client("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
client.delete_table(TableName="test1")
resp = client.list_tables()
assert len(resp["TableNames"]) == 0
with pytest.raises(ClientError) as err:
client.delete_table(TableName="test1")
assert err.value.response["Error"]["Code"] == "ResourceNotFoundException"
@mock_dynamodb
def test_delete_item():
client = boto3.client("dynamodb", region_name="us-east-1")
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
client.put_item(
TableName="test1", Item={"client": {"S": "client1"}, "app": {"S": "app1"}}
)
client.put_item(
TableName="test1", Item={"client": {"S": "client1"}, "app": {"S": "app2"}}
)
table = dynamodb.Table("test1")
response = table.scan()
assert response["Count"] == 2
# Test ReturnValues validation
with pytest.raises(ClientError) as ex:
table.delete_item(
Key={"client": "client1", "app": "app1"}, ReturnValues="ALL_NEW"
)
err = ex.value.response["Error"]
assert err["Code"] == "ValidationException"
assert err["Message"] == "Return values set to invalid value"
# Test deletion and returning old value
response = table.delete_item(
Key={"client": "client1", "app": "app1"}, ReturnValues="ALL_OLD"
)
assert "client" in response["Attributes"]
assert "app" in response["Attributes"]
response = table.scan()
assert response["Count"] == 1
# Test deletion returning nothing
response = table.delete_item(Key={"client": "client1", "app": "app2"})
assert len(response["Attributes"]) == 0
response = table.scan()
assert response["Count"] == 0
@mock_dynamodb
def test_delete_item_error():
# Setup
client = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
BillingMode="PAY_PER_REQUEST",
)
table = client.Table("test1")
table.delete()
# Execute
with pytest.raises(ClientError) as ex:
table.delete_item(
Key={"client": "client1", "app": "app1"},
)
# Verify
err = ex.value.response["Error"]
assert err["Code"] == "ResourceNotFoundException"
assert err["Message"] == "Requested resource not found"
@mock_dynamodb
def test_describe_limits():
client = boto3.client("dynamodb", region_name="eu-central-1")
resp = client.describe_limits()
assert resp["AccountMaxReadCapacityUnits"] == 20000
assert resp["AccountMaxWriteCapacityUnits"] == 20000
assert resp["TableMaxWriteCapacityUnits"] == 10000
assert resp["TableMaxReadCapacityUnits"] == 10000
@mock_dynamodb
def test_set_ttl():
client = boto3.client("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
client.update_time_to_live(
TableName="test1",
TimeToLiveSpecification={"Enabled": True, "AttributeName": "expire"},
)
resp = client.describe_time_to_live(TableName="test1")
assert resp["TimeToLiveDescription"]["TimeToLiveStatus"] == "ENABLED"
assert resp["TimeToLiveDescription"]["AttributeName"] == "expire"
client.update_time_to_live(
TableName="test1",
TimeToLiveSpecification={"Enabled": False, "AttributeName": "expire"},
)
resp = client.describe_time_to_live(TableName="test1")
assert resp["TimeToLiveDescription"]["TimeToLiveStatus"] == "DISABLED"
@mock_dynamodb
def test_describe_continuous_backups():
# given
client = boto3.client("dynamodb", region_name="us-east-1")
table_name = client.create_table(
TableName="test",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
BillingMode="PAY_PER_REQUEST",
)["TableDescription"]["TableName"]
# when
response = client.describe_continuous_backups(TableName=table_name)
# then
assert response["ContinuousBackupsDescription"] == {
"ContinuousBackupsStatus": "ENABLED",
"PointInTimeRecoveryDescription": {"PointInTimeRecoveryStatus": "DISABLED"},
}
@mock_dynamodb
def test_describe_continuous_backups_errors():
# given
client = boto3.client("dynamodb", region_name="us-east-1")
# when
with pytest.raises(ClientError) as e:
client.describe_continuous_backups(TableName="not-existing-table")
# then
ex = e.value
assert ex.operation_name == "DescribeContinuousBackups"
assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert ex.response["Error"]["Code"] == "TableNotFoundException"
assert ex.response["Error"]["Message"] == "Table not found: not-existing-table"
@mock_dynamodb
def test_update_continuous_backups():
# given
client = boto3.client("dynamodb", region_name="us-east-1")
table_name = client.create_table(
TableName="test",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
BillingMode="PAY_PER_REQUEST",
)["TableDescription"]["TableName"]
# when
response = client.update_continuous_backups(
TableName=table_name,
PointInTimeRecoverySpecification={"PointInTimeRecoveryEnabled": True},
)
# then
assert (
response["ContinuousBackupsDescription"]["ContinuousBackupsStatus"] == "ENABLED"
)
point_in_time = response["ContinuousBackupsDescription"][
"PointInTimeRecoveryDescription"
]
earliest_datetime = point_in_time["EarliestRestorableDateTime"]
assert isinstance(earliest_datetime, datetime)
latest_datetime = point_in_time["LatestRestorableDateTime"]
assert isinstance(latest_datetime, datetime)
assert point_in_time["PointInTimeRecoveryStatus"] == "ENABLED"
# when
# a second update should not change anything
response = client.update_continuous_backups(
TableName=table_name,
PointInTimeRecoverySpecification={"PointInTimeRecoveryEnabled": True},
)
# then
assert (
response["ContinuousBackupsDescription"]["ContinuousBackupsStatus"] == "ENABLED"
)
point_in_time = response["ContinuousBackupsDescription"][
"PointInTimeRecoveryDescription"
]
assert point_in_time["EarliestRestorableDateTime"] == earliest_datetime
assert point_in_time["LatestRestorableDateTime"] == latest_datetime
assert point_in_time["PointInTimeRecoveryStatus"] == "ENABLED"
# when
response = client.update_continuous_backups(
TableName=table_name,
PointInTimeRecoverySpecification={"PointInTimeRecoveryEnabled": False},
)
# then
assert response["ContinuousBackupsDescription"] == {
"ContinuousBackupsStatus": "ENABLED",
"PointInTimeRecoveryDescription": {"PointInTimeRecoveryStatus": "DISABLED"},
}
@mock_dynamodb
def test_update_continuous_backups_errors():
# given
client = boto3.client("dynamodb", region_name="us-east-1")
# when
with pytest.raises(ClientError) as e:
client.update_continuous_backups(
TableName="not-existing-table",
PointInTimeRecoverySpecification={"PointInTimeRecoveryEnabled": True},
)
# then
ex = e.value
assert ex.operation_name == "UpdateContinuousBackups"
assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert ex.response["Error"]["Code"] == "TableNotFoundException"
assert ex.response["Error"]["Message"] == "Table not found: not-existing-table"
# https://github.com/getmoto/moto/issues/1043
@mock_dynamodb
def test_query_missing_expr_names():
client = boto3.client("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
)
client.put_item(
TableName="test1", Item={"client": {"S": "test1"}, "app": {"S": "test1"}}
)
client.put_item(
TableName="test1", Item={"client": {"S": "test2"}, "app": {"S": "test2"}}
)
resp = client.query(
TableName="test1",
KeyConditionExpression="client=:client",
ExpressionAttributeValues={":client": {"S": "test1"}},
)
assert resp["Count"] == 1
assert resp["Items"][0]["client"]["S"] == "test1"
# https://github.com/getmoto/moto/issues/2328
@mock_dynamodb
def test_update_item_with_list():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
dynamodb.create_table(
TableName="Table",
KeySchema=[{"AttributeName": "key", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "key", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
)
table = dynamodb.Table("Table")
table.update_item(
Key={"key": "the-key"},
AttributeUpdates={"list": {"Value": [1, 2], "Action": "PUT"}},
)
resp = table.get_item(Key={"key": "the-key"})
assert resp["Item"] == {"key": "the-key", "list": [1, 2]}
# https://github.com/getmoto/moto/issues/2328
@mock_dynamodb
def test_update_item_with_no_action_passed_with_list():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
dynamodb.create_table(
TableName="Table",
KeySchema=[{"AttributeName": "key", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "key", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
)
table = dynamodb.Table("Table")
table.update_item(
Key={"key": "the-key"},
# Do not pass 'Action' key, in order to check that the
# parameter's default value will be used.
AttributeUpdates={"list": {"Value": [1, 2]}},
)
resp = table.get_item(Key={"key": "the-key"})
assert resp["Item"] == {"key": "the-key", "list": [1, 2]}
# https://github.com/getmoto/moto/issues/1342
@mock_dynamodb
def test_update_item_on_map():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
client = boto3.client("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
dynamodb.create_table(
TableName="users",
KeySchema=[
{"AttributeName": "forum_name", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "forum_name", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.put_item(
Item={
"forum_name": "the-key",
"subject": "123",
"body": {"nested": {"data": "test"}},
}
)
resp = table.scan()
assert resp["Items"][0]["body"] == {"nested": {"data": "test"}}
# Nonexistent nested attributes are supported for existing top-level attributes.
table.update_item(
Key={"forum_name": "the-key", "subject": "123"},
UpdateExpression="SET body.#nested.#data = :tb",
ExpressionAttributeNames={"#nested": "nested", "#data": "data"},
ExpressionAttributeValues={":tb": "new_value"},
)
# Running this against AWS DDB gives an exception so make sure it also fails.:
with pytest.raises(client.exceptions.ClientError):
# botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the UpdateItem
# operation: The document path provided in the update expression is invalid for update
table.update_item(
Key={"forum_name": "the-key", "subject": "123"},
UpdateExpression="SET body.#nested.#nonexistentnested.#data = :tb2",
ExpressionAttributeNames={
"#nested": "nested",
"#nonexistentnested": "nonexistentnested",
"#data": "data",
},
ExpressionAttributeValues={":tb2": "other_value"},
)
table.update_item(
Key={"forum_name": "the-key", "subject": "123"},
UpdateExpression="SET body.#nested.#nonexistentnested = :tb2",
ExpressionAttributeNames={
"#nested": "nested",
"#nonexistentnested": "nonexistentnested",
},
ExpressionAttributeValues={":tb2": {"data": "other_value"}},
)
resp = table.scan()
assert resp["Items"][0]["body"] == {
"nested": {"data": "new_value", "nonexistentnested": {"data": "other_value"}}
}
# Test nested value for a nonexistent attribute throws a ClientError.
with pytest.raises(client.exceptions.ClientError):
table.update_item(
Key={"forum_name": "the-key", "subject": "123"},
UpdateExpression="SET nonexistent.#nested = :tb",
ExpressionAttributeNames={"#nested": "nested"},
ExpressionAttributeValues={":tb": "new_value"},
)
# https://github.com/getmoto/moto/issues/1358
@mock_dynamodb
def test_update_if_not_exists():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
dynamodb.create_table(
TableName="users",
KeySchema=[
{"AttributeName": "forum_name", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "forum_name", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.put_item(Item={"forum_name": "the-key", "subject": "123"})
table.update_item(
Key={"forum_name": "the-key", "subject": "123"},
# if_not_exists without space
UpdateExpression="SET created_at=if_not_exists(created_at,:created_at)",
ExpressionAttributeValues={":created_at": 123},
)
resp = table.scan()
assert resp["Items"][0]["created_at"] == 123
table.update_item(
Key={"forum_name": "the-key", "subject": "123"},
# if_not_exists with space
UpdateExpression="SET created_at = if_not_exists (created_at, :created_at)",
ExpressionAttributeValues={":created_at": 456},
)
resp = table.scan()
# Still the original value
assert resp["Items"][0]["created_at"] == 123
# https://github.com/getmoto/moto/issues/1937
@mock_dynamodb
def test_update_return_attributes():
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="moto-test",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
)
def update(col, to, rv):
return dynamodb.update_item(
TableName="moto-test",
Key={"id": {"S": "foo"}},
AttributeUpdates={col: {"Value": {"S": to}, "Action": "PUT"}},
ReturnValues=rv,
)
r = update("col1", "val1", "ALL_NEW")
assert r["Attributes"] == {"id": {"S": "foo"}, "col1": {"S": "val1"}}
r = update("col1", "val2", "ALL_OLD")
assert r["Attributes"] == {"id": {"S": "foo"}, "col1": {"S": "val1"}}
r = update("col2", "val3", "UPDATED_NEW")
assert r["Attributes"] == {"col2": {"S": "val3"}}
r = update("col2", "val4", "UPDATED_OLD")
assert r["Attributes"] == {"col2": {"S": "val3"}}
r = update("col1", "val5", "NONE")
assert r["Attributes"] == {}
with pytest.raises(ClientError) as ex:
update("col1", "val6", "WRONG")
err = ex.value.response["Error"]
assert err["Code"] == "ValidationException"
assert err["Message"] == "Return values set to invalid value"
# https://github.com/getmoto/moto/issues/3448
@mock_dynamodb
def test_update_return_updated_new_attributes_when_same():
dynamo_client = boto3.resource("dynamodb", region_name="us-east-1")
dynamo_client.create_table(
TableName="moto-test",
KeySchema=[{"AttributeName": "HashKey1", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "HashKey1", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
)
dynamodb_table = dynamo_client.Table("moto-test")
dynamodb_table.put_item(
Item={"HashKey1": "HashKeyValue1", "listValuedAttribute1": ["a", "b"]}
)
def update(col, to, rv):
return dynamodb_table.update_item(
TableName="moto-test",
Key={"HashKey1": "HashKeyValue1"},
UpdateExpression="SET listValuedAttribute1=:" + col,
ExpressionAttributeValues={":" + col: to},
ReturnValues=rv,
)
r = update("a", ["a", "c"], "UPDATED_NEW")
assert r["Attributes"] == {"listValuedAttribute1": ["a", "c"]}
r = update("a", {"a", "c"}, "UPDATED_NEW")
assert r["Attributes"] == {"listValuedAttribute1": {"a", "c"}}
r = update("a", {1, 2}, "UPDATED_NEW")
assert r["Attributes"] == {"listValuedAttribute1": {1, 2}}
with pytest.raises(ClientError) as ex:
update("a", ["a", "c"], "WRONG")
err = ex.value.response["Error"]
assert err["Code"] == "ValidationException"
assert err["Message"] == "Return values set to invalid value"
@mock_dynamodb
def test_put_return_attributes():
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="moto-test",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
)
r = dynamodb.put_item(
TableName="moto-test",
Item={"id": {"S": "foo"}, "col1": {"S": "val1"}},
ReturnValues="NONE",
)
assert "Attributes" not in r
r = dynamodb.put_item(
TableName="moto-test",
Item={"id": {"S": "foo"}, "col1": {"S": "val2"}},
ReturnValues="ALL_OLD",
)
assert r["Attributes"] == {"id": {"S": "foo"}, "col1": {"S": "val1"}}
with pytest.raises(ClientError) as ex:
dynamodb.put_item(
TableName="moto-test",
Item={"id": {"S": "foo"}, "col1": {"S": "val3"}},
ReturnValues="ALL_NEW",
)
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert ex.value.response["Error"]["Message"] == "Return values set to invalid value"
@mock_dynamodb
def test_query_global_secondary_index_when_created_via_update_table_resource():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
dynamodb.create_table(
TableName="users",
KeySchema=[{"AttributeName": "user_id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "user_id", "AttributeType": "N"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("users")
table.update(
AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
GlobalSecondaryIndexUpdates=[
{
"Create": {
"IndexName": "forum_name_index",
"KeySchema": [{"AttributeName": "forum_name", "KeyType": "HASH"}],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 5,
"WriteCapacityUnits": 5,
},
}
}
],
)
next_user_id = 1
for my_forum_name in ["cats", "dogs"]:
for my_subject in [
"my pet is the cutest",
"wow look at what my pet did",
"don't you love my pet?",
]:
table.put_item(
Item={
"user_id": next_user_id,
"forum_name": my_forum_name,
"subject": my_subject,
}
)
next_user_id += 1
# get all the cat users
forum_only_query_response = table.query(
IndexName="forum_name_index",
Select="ALL_ATTRIBUTES",
KeyConditionExpression=Key("forum_name").eq("cats"),
)
forum_only_items = forum_only_query_response["Items"]
assert len(forum_only_items) == 3
for item in forum_only_items:
assert item["forum_name"] == "cats"
# query all cat users with a particular subject
forum_and_subject_query_results = table.query(
IndexName="forum_name_index",
Select="ALL_ATTRIBUTES",
KeyConditionExpression=Key("forum_name").eq("cats"),
FilterExpression=Attr("subject").eq("my pet is the cutest"),
)
forum_and_subject_items = forum_and_subject_query_results["Items"]
assert len(forum_and_subject_items) == 1
assert forum_and_subject_items[0] == {
"user_id": Decimal("1"),
"forum_name": "cats",
"subject": "my pet is the cutest",
}
@mock_dynamodb
def test_query_gsi_with_range_key():
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "gsi_hash_key", "AttributeType": "S"},
{"AttributeName": "gsi_range_key", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
GlobalSecondaryIndexes=[
{
"IndexName": "test_gsi",
"KeySchema": [
{"AttributeName": "gsi_hash_key", "KeyType": "HASH"},
{"AttributeName": "gsi_range_key", "KeyType": "RANGE"},
],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1,
},
}
],
)
dynamodb.put_item(
TableName="test",
Item={
"id": {"S": "test1"},
"gsi_hash_key": {"S": "key1"},
"gsi_range_key": {"S": "range1"},
},
)
dynamodb.put_item(
TableName="test", Item={"id": {"S": "test2"}, "gsi_hash_key": {"S": "key1"}}
)
res = dynamodb.query(
TableName="test",
IndexName="test_gsi",
KeyConditionExpression="gsi_hash_key = :gsi_hash_key and gsi_range_key = :gsi_range_key",
ExpressionAttributeValues={
":gsi_hash_key": {"S": "key1"},
":gsi_range_key": {"S": "range1"},
},
)
assert res["Count"] == 1
assert res["Items"][0] == {
"id": {"S": "test1"},
"gsi_hash_key": {"S": "key1"},
"gsi_range_key": {"S": "range1"},
}
@mock_dynamodb
def test_scan_by_non_exists_index():
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "gsi_col", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
GlobalSecondaryIndexes=[
{
"IndexName": "test_gsi",
"KeySchema": [{"AttributeName": "gsi_col", "KeyType": "HASH"}],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1,
},
}
],
)
with pytest.raises(ClientError) as ex:
dynamodb.scan(TableName="test", IndexName="non_exists_index")
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert (
ex.value.response["Error"]["Message"]
== "The table does not have the specified index: non_exists_index"
)
@mock_dynamodb
def test_query_by_non_exists_index():
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "gsi_col", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
GlobalSecondaryIndexes=[
{
"IndexName": "test_gsi",
"KeySchema": [{"AttributeName": "gsi_col", "KeyType": "HASH"}],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1,
},
}
],
)
with pytest.raises(ClientError) as ex:
dynamodb.query(
TableName="test",
IndexName="non_exists_index",
KeyConditionExpression="CarModel=M",
)
assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
assert (
ex.value.response["Error"]["Message"]
== "Invalid index: non_exists_index for table: test. Available indexes are: test_gsi"
)
@mock_dynamodb
def test_index_with_unknown_attributes_should_fail():
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
expected_exception = (
"Some index key attributes are not defined in AttributeDefinitions."
)
with pytest.raises(ClientError) as ex:
dynamodb.create_table(
AttributeDefinitions=[
{"AttributeName": "customer_nr", "AttributeType": "S"},
{"AttributeName": "last_name", "AttributeType": "S"},
],
TableName="table_with_missing_attribute_definitions",
KeySchema=[
{"AttributeName": "customer_nr", "KeyType": "HASH"},
{"AttributeName": "last_name", "KeyType": "RANGE"},
],
LocalSecondaryIndexes=[
{
"IndexName": "indexthataddsanadditionalattribute",
"KeySchema": [
{"AttributeName": "customer_nr", "KeyType": "HASH"},
{"AttributeName": "postcode", "KeyType": "RANGE"},
],
"Projection": {"ProjectionType": "ALL"},
}
],
BillingMode="PAY_PER_REQUEST",
)
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert expected_exception in ex.value.response["Error"]["Message"]
@mock_dynamodb
def test_update_list_index__set_existing_index():
table_name = "test_list_index_access"
client = create_table_with_list(table_name)
client.put_item(
TableName=table_name,
Item={
"id": {"S": "foo"},
"itemlist": {"L": [{"S": "bar1"}, {"S": "bar2"}, {"S": "bar3"}]},
},
)
client.update_item(
TableName=table_name,
Key={"id": {"S": "foo"}},
UpdateExpression="set itemlist[1]=:Item",
ExpressionAttributeValues={":Item": {"S": "bar2_update"}},
)
#
result = client.get_item(TableName=table_name, Key={"id": {"S": "foo"}})["Item"]
assert result["id"] == {"S": "foo"}
assert result["itemlist"] == {
"L": [{"S": "bar1"}, {"S": "bar2_update"}, {"S": "bar3"}]
}
@mock_dynamodb
def test_update_list_index__set_existing_nested_index():
table_name = "test_list_index_access"
client = create_table_with_list(table_name)
client.put_item(
TableName=table_name,
Item={
"id": {"S": "foo2"},
"itemmap": {
"M": {"itemlist": {"L": [{"S": "bar1"}, {"S": "bar2"}, {"S": "bar3"}]}}
},
},
)
client.update_item(
TableName=table_name,
Key={"id": {"S": "foo2"}},
UpdateExpression="set itemmap.itemlist[1]=:Item",
ExpressionAttributeValues={":Item": {"S": "bar2_update"}},
)
#
result = client.get_item(TableName=table_name, Key={"id": {"S": "foo2"}})["Item"]
assert result["id"] == {"S": "foo2"}
assert result["itemmap"]["M"]["itemlist"]["L"] == [
{"S": "bar1"},
{"S": "bar2_update"},
{"S": "bar3"},
]
@mock_dynamodb
def test_update_list_index__set_index_out_of_range():
table_name = "test_list_index_access"
client = create_table_with_list(table_name)
client.put_item(
TableName=table_name,
Item={
"id": {"S": "foo"},
"itemlist": {"L": [{"S": "bar1"}, {"S": "bar2"}, {"S": "bar3"}]},
},
)
client.update_item(
TableName=table_name,
Key={"id": {"S": "foo"}},
UpdateExpression="set itemlist[10]=:Item",
ExpressionAttributeValues={":Item": {"S": "bar10"}},
)
#
result = client.get_item(TableName=table_name, Key={"id": {"S": "foo"}})["Item"]
assert result["id"] == {"S": "foo"}
assert result["itemlist"] == {
"L": [{"S": "bar1"}, {"S": "bar2"}, {"S": "bar3"}, {"S": "bar10"}]
}
@mock_dynamodb
def test_update_list_index__set_nested_index_out_of_range():
table_name = "test_list_index_access"
client = create_table_with_list(table_name)
client.put_item(
TableName=table_name,
Item={
"id": {"S": "foo2"},
"itemmap": {
"M": {"itemlist": {"L": [{"S": "bar1"}, {"S": "bar2"}, {"S": "bar3"}]}}
},
},
)
client.update_item(
TableName=table_name,
Key={"id": {"S": "foo2"}},
UpdateExpression="set itemmap.itemlist[10]=:Item",
ExpressionAttributeValues={":Item": {"S": "bar10"}},
)
#
result = client.get_item(TableName=table_name, Key={"id": {"S": "foo2"}})["Item"]
assert result["id"] == {"S": "foo2"}
assert result["itemmap"]["M"]["itemlist"]["L"] == [
{"S": "bar1"},
{"S": "bar2"},
{"S": "bar3"},
{"S": "bar10"},
]
@mock_dynamodb
def test_update_list_index__set_double_nested_index():
table_name = "test_list_index_access"
client = create_table_with_list(table_name)
client.put_item(
TableName=table_name,
Item={
"id": {"S": "foo2"},
"itemmap": {
"M": {
"itemlist": {
"L": [
{"M": {"foo": {"S": "bar11"}, "foos": {"S": "bar12"}}},
{"M": {"foo": {"S": "bar21"}, "foos": {"S": "bar21"}}},
]
}
}
},
},
)
client.update_item(
TableName=table_name,
Key={"id": {"S": "foo2"}},
UpdateExpression="set itemmap.itemlist[1].foos=:Item",
ExpressionAttributeValues={":Item": {"S": "bar22"}},
)
#
result = client.get_item(TableName=table_name, Key={"id": {"S": "foo2"}})["Item"]
assert result["id"] == {"S": "foo2"}
assert len(result["itemmap"]["M"]["itemlist"]["L"]) == 2
assert result["itemmap"]["M"]["itemlist"]["L"][0] == {
"M": {"foo": {"S": "bar11"}, "foos": {"S": "bar12"}}
} # unchanged
assert result["itemmap"]["M"]["itemlist"]["L"][1] == {
"M": {"foo": {"S": "bar21"}, "foos": {"S": "bar22"}}
} # updated
@mock_dynamodb
def test_update_list_index__set_index_of_a_string():
table_name = "test_list_index_access"
client = create_table_with_list(table_name)
client.put_item(
TableName=table_name, Item={"id": {"S": "foo2"}, "itemstr": {"S": "somestring"}}
)
with pytest.raises(ClientError) as ex:
client.update_item(
TableName=table_name,
Key={"id": {"S": "foo2"}},
UpdateExpression="set itemstr[1]=:Item",
ExpressionAttributeValues={":Item": {"S": "string_update"}},
)
client.get_item(TableName=table_name, Key={"id": {"S": "foo2"}})["Item"]
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert (
ex.value.response["Error"]["Message"]
== "The document path provided in the update expression is invalid for update"
)
@mock_dynamodb
def test_remove_top_level_attribute():
table_name = "test_remove"
client = create_table_with_list(table_name)
client.put_item(
TableName=table_name, Item={"id": {"S": "foo"}, "item": {"S": "bar"}}
)
client.update_item(
TableName=table_name,
Key={"id": {"S": "foo"}},
UpdateExpression="REMOVE #i",
ExpressionAttributeNames={"#i": "item"},
)
#
result = client.get_item(TableName=table_name, Key={"id": {"S": "foo"}})["Item"]
assert result == {"id": {"S": "foo"}}
@mock_dynamodb
def test_remove_top_level_attribute_non_existent():
"""
Remove statements do not require attribute to exist they silently pass
"""
table_name = "test_remove"
client = create_table_with_list(table_name)
ddb_item = {"id": {"S": "foo"}, "item": {"S": "bar"}}
client.put_item(TableName=table_name, Item=ddb_item)
client.update_item(
TableName=table_name,
Key={"id": {"S": "foo"}},
UpdateExpression="REMOVE non_existent_attribute",
ExpressionAttributeNames={"#i": "item"},
)
result = client.get_item(TableName=table_name, Key={"id": {"S": "foo"}})["Item"]
assert result == ddb_item
@mock_dynamodb
def test_remove_list_index__remove_existing_index():
table_name = "test_list_index_access"
client = create_table_with_list(table_name)
client.put_item(
TableName=table_name,
Item={
"id": {"S": "foo"},
"itemlist": {"L": [{"S": "bar1"}, {"S": "bar2"}, {"S": "bar3"}]},
},
)
client.update_item(
TableName=table_name,
Key={"id": {"S": "foo"}},
UpdateExpression="REMOVE itemlist[1]",
)
#
result = client.get_item(TableName=table_name, Key={"id": {"S": "foo"}})["Item"]
assert result["id"] == {"S": "foo"}
assert result["itemlist"] == {"L": [{"S": "bar1"}, {"S": "bar3"}]}
@mock_dynamodb
def test_remove_list_index__remove_multiple_indexes():
table_name = "remove-test"
create_table_with_list(table_name)
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
table = dynamodb.Table(table_name)
table.put_item(
Item={
"id": "woop",
"bla": ["1", "2", "3", "4", "5"],
},
)
table.update_item(
Key={"id": "woop"}, UpdateExpression="REMOVE bla[0], bla[1], bla[2]"
)
result = table.get_item(Key={"id": "woop"})
item = result["Item"]
assert item["bla"] == ["4", "5"]
@mock_dynamodb
def test_remove_list_index__remove_existing_nested_index():
table_name = "test_list_index_access"
client = create_table_with_list(table_name)
client.put_item(
TableName=table_name,
Item={
"id": {"S": "foo2"},
"itemmap": {"M": {"itemlist": {"L": [{"S": "bar1"}, {"S": "bar2"}]}}},
},
)
client.update_item(
TableName=table_name,
Key={"id": {"S": "foo2"}},
UpdateExpression="REMOVE itemmap.itemlist[1]",
)
#
result = client.get_item(TableName=table_name, Key={"id": {"S": "foo2"}})["Item"]
assert result["id"] == {"S": "foo2"}
assert result["itemmap"]["M"]["itemlist"]["L"] == [{"S": "bar1"}]
@mock_dynamodb
def test_remove_list_index__remove_existing_double_nested_index():
table_name = "test_list_index_access"
client = create_table_with_list(table_name)
client.put_item(
TableName=table_name,
Item={
"id": {"S": "foo2"},
"itemmap": {
"M": {
"itemlist": {
"L": [
{"M": {"foo00": {"S": "bar1"}, "foo01": {"S": "bar2"}}},
{"M": {"foo10": {"S": "bar1"}, "foo11": {"S": "bar2"}}},
]
}
}
},
},
)
client.update_item(
TableName=table_name,
Key={"id": {"S": "foo2"}},
UpdateExpression="REMOVE itemmap.itemlist[1].foo10",
)
#
result = client.get_item(TableName=table_name, Key={"id": {"S": "foo2"}})["Item"]
assert result["id"] == {"S": "foo2"}
# untouched
assert result["itemmap"]["M"]["itemlist"]["L"][0]["M"] == {
"foo00": {"S": "bar1"},
"foo01": {"S": "bar2"},
}
# changed
assert result["itemmap"]["M"]["itemlist"]["L"][1]["M"] == {"foo11": {"S": "bar2"}}
@mock_dynamodb
def test_remove_list_index__remove_index_out_of_range():
table_name = "test_list_index_access"
client = create_table_with_list(table_name)
client.put_item(
TableName=table_name,
Item={
"id": {"S": "foo"},
"itemlist": {"L": [{"S": "bar1"}, {"S": "bar2"}, {"S": "bar3"}]},
},
)
client.update_item(
TableName=table_name,
Key={"id": {"S": "foo"}},
UpdateExpression="REMOVE itemlist[10]",
)
#
result = client.get_item(TableName=table_name, Key={"id": {"S": "foo"}})["Item"]
assert result["id"] == {"S": "foo"}
assert result["itemlist"] == {"L": [{"S": "bar1"}, {"S": "bar2"}, {"S": "bar3"}]}
def create_table_with_list(table_name):
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
return client
@mock_dynamodb
def test_sorted_query_with_numerical_sort_key():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="CarCollection",
KeySchema=[
{"AttributeName": "CarModel", "KeyType": "HASH"},
{"AttributeName": "CarPrice", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "CarModel", "AttributeType": "S"},
{"AttributeName": "CarPrice", "AttributeType": "N"},
],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
)
def create_item(price):
return {"CarModel": "M", "CarPrice": price}
table = dynamodb.Table("CarCollection")
items = list(map(create_item, [2, 1, 10, 3]))
for item in items:
table.put_item(Item=item)
response = table.query(KeyConditionExpression=Key("CarModel").eq("M"))
response_items = response["Items"]
assert len(items) == len(response_items)
assert all(isinstance(item["CarPrice"], Decimal) for item in response_items)
response_prices = [item["CarPrice"] for item in response_items]
expected_prices = [Decimal(item["CarPrice"]) for item in items]
expected_prices.sort()
assert (
expected_prices == response_prices
), "result items are not sorted by numerical value"
# https://github.com/getmoto/moto/issues/1874
@mock_dynamodb
def test_item_size_is_under_400KB():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
client = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="moto-test",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
)
table = dynamodb.Table("moto-test")
large_item = "x" * 410 * 1000
assert_failure_due_to_item_size(
func=client.put_item,
TableName="moto-test",
Item={"id": {"S": "foo"}, "cont": {"S": large_item}},
)
assert_failure_due_to_item_size(
func=table.put_item, Item={"id": "bar", "cont": large_item}
)
assert_failure_due_to_item_size_to_update(
func=client.update_item,
TableName="moto-test",
Key={"id": {"S": "foo2"}},
UpdateExpression="set cont=:Item",
ExpressionAttributeValues={":Item": {"S": large_item}},
)
# Assert op fails when updating a nested item
assert_failure_due_to_item_size(
func=table.put_item, Item={"id": "bar", "itemlist": [{"cont": large_item}]}
)
assert_failure_due_to_item_size(
func=client.put_item,
TableName="moto-test",
Item={
"id": {"S": "foo"},
"itemlist": {"L": [{"M": {"item1": {"S": large_item}}}]},
},
)
def assert_failure_due_to_item_size(func, **kwargs):
with pytest.raises(ClientError) as ex:
func(**kwargs)
err = ex.value.response["Error"]
assert err["Code"] == "ValidationException"
assert err["Message"] == "Item size has exceeded the maximum allowed size"
def assert_failure_due_to_item_size_to_update(func, **kwargs):
with pytest.raises(ClientError) as ex:
func(**kwargs)
err = ex.value.response["Error"]
assert err["Code"] == "ValidationException"
assert err["Message"] == "Item size to update has exceeded the maximum allowed size"
@mock_dynamodb
def test_update_supports_complex_expression_attribute_values():
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
AttributeDefinitions=[{"AttributeName": "SHA256", "AttributeType": "S"}],
TableName="TestTable",
KeySchema=[{"AttributeName": "SHA256", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
client.update_item(
TableName="TestTable",
Key={"SHA256": {"S": "sha-of-file"}},
UpdateExpression=(
"SET MD5 = :md5," "MyStringSet = :string_set," "MyMap = :map"
),
ExpressionAttributeValues={
":md5": {"S": "md5-of-file"},
":string_set": {"SS": ["string1", "string2"]},
":map": {"M": {"EntryKey": {"SS": ["thing1", "thing2"]}}},
},
)
result = client.get_item(
TableName="TestTable", Key={"SHA256": {"S": "sha-of-file"}}
)["Item"]
assert result == {
"MyStringSet": {"SS": ["string1", "string2"]},
"MyMap": {"M": {"EntryKey": {"SS": ["thing1", "thing2"]}}},
"SHA256": {"S": "sha-of-file"},
"MD5": {"S": "md5-of-file"},
}
@mock_dynamodb
def test_update_supports_list_append():
# Verify whether the list_append operation works as expected
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
AttributeDefinitions=[{"AttributeName": "SHA256", "AttributeType": "S"}],
TableName="TestTable",
KeySchema=[{"AttributeName": "SHA256", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
client.put_item(
TableName="TestTable",
Item={"SHA256": {"S": "sha-of-file"}, "crontab": {"L": [{"S": "bar1"}]}},
)
# Update item using list_append expression
updated_item = client.update_item(
TableName="TestTable",
Key={"SHA256": {"S": "sha-of-file"}},
UpdateExpression="SET crontab = list_append(crontab, :i)",
ExpressionAttributeValues={":i": {"L": [{"S": "bar2"}]}},
ReturnValues="UPDATED_NEW",
)
# Verify updated item is correct
assert updated_item["Attributes"] == {
"crontab": {"L": [{"S": "bar1"}, {"S": "bar2"}]}
}
# Verify item is appended to the existing list
result = client.get_item(
TableName="TestTable", Key={"SHA256": {"S": "sha-of-file"}}
)["Item"]
assert result == {
"SHA256": {"S": "sha-of-file"},
"crontab": {"L": [{"S": "bar1"}, {"S": "bar2"}]},
}
@mock_dynamodb
def test_update_supports_nested_list_append():
# Verify whether we can append a list that's inside a map
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
TableName="TestTable",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
client.put_item(
TableName="TestTable",
Item={
"id": {"S": "nested_list_append"},
"a": {"M": {"b": {"L": [{"S": "bar1"}]}}},
},
)
# Update item using list_append expression
updated_item = client.update_item(
TableName="TestTable",
Key={"id": {"S": "nested_list_append"}},
UpdateExpression="SET a.#b = list_append(a.#b, :i)",
ExpressionAttributeValues={":i": {"L": [{"S": "bar2"}]}},
ExpressionAttributeNames={"#b": "b"},
ReturnValues="UPDATED_NEW",
)
# Verify updated item is correct
assert updated_item["Attributes"] == {
"a": {"M": {"b": {"L": [{"S": "bar1"}, {"S": "bar2"}]}}}
}
result = client.get_item(
TableName="TestTable", Key={"id": {"S": "nested_list_append"}}
)["Item"]
assert result == {
"id": {"S": "nested_list_append"},
"a": {"M": {"b": {"L": [{"S": "bar1"}, {"S": "bar2"}]}}},
}
@mock_dynamodb
def test_update_supports_multiple_levels_nested_list_append():
# Verify whether we can append a list that's inside a map that's inside a map (Inception!)
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
TableName="TestTable",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
client.put_item(
TableName="TestTable",
Item={
"id": {"S": "nested_list_append"},
"a": {"M": {"b": {"M": {"c": {"L": [{"S": "bar1"}]}}}}},
},
)
# Update item using list_append expression
updated_item = client.update_item(
TableName="TestTable",
Key={"id": {"S": "nested_list_append"}},
UpdateExpression="SET a.#b.c = list_append(a.#b.#c, :i)",
ExpressionAttributeValues={":i": {"L": [{"S": "bar2"}]}},
ExpressionAttributeNames={"#b": "b", "#c": "c"},
ReturnValues="UPDATED_NEW",
)
# Verify updated item is correct
assert updated_item["Attributes"] == {
"a": {"M": {"b": {"M": {"c": {"L": [{"S": "bar1"}, {"S": "bar2"}]}}}}}
}
# Verify item is appended to the existing list
result = client.get_item(
TableName="TestTable", Key={"id": {"S": "nested_list_append"}}
)["Item"]
assert result == {
"id": {"S": "nested_list_append"},
"a": {"M": {"b": {"M": {"c": {"L": [{"S": "bar1"}, {"S": "bar2"}]}}}}},
}
@mock_dynamodb
def test_update_supports_nested_list_append_onto_another_list():
# Verify whether we can take the contents of one list, and use that to fill another list
# Note that the contents of the other list is completely overwritten
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
TableName="TestTable",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
client.put_item(
TableName="TestTable",
Item={
"id": {"S": "list_append_another"},
"a": {"M": {"b": {"L": [{"S": "bar1"}]}, "c": {"L": [{"S": "car1"}]}}},
},
)
# Update item using list_append expression
updated_item = client.update_item(
TableName="TestTable",
Key={"id": {"S": "list_append_another"}},
UpdateExpression="SET a.#c = list_append(a.#b, :i)",
ExpressionAttributeValues={":i": {"L": [{"S": "bar2"}]}},
ExpressionAttributeNames={"#b": "b", "#c": "c"},
ReturnValues="UPDATED_NEW",
)
# Verify updated item is correct
assert updated_item["Attributes"] == {
"a": {"M": {"c": {"L": [{"S": "bar1"}, {"S": "bar2"}]}}}
}
# Verify item is appended to the existing list
result = client.get_item(
TableName="TestTable", Key={"id": {"S": "list_append_another"}}
)["Item"]
assert result == {
"id": {"S": "list_append_another"},
"a": {
"M": {
"b": {"L": [{"S": "bar1"}]},
"c": {"L": [{"S": "bar1"}, {"S": "bar2"}]},
}
},
}
@mock_dynamodb
def test_update_supports_list_append_maps():
client = boto3.client("dynamodb", region_name="us-west-1")
client.create_table(
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "rid", "AttributeType": "S"},
],
TableName="TestTable",
KeySchema=[
{"AttributeName": "id", "KeyType": "HASH"},
{"AttributeName": "rid", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
client.put_item(
TableName="TestTable",
Item={
"id": {"S": "nested_list_append"},
"rid": {"S": "range_key"},
"a": {"L": [{"M": {"b": {"S": "bar1"}}}]},
},
)
# Update item using list_append expression
updated_item = client.update_item(
TableName="TestTable",
Key={"id": {"S": "nested_list_append"}, "rid": {"S": "range_key"}},
UpdateExpression="SET a = list_append(a, :i)",
ExpressionAttributeValues={":i": {"L": [{"M": {"b": {"S": "bar2"}}}]}},
ReturnValues="UPDATED_NEW",
)
# Verify updated item is correct
assert updated_item["Attributes"] == {
"a": {"L": [{"M": {"b": {"S": "bar1"}}}, {"M": {"b": {"S": "bar2"}}}]}
}
# Verify item is appended to the existing list
result = client.query(
TableName="TestTable",
KeyConditionExpression="id = :i AND begins_with(rid, :r)",
ExpressionAttributeValues={
":i": {"S": "nested_list_append"},
":r": {"S": "range_key"},
},
)["Items"]
assert result == [
{
"a": {"L": [{"M": {"b": {"S": "bar1"}}}, {"M": {"b": {"S": "bar2"}}}]},
"rid": {"S": "range_key"},
"id": {"S": "nested_list_append"},
}
]
@mock_dynamodb
def test_update_supports_nested_update_if_nested_value_not_exists():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
name = "TestTable"
dynamodb.create_table(
TableName=name,
KeySchema=[{"AttributeName": "user_id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "user_id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table(name)
table.put_item(
Item={"user_id": "1234", "friends": {"5678": {"name": "friend_5678"}}}
)
table.update_item(
Key={"user_id": "1234"},
ExpressionAttributeNames={"#friends": "friends", "#friendid": "0000"},
ExpressionAttributeValues={":friend": {"name": "friend_0000"}},
UpdateExpression="SET #friends.#friendid = :friend",
ReturnValues="UPDATED_NEW",
)
item = table.get_item(Key={"user_id": "1234"})["Item"]
assert item == {
"user_id": "1234",
"friends": {"5678": {"name": "friend_5678"}, "0000": {"name": "friend_0000"}},
}
@mock_dynamodb
def test_update_supports_list_append_with_nested_if_not_exists_operation():
dynamo = boto3.resource("dynamodb", region_name="us-west-1")
table_name = "test"
dynamo.create_table(
TableName=table_name,
AttributeDefinitions=[{"AttributeName": "Id", "AttributeType": "S"}],
KeySchema=[{"AttributeName": "Id", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 20, "WriteCapacityUnits": 20},
)
table = dynamo.Table(table_name)
table.put_item(Item={"Id": "item-id", "nest1": {"nest2": {}}})
updated_item = table.update_item(
Key={"Id": "item-id"},
UpdateExpression="SET nest1.nest2.event_history = list_append(if_not_exists(nest1.nest2.event_history, :empty_list), :new_value)",
ExpressionAttributeValues={":empty_list": [], ":new_value": ["some_value"]},
ReturnValues="UPDATED_NEW",
)
# Verify updated item is correct
assert updated_item["Attributes"] == {
"nest1": {"nest2": {"event_history": ["some_value"]}}
}
assert table.get_item(Key={"Id": "item-id"})["Item"] == {
"Id": "item-id",
"nest1": {"nest2": {"event_history": ["some_value"]}},
}
@mock_dynamodb
def test_update_supports_list_append_with_nested_if_not_exists_operation_and_property_already_exists():
dynamo = boto3.resource("dynamodb", region_name="us-west-1")
table_name = "test"
dynamo.create_table(
TableName=table_name,
AttributeDefinitions=[{"AttributeName": "Id", "AttributeType": "S"}],
KeySchema=[{"AttributeName": "Id", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 20, "WriteCapacityUnits": 20},
)
table = dynamo.Table(table_name)
table.put_item(Item={"Id": "item-id", "event_history": ["other_value"]})
updated_item = table.update_item(
Key={"Id": "item-id"},
UpdateExpression="SET event_history = list_append(if_not_exists(event_history, :empty_list), :new_value)",
ExpressionAttributeValues={":empty_list": [], ":new_value": ["some_value"]},
ReturnValues="UPDATED_NEW",
)
# Verify updated item is correct
assert updated_item["Attributes"] == {
"event_history": ["other_value", "some_value"]
}
assert table.get_item(Key={"Id": "item-id"})["Item"] == {
"Id": "item-id",
"event_history": ["other_value", "some_value"],
}
@mock_dynamodb
def test_update_item_if_original_value_is_none():
dynamo = boto3.resource("dynamodb", region_name="eu-central-1")
dynamo.create_table(
AttributeDefinitions=[{"AttributeName": "job_id", "AttributeType": "S"}],
TableName="origin-rbu-dev",
KeySchema=[{"AttributeName": "job_id", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
)
table = dynamo.Table("origin-rbu-dev")
table.put_item(Item={"job_id": "a", "job_name": None})
table.update_item(
Key={"job_id": "a"},
UpdateExpression="SET job_name = :output",
ExpressionAttributeValues={":output": "updated"},
)
assert table.scan()["Items"][0]["job_name"] == "updated"
@mock_dynamodb
def test_update_nested_item_if_original_value_is_none():
dynamo = boto3.resource("dynamodb", region_name="eu-central-1")
dynamo.create_table(
AttributeDefinitions=[{"AttributeName": "job_id", "AttributeType": "S"}],
TableName="origin-rbu-dev",
KeySchema=[{"AttributeName": "job_id", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
)
table = dynamo.Table("origin-rbu-dev")
table.put_item(Item={"job_id": "a", "job_details": {"job_name": None}})
updated_item = table.update_item(
Key={"job_id": "a"},
UpdateExpression="SET job_details.job_name = :output",
ExpressionAttributeValues={":output": "updated"},
ReturnValues="UPDATED_NEW",
)
# Verify updated item is correct
assert updated_item["Attributes"] == {"job_details": {"job_name": "updated"}}
assert table.scan()["Items"][0]["job_details"]["job_name"] == "updated"
@mock_dynamodb
def test_allow_update_to_item_with_different_type():
dynamo = boto3.resource("dynamodb", region_name="eu-central-1")
dynamo.create_table(
AttributeDefinitions=[{"AttributeName": "job_id", "AttributeType": "S"}],
TableName="origin-rbu-dev",
KeySchema=[{"AttributeName": "job_id", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
)
table = dynamo.Table("origin-rbu-dev")
table.put_item(Item={"job_id": "a", "job_details": {"job_name": {"nested": "yes"}}})
table.put_item(Item={"job_id": "b", "job_details": {"job_name": {"nested": "yes"}}})
updated_item = table.update_item(
Key={"job_id": "a"},
UpdateExpression="SET job_details.job_name = :output",
ExpressionAttributeValues={":output": "updated"},
ReturnValues="UPDATED_NEW",
)
# Verify updated item is correct
assert updated_item["Attributes"] == {"job_details": {"job_name": "updated"}}
assert (
table.get_item(Key={"job_id": "a"})["Item"]["job_details"]["job_name"]
== "updated"
)
assert table.get_item(Key={"job_id": "b"})["Item"]["job_details"]["job_name"] == {
"nested": "yes"
}
@mock_dynamodb
def test_query_catches_when_no_filters():
dynamo = boto3.resource("dynamodb", region_name="eu-central-1")
dynamo.create_table(
AttributeDefinitions=[{"AttributeName": "job_id", "AttributeType": "S"}],
TableName="origin-rbu-dev",
KeySchema=[{"AttributeName": "job_id", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
)
table = dynamo.Table("origin-rbu-dev")
with pytest.raises(ClientError) as ex:
table.query(TableName="original-rbu-dev")
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert (
ex.value.response["Error"]["Message"]
== "Either KeyConditions or QueryFilter should be present"
)
@mock_dynamodb
def test_invalid_transact_get_items():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test1",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = dynamodb.Table("test1")
table.put_item(Item={"id": "1", "val": "1"})
table.put_item(Item={"id": "1", "val": "2"})
client = boto3.client("dynamodb", region_name="us-east-1")
with pytest.raises(ClientError) as ex:
client.transact_get_items(
TransactItems=[
{"Get": {"Key": {"id": {"S": "1"}}, "TableName": "test1"}}
for i in range(26)
]
)
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert (
"Member must have length less than or equal to 25"
in ex.value.response["Error"]["Message"]
)
with pytest.raises(ClientError) as ex:
client.transact_get_items(
TransactItems=[
{"Get": {"Key": {"id": {"S": "1"}}, "TableName": "test1"}},
{"Get": {"Key": {"id": {"S": "1"}}, "TableName": "non_exists_table"}},
]
)
assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert ex.value.response["Error"]["Message"] == "Requested resource not found"
@mock_dynamodb
def test_valid_transact_get_items():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test1",
KeySchema=[
{"AttributeName": "id", "KeyType": "HASH"},
{"AttributeName": "sort_key", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "sort_key", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table1 = dynamodb.Table("test1")
table1.put_item(Item={"id": "1", "sort_key": "1"})
table1.put_item(Item={"id": "1", "sort_key": "2"})
dynamodb.create_table(
TableName="test2",
KeySchema=[
{"AttributeName": "id", "KeyType": "HASH"},
{"AttributeName": "sort_key", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "sort_key", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table2 = dynamodb.Table("test2")
table2.put_item(Item={"id": "1", "sort_key": "1"})
client = boto3.client("dynamodb", region_name="us-east-1")
res = client.transact_get_items(
TransactItems=[
{
"Get": {
"Key": {"id": {"S": "1"}, "sort_key": {"S": "1"}},
"TableName": "test1",
}
},
{
"Get": {
"Key": {"id": {"S": "non_exists_key"}, "sort_key": {"S": "2"}},
"TableName": "test1",
}
},
]
)
assert res["Responses"][0]["Item"] == {"id": {"S": "1"}, "sort_key": {"S": "1"}}
assert len(res["Responses"]) == 2
assert res["Responses"][1] == {}
res = client.transact_get_items(
TransactItems=[
{
"Get": {
"Key": {"id": {"S": "1"}, "sort_key": {"S": "1"}},
"TableName": "test1",
}
},
{
"Get": {
"Key": {"id": {"S": "1"}, "sort_key": {"S": "2"}},
"TableName": "test1",
}
},
{
"Get": {
"Key": {"id": {"S": "1"}, "sort_key": {"S": "1"}},
"TableName": "test2",
}
},
]
)
assert res["Responses"][0]["Item"] == {"id": {"S": "1"}, "sort_key": {"S": "1"}}
assert res["Responses"][1]["Item"] == {"id": {"S": "1"}, "sort_key": {"S": "2"}}
assert res["Responses"][2]["Item"] == {"id": {"S": "1"}, "sort_key": {"S": "1"}}
res = client.transact_get_items(
TransactItems=[
{
"Get": {
"Key": {"id": {"S": "1"}, "sort_key": {"S": "1"}},
"TableName": "test1",
}
},
{
"Get": {
"Key": {"id": {"S": "1"}, "sort_key": {"S": "2"}},
"TableName": "test1",
}
},
{
"Get": {
"Key": {"id": {"S": "1"}, "sort_key": {"S": "1"}},
"TableName": "test2",
}
},
],
ReturnConsumedCapacity="TOTAL",
)
assert res["ConsumedCapacity"][0] == {
"TableName": "test1",
"CapacityUnits": 4.0,
"ReadCapacityUnits": 4.0,
}
assert res["ConsumedCapacity"][1] == {
"TableName": "test2",
"CapacityUnits": 2.0,
"ReadCapacityUnits": 2.0,
}
res = client.transact_get_items(
TransactItems=[
{
"Get": {
"Key": {"id": {"S": "1"}, "sort_key": {"S": "1"}},
"TableName": "test1",
}
},
{
"Get": {
"Key": {"id": {"S": "1"}, "sort_key": {"S": "2"}},
"TableName": "test1",
}
},
{
"Get": {
"Key": {"id": {"S": "1"}, "sort_key": {"S": "1"}},
"TableName": "test2",
}
},
],
ReturnConsumedCapacity="INDEXES",
)
assert res["ConsumedCapacity"][0] == {
"TableName": "test1",
"CapacityUnits": 4.0,
"ReadCapacityUnits": 4.0,
"Table": {"CapacityUnits": 4.0, "ReadCapacityUnits": 4.0},
}
assert res["ConsumedCapacity"][1] == {
"TableName": "test2",
"CapacityUnits": 2.0,
"ReadCapacityUnits": 2.0,
"Table": {"CapacityUnits": 2.0, "ReadCapacityUnits": 2.0},
}
@mock_dynamodb
def test_gsi_verify_negative_number_order():
table_schema = {
"KeySchema": [{"AttributeName": "partitionKey", "KeyType": "HASH"}],
"GlobalSecondaryIndexes": [
{
"IndexName": "GSI-K1",
"KeySchema": [
{"AttributeName": "gsiK1PartitionKey", "KeyType": "HASH"},
{"AttributeName": "gsiK1SortKey", "KeyType": "RANGE"},
],
"Projection": {"ProjectionType": "KEYS_ONLY"},
}
],
"AttributeDefinitions": [
{"AttributeName": "partitionKey", "AttributeType": "S"},
{"AttributeName": "gsiK1PartitionKey", "AttributeType": "S"},
{"AttributeName": "gsiK1SortKey", "AttributeType": "N"},
],
}
item1 = {
"partitionKey": "pk-1",
"gsiK1PartitionKey": "gsi-k1",
"gsiK1SortKey": Decimal("-0.6"),
}
item2 = {
"partitionKey": "pk-2",
"gsiK1PartitionKey": "gsi-k1",
"gsiK1SortKey": Decimal("-0.7"),
}
item3 = {
"partitionKey": "pk-3",
"gsiK1PartitionKey": "gsi-k1",
"gsiK1SortKey": Decimal("0.7"),
}
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
table = dynamodb.Table("test-table")
table.put_item(Item=item3)
table.put_item(Item=item1)
table.put_item(Item=item2)
resp = table.query(
KeyConditionExpression=Key("gsiK1PartitionKey").eq("gsi-k1"), IndexName="GSI-K1"
)
# Items should be ordered with the lowest number first
assert [float(item["gsiK1SortKey"]) for item in resp["Items"]] == [-0.7, -0.6, 0.7]
@mock_dynamodb
def test_transact_write_items_put():
table_schema = {
"KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}],
"AttributeDefinitions": [{"AttributeName": "id", "AttributeType": "S"}],
}
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
# Put multiple items
dynamodb.transact_write_items(
TransactItems=[
{
"Put": {
"Item": {"id": {"S": f"foo{str(i)}"}, "foo": {"S": "bar"}},
"TableName": "test-table",
}
}
for i in range(0, 5)
]
)
# Assert all are present
items = dynamodb.scan(TableName="test-table")["Items"]
assert len(items) == 5
@pytest.mark.aws_verified
@dynamodb_aws_verified()
def test_transact_write_items_put_conditional_expressions(table_name=None):
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.put_item(TableName=table_name, Item={"pk": {"S": "foo2"}})
# Put multiple items
with pytest.raises(ClientError) as ex:
dynamodb.transact_write_items(
TransactItems=[
{
"Put": {
"Item": {
"pk": {"S": f"foo{i}"},
"foo": {"S": "bar"},
},
"TableName": table_name,
"ConditionExpression": "#i <> :i",
"ExpressionAttributeNames": {"#i": "pk"},
"ExpressionAttributeValues": {
":i": {
"S": "foo2"
} # This item already exist, so the ConditionExpression should fail
},
}
}
for i in range(0, 5)
]
)
# Assert the exception is correct
assert ex.value.response["Error"]["Code"] == "TransactionCanceledException"
reasons = ex.value.response["CancellationReasons"]
assert len(reasons) == 5
assert {
"Code": "ConditionalCheckFailed",
"Message": "The conditional request failed",
} in reasons
assert {"Code": "None"} in reasons
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
# Assert all are present
items = dynamodb.scan(TableName=table_name)["Items"]
assert len(items) == 1
assert items[0] == {"pk": {"S": "foo2"}}
@pytest.mark.aws_verified
@dynamodb_aws_verified()
def test_transact_write_items_failure__return_item(table_name=None):
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.put_item(TableName=table_name, Item={"pk": {"S": "foo2"}})
# Put multiple items
with pytest.raises(ClientError) as ex:
dynamodb.transact_write_items(
TransactItems=[
{
"Put": {
"Item": {
"pk": {"S": f"foo{i}"},
"foo": {"S": "bar"},
},
"TableName": table_name,
"ConditionExpression": "#i <> :i",
"ExpressionAttributeNames": {"#i": "pk"},
# This man right here - should return item as part of error message
"ReturnValuesOnConditionCheckFailure": "ALL_OLD",
"ExpressionAttributeValues": {
":i": {
"S": "foo2"
} # This item already exist, so the ConditionExpression should fail
},
}
}
for i in range(0, 5)
]
)
# Assert the exception is correct
assert ex.value.response["Error"]["Code"] == "TransactionCanceledException"
reasons = ex.value.response["CancellationReasons"]
assert len(reasons) == 5
assert {
"Code": "ConditionalCheckFailed",
"Message": "The conditional request failed",
"Item": {"pk": {"S": "foo2"}},
} in reasons
assert {"Code": "None"} in reasons
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
# Assert all are present
items = dynamodb.scan(TableName=table_name)["Items"]
assert len(items) == 1
assert items[0] == {"pk": {"S": "foo2"}}
@mock_dynamodb
def test_transact_write_items_conditioncheck_passes():
table_schema = {
"KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}],
"AttributeDefinitions": [{"AttributeName": "id", "AttributeType": "S"}],
}
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
# Insert an item without email address
dynamodb.put_item(TableName="test-table", Item={"id": {"S": "foo"}})
# Put an email address, after verifying it doesn't exist yet
dynamodb.transact_write_items(
TransactItems=[
{
"ConditionCheck": {
"Key": {"id": {"S": "foo"}},
"TableName": "test-table",
"ConditionExpression": "attribute_not_exists(#e)",
"ExpressionAttributeNames": {"#e": "email_address"},
}
},
{
"Put": {
"Item": {
"id": {"S": "foo"},
"email_address": {"S": "test@moto.com"},
},
"TableName": "test-table",
}
},
]
)
# Assert all are present
items = dynamodb.scan(TableName="test-table")["Items"]
assert len(items) == 1
assert items[0] == {"email_address": {"S": "test@moto.com"}, "id": {"S": "foo"}}
@mock_dynamodb
def test_transact_write_items_conditioncheck_fails():
table_schema = {
"KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}],
"AttributeDefinitions": [{"AttributeName": "id", "AttributeType": "S"}],
}
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
# Insert an item with email address
dynamodb.put_item(
TableName="test-table",
Item={"id": {"S": "foo"}, "email_address": {"S": "test@moto.com"}},
)
# Try to put an email address, but verify whether it exists
# ConditionCheck should fail
with pytest.raises(ClientError) as ex:
dynamodb.transact_write_items(
TransactItems=[
{
"ConditionCheck": {
"Key": {"id": {"S": "foo"}},
"TableName": "test-table",
"ConditionExpression": "attribute_not_exists(#e)",
"ExpressionAttributeNames": {"#e": "email_address"},
}
},
{
"Put": {
"Item": {
"id": {"S": "foo"},
"email_address": {"S": "update@moto.com"},
},
"TableName": "test-table",
}
},
]
)
# Assert the exception is correct
assert ex.value.response["Error"]["Code"] == "TransactionCanceledException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
# Assert the original email address is still present
items = dynamodb.scan(TableName="test-table")["Items"]
assert len(items) == 1
assert items[0] == {"email_address": {"S": "test@moto.com"}, "id": {"S": "foo"}}
@mock_dynamodb
def test_transact_write_items_delete():
table_schema = {
"KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}],
"AttributeDefinitions": [{"AttributeName": "id", "AttributeType": "S"}],
}
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
# Insert an item
dynamodb.put_item(TableName="test-table", Item={"id": {"S": "foo"}})
# Delete the item
dynamodb.transact_write_items(
TransactItems=[
{"Delete": {"Key": {"id": {"S": "foo"}}, "TableName": "test-table"}}
]
)
# Assert the item is deleted
items = dynamodb.scan(TableName="test-table")["Items"]
assert len(items) == 0
@mock_dynamodb
def test_transact_write_items_delete_with_successful_condition_expression():
table_schema = {
"KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}],
"AttributeDefinitions": [{"AttributeName": "id", "AttributeType": "S"}],
}
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
# Insert an item without email address
dynamodb.put_item(TableName="test-table", Item={"id": {"S": "foo"}})
# ConditionExpression will pass - no email address has been specified yet
dynamodb.transact_write_items(
TransactItems=[
{
"Delete": {
"Key": {"id": {"S": "foo"}},
"TableName": "test-table",
"ConditionExpression": "attribute_not_exists(#e)",
"ExpressionAttributeNames": {"#e": "email_address"},
}
}
]
)
# Assert the item is deleted
items = dynamodb.scan(TableName="test-table")["Items"]
assert len(items) == 0
@mock_dynamodb
def test_transact_write_items_delete_with_failed_condition_expression():
table_schema = {
"KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}],
"AttributeDefinitions": [{"AttributeName": "id", "AttributeType": "S"}],
}
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
# Insert an item with email address
dynamodb.put_item(
TableName="test-table",
Item={"id": {"S": "foo"}, "email_address": {"S": "test@moto.com"}},
)
# Try to delete an item that does not have an email address
# ConditionCheck should fail
with pytest.raises(ClientError) as ex:
dynamodb.transact_write_items(
TransactItems=[
{
"Delete": {
"Key": {"id": {"S": "foo"}},
"TableName": "test-table",
"ConditionExpression": "attribute_not_exists(#e)",
"ExpressionAttributeNames": {"#e": "email_address"},
}
}
]
)
# Assert the exception is correct
assert ex.value.response["Error"]["Code"] == "TransactionCanceledException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
# Assert the original item is still present
items = dynamodb.scan(TableName="test-table")["Items"]
assert len(items) == 1
assert items[0] == {"email_address": {"S": "test@moto.com"}, "id": {"S": "foo"}}
@mock_dynamodb
def test_transact_write_items_update():
table_schema = {
"KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}],
"AttributeDefinitions": [{"AttributeName": "id", "AttributeType": "S"}],
}
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
# Insert an item
dynamodb.put_item(TableName="test-table", Item={"id": {"S": "foo"}})
# Update the item
dynamodb.transact_write_items(
TransactItems=[
{
"Update": {
"Key": {"id": {"S": "foo"}},
"TableName": "test-table",
"UpdateExpression": "SET #e = :v",
"ExpressionAttributeNames": {"#e": "email_address"},
"ExpressionAttributeValues": {":v": {"S": "test@moto.com"}},
}
}
]
)
# Assert the item is updated
items = dynamodb.scan(TableName="test-table")["Items"]
assert len(items) == 1
assert items[0] == {"id": {"S": "foo"}, "email_address": {"S": "test@moto.com"}}
@mock_dynamodb
def test_transact_write_items_update_with_failed_condition_expression():
table_schema = {
"KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}],
"AttributeDefinitions": [{"AttributeName": "id", "AttributeType": "S"}],
}
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
# Insert an item with email address
dynamodb.put_item(
TableName="test-table",
Item={"id": {"S": "foo"}, "email_address": {"S": "test@moto.com"}},
)
# Try to update an item that does not have an email address
# ConditionCheck should fail
with pytest.raises(ClientError) as ex:
dynamodb.transact_write_items(
TransactItems=[
{
"Update": {
"Key": {"id": {"S": "foo"}},
"TableName": "test-table",
"UpdateExpression": "SET #e = :v",
"ConditionExpression": "attribute_not_exists(#e)",
"ExpressionAttributeNames": {"#e": "email_address"},
"ExpressionAttributeValues": {":v": {"S": "update@moto.com"}},
}
}
]
)
# Assert the exception is correct
assert ex.value.response["Error"]["Code"] == "TransactionCanceledException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
# Assert the original item is still present
items = dynamodb.scan(TableName="test-table")["Items"]
assert len(items) == 1
assert items[0] == {"email_address": {"S": "test@moto.com"}, "id": {"S": "foo"}}
@mock_dynamodb
def test_dynamodb_max_1mb_limit():
ddb = boto3.resource("dynamodb", region_name="eu-west-1")
table_name = "populated-mock-table"
table = ddb.create_table(
TableName=table_name,
KeySchema=[
{"AttributeName": "partition_key", "KeyType": "HASH"},
{"AttributeName": "sort_key", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "partition_key", "AttributeType": "S"},
{"AttributeName": "sort_key", "AttributeType": "S"},
],
BillingMode="PAY_PER_REQUEST",
)
# Populate the table
items = [
{
"partition_key": "partition_key_val", # size=30
"sort_key": "sort_key_value____" + str(i), # size=30
}
for i in range(10000, 29999)
]
with table.batch_writer() as batch:
for item in items:
batch.put_item(Item=item)
response = table.query(
KeyConditionExpression=Key("partition_key").eq("partition_key_val")
)
# We shouldn't get everything back - the total result set is well over 1MB
assert len(items) > response["Count"]
assert response["LastEvaluatedKey"] is not None
def assert_raise_syntax_error(client_error, token, near):
"""
Assert whether a client_error is as expected Syntax error. Syntax error looks like: `syntax_error_template`
Args:
client_error(ClientError): The ClientError exception that was raised
token(str): The token that ws unexpected
near(str): The part in the expression that shows where the error occurs it generally has the preceding token the
optional separation and the problematic token.
"""
syntax_error_template = (
'Invalid UpdateExpression: Syntax error; token: "{token}", near: "{near}"'
)
expected_syntax_error = syntax_error_template.format(token=token, near=near)
assert client_error["Code"] == "ValidationException"
assert expected_syntax_error == client_error["Message"]
@mock_dynamodb
def test_update_expression_with_numeric_literal_instead_of_value():
"""
DynamoDB requires literals to be passed in as values. If they are put literally in the expression a token error will
be raised
"""
dynamodb = boto3.client("dynamodb", region_name="eu-west-1")
dynamodb.create_table(
TableName="moto-test",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
with pytest.raises(ClientError) as exc:
dynamodb.update_item(
TableName="moto-test",
Key={"id": {"S": "1"}},
UpdateExpression="SET MyStr = myNum + 1",
)
err = exc.value.response["Error"]
assert_raise_syntax_error(err, "1", "+ 1")
@mock_dynamodb
def test_update_expression_with_multiple_set_clauses_must_be_comma_separated():
"""
An UpdateExpression can have multiple set clauses but if they are passed in without the separating comma.
"""
dynamodb = boto3.client("dynamodb", region_name="eu-west-1")
dynamodb.create_table(
TableName="moto-test",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
with pytest.raises(ClientError) as exc:
dynamodb.update_item(
TableName="moto-test",
Key={"id": {"S": "1"}},
UpdateExpression="SET MyStr = myNum Mystr2 myNum2",
)
err = exc.value.response["Error"]
assert_raise_syntax_error(err, "Mystr2", "myNum Mystr2 myNum2")
@mock_dynamodb
def test_list_tables_exclusive_start_table_name_empty():
client = boto3.client("dynamodb", region_name="us-east-1")
resp = client.list_tables(Limit=1, ExclusiveStartTableName="whatever")
assert len(resp["TableNames"]) == 0
def assert_correct_client_error(
client_error, code, message_template, message_values=None, braces=None
):
"""
Assert whether a client_error is as expected. Allow for a list of values to be passed into the message
Args:
client_error(ClientError): The ClientError exception that was raised
code(str): The code for the error (e.g. ValidationException)
message_template(str): Error message template. if message_values is not None then this template has a {values}
as placeholder. For example:
'Value provided in ExpressionAttributeValues unused in expressions: keys: {values}'
message_values(list of str|None): The values that are passed in the error message
braces(list of str|None): List of length 2 with opening and closing brace for the values. By default it will be
surrounded by curly brackets
"""
braces = braces or ["{", "}"]
assert client_error.response["Error"]["Code"] == code
if message_values is not None:
values_string = f"{braces[0]}(?P<values>.*){braces[1]}"
re_msg = re.compile(message_template.format(values=values_string))
match_result = re_msg.match(client_error.response["Error"]["Message"])
assert match_result is not None
values_string = match_result.groupdict()["values"]
values = [key for key in values_string.split(", ")]
assert len(message_values) == len(values)
for value in message_values:
assert value in values
else:
assert client_error.response["Error"]["Message"] == message_template
def create_simple_table_and_return_client():
dynamodb = boto3.client("dynamodb", region_name="eu-west-1")
dynamodb.create_table(
TableName="moto-test",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
)
dynamodb.put_item(
TableName="moto-test",
Item={"id": {"S": "1"}, "myNum": {"N": "1"}, "MyStr": {"S": "1"}},
)
return dynamodb
# https://github.com/getmoto/moto/issues/2806
# https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html
# #DDB-UpdateItem-request-UpdateExpression
@mock_dynamodb
def test_update_item_with_attribute_in_right_hand_side_and_operation():
dynamodb = create_simple_table_and_return_client()
dynamodb.update_item(
TableName="moto-test",
Key={"id": {"S": "1"}},
UpdateExpression="SET myNum = myNum+:val",
ExpressionAttributeValues={":val": {"N": "3"}},
)
result = dynamodb.get_item(TableName="moto-test", Key={"id": {"S": "1"}})
assert result["Item"]["myNum"]["N"] == "4"
dynamodb.update_item(
TableName="moto-test",
Key={"id": {"S": "1"}},
UpdateExpression="SET myNum = myNum - :val",
ExpressionAttributeValues={":val": {"N": "1"}},
)
result = dynamodb.get_item(TableName="moto-test", Key={"id": {"S": "1"}})
assert result["Item"]["myNum"]["N"] == "3"
@mock_dynamodb
def test_non_existing_attribute_should_raise_exception():
"""
Does error message get correctly raised if attribute is referenced but it does not exist for the item.
"""
dynamodb = create_simple_table_and_return_client()
try:
dynamodb.update_item(
TableName="moto-test",
Key={"id": {"S": "1"}},
UpdateExpression="SET MyStr = no_attr + MyStr",
)
assert False, "Validation exception not thrown"
except dynamodb.exceptions.ClientError as e:
assert_correct_client_error(
e,
"ValidationException",
"The provided expression refers to an attribute that does not exist in the item",
)
@mock_dynamodb
def test_update_expression_with_plus_in_attribute_name():
"""
Does error message get correctly raised if attribute contains a plus and is passed in without an AttributeName. And
lhs & rhs are not attribute IDs by themselve.
"""
dynamodb = create_simple_table_and_return_client()
dynamodb.put_item(
TableName="moto-test",
Item={"id": {"S": "1"}, "my+Num": {"S": "1"}, "MyStr": {"S": "aaa"}},
)
try:
dynamodb.update_item(
TableName="moto-test",
Key={"id": {"S": "1"}},
UpdateExpression="SET MyStr = my+Num",
)
assert False, "Validation exception not thrown"
except dynamodb.exceptions.ClientError as e:
assert_correct_client_error(
e,
"ValidationException",
"The provided expression refers to an attribute that does not exist in the item",
)
@mock_dynamodb
def test_update_expression_with_minus_in_attribute_name():
"""
Does error message get correctly raised if attribute contains a minus and is passed in without an AttributeName. And
lhs & rhs are not attribute IDs by themselve.
"""
dynamodb = create_simple_table_and_return_client()
dynamodb.put_item(
TableName="moto-test",
Item={"id": {"S": "1"}, "my-Num": {"S": "1"}, "MyStr": {"S": "aaa"}},
)
try:
dynamodb.update_item(
TableName="moto-test",
Key={"id": {"S": "1"}},
UpdateExpression="SET MyStr = my-Num",
)
assert False, "Validation exception not thrown"
except dynamodb.exceptions.ClientError as e:
assert_correct_client_error(
e,
"ValidationException",
"The provided expression refers to an attribute that does not exist in the item",
)
@mock_dynamodb
def test_update_expression_with_space_in_attribute_name():
"""
Does error message get correctly raised if attribute contains a space and is passed in without an AttributeName. And
lhs & rhs are not attribute IDs by themselves.
"""
dynamodb = create_simple_table_and_return_client()
dynamodb.put_item(
TableName="moto-test",
Item={"id": {"S": "1"}, "my Num": {"S": "1"}, "MyStr": {"S": "aaa"}},
)
with pytest.raises(ClientError) as exc:
dynamodb.update_item(
TableName="moto-test",
Key={"id": {"S": "1"}},
UpdateExpression="SET MyStr = my Num",
)
err = exc.value.response["Error"]
assert_raise_syntax_error(err, "Num", "my Num")
@mock_dynamodb
def test_summing_up_2_strings_raises_exception():
"""
Update set supports different DynamoDB types but some operations are not supported. For example summing up 2 strings
raises an exception. It results in ClientError with code ValidationException:
Saying An operand in the update expression has an incorrect data type
"""
dynamodb = create_simple_table_and_return_client()
try:
dynamodb.update_item(
TableName="moto-test",
Key={"id": {"S": "1"}},
UpdateExpression="SET MyStr = MyStr + MyStr",
)
assert False, "Validation exception not thrown"
except dynamodb.exceptions.ClientError as e:
assert_correct_client_error(
e,
"ValidationException",
"An operand in the update expression has an incorrect data type",
)
# https://github.com/getmoto/moto/issues/2806
@mock_dynamodb
def test_update_item_with_attribute_in_right_hand_side():
"""
After tokenization and building expression make sure referenced attributes are replaced with their current value
"""
dynamodb = create_simple_table_and_return_client()
# Make sure there are 2 values
dynamodb.put_item(
TableName="moto-test",
Item={"id": {"S": "1"}, "myVal1": {"S": "Value1"}, "myVal2": {"S": "Value2"}},
)
dynamodb.update_item(
TableName="moto-test",
Key={"id": {"S": "1"}},
UpdateExpression="SET myVal1 = myVal2",
)
result = dynamodb.get_item(TableName="moto-test", Key={"id": {"S": "1"}})
assert result["Item"]["myVal1"]["S"] == result["Item"]["myVal2"]["S"] == "Value2"
@mock_dynamodb
def test_multiple_updates():
dynamodb = create_simple_table_and_return_client()
dynamodb.put_item(
TableName="moto-test",
Item={"id": {"S": "1"}, "myNum": {"N": "1"}, "path": {"N": "6"}},
)
dynamodb.update_item(
TableName="moto-test",
Key={"id": {"S": "1"}},
UpdateExpression="SET myNum = #p + :val, newAttr = myNum",
ExpressionAttributeValues={":val": {"N": "1"}},
ExpressionAttributeNames={"#p": "path"},
)
result = dynamodb.get_item(TableName="moto-test", Key={"id": {"S": "1"}})["Item"]
expected_result = {
"myNum": {"N": "7"},
"newAttr": {"N": "1"},
"path": {"N": "6"},
"id": {"S": "1"},
}
assert result == expected_result
@mock_dynamodb
def test_update_item_atomic_counter():
table = "table_t"
ddb_mock = boto3.client("dynamodb", region_name="eu-west-3")
ddb_mock.create_table(
TableName=table,
KeySchema=[{"AttributeName": "t_id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "t_id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
key = {"t_id": {"S": "item1"}}
ddb_mock.put_item(
TableName=table,
Item={"t_id": {"S": "item1"}, "n_i": {"N": "5"}, "n_f": {"N": "5.3"}},
)
ddb_mock.update_item(
TableName=table,
Key=key,
UpdateExpression="set n_i = n_i + :inc1, n_f = n_f + :inc2",
ExpressionAttributeValues={":inc1": {"N": "1.2"}, ":inc2": {"N": "0.05"}},
)
updated_item = ddb_mock.get_item(TableName=table, Key=key)["Item"]
assert updated_item["n_i"]["N"] == "6.2"
assert updated_item["n_f"]["N"] == "5.35"
@mock_dynamodb
def test_update_item_atomic_counter_return_values():
table = "table_t"
ddb_mock = boto3.client("dynamodb", region_name="eu-west-3")
ddb_mock.create_table(
TableName=table,
KeySchema=[{"AttributeName": "t_id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "t_id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
key = {"t_id": {"S": "item1"}}
ddb_mock.put_item(TableName=table, Item={"t_id": {"S": "item1"}, "v": {"N": "5"}})
response = ddb_mock.update_item(
TableName=table,
Key=key,
UpdateExpression="set v = v + :inc",
ExpressionAttributeValues={":inc": {"N": "1"}},
ReturnValues="UPDATED_OLD",
)
# v has been updated, and should be returned here
assert response["Attributes"]["v"]["N"] == "5"
# second update
response = ddb_mock.update_item(
TableName=table,
Key=key,
UpdateExpression="set v = v + :inc",
ExpressionAttributeValues={":inc": {"N": "1"}},
ReturnValues="UPDATED_OLD",
)
# v has been updated, and should be returned here
assert response["Attributes"]["v"]["N"] == "6"
# third update
response = ddb_mock.update_item(
TableName=table,
Key=key,
UpdateExpression="set v = v + :inc",
ExpressionAttributeValues={":inc": {"N": "1"}},
ReturnValues="UPDATED_NEW",
)
# v has been updated, and should be returned here
assert response["Attributes"]["v"]["N"] == "8"
@mock_dynamodb
def test_update_item_atomic_counter_from_zero():
table = "table_t"
ddb_mock = boto3.client("dynamodb", region_name="eu-west-1")
ddb_mock.create_table(
TableName=table,
KeySchema=[{"AttributeName": "t_id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "t_id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
key = {"t_id": {"S": "item1"}}
ddb_mock.put_item(TableName=table, Item=key)
ddb_mock.update_item(
TableName=table,
Key=key,
UpdateExpression="add n_i :inc1, n_f :inc2",
ExpressionAttributeValues={":inc1": {"N": "1.2"}, ":inc2": {"N": "-0.5"}},
)
updated_item = ddb_mock.get_item(TableName=table, Key=key)["Item"]
assert updated_item["n_i"]["N"] == "1.2"
assert updated_item["n_f"]["N"] == "-0.5"
@mock_dynamodb
def test_update_item_add_to_non_existent_set():
table = "table_t"
ddb_mock = boto3.client("dynamodb", region_name="eu-west-1")
ddb_mock.create_table(
TableName=table,
KeySchema=[{"AttributeName": "t_id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "t_id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
key = {"t_id": {"S": "item1"}}
ddb_mock.put_item(TableName=table, Item=key)
ddb_mock.update_item(
TableName=table,
Key=key,
UpdateExpression="add s_i :s1",
ExpressionAttributeValues={":s1": {"SS": ["hello"]}},
)
updated_item = ddb_mock.get_item(TableName=table, Key=key)["Item"]
assert updated_item["s_i"]["SS"] == ["hello"]
@mock_dynamodb
def test_update_item_add_to_non_existent_number_set():
table = "table_t"
ddb_mock = boto3.client("dynamodb", region_name="eu-west-1")
ddb_mock.create_table(
TableName=table,
KeySchema=[{"AttributeName": "t_id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "t_id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
key = {"t_id": {"S": "item1"}}
ddb_mock.put_item(TableName=table, Item=key)
ddb_mock.update_item(
TableName=table,
Key=key,
UpdateExpression="add s_i :s1",
ExpressionAttributeValues={":s1": {"NS": ["3"]}},
)
updated_item = ddb_mock.get_item(TableName=table, Key=key)["Item"]
assert updated_item["s_i"]["NS"] == ["3"]
@mock_dynamodb
def test_transact_write_items_fails_with_transaction_canceled_exception():
table_schema = {
"KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}],
"AttributeDefinitions": [{"AttributeName": "id", "AttributeType": "S"}],
}
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
# Insert one item
dynamodb.put_item(TableName="test-table", Item={"id": {"S": "foo"}})
# Update two items, the one that exists and another that doesn't
with pytest.raises(ClientError) as ex:
dynamodb.transact_write_items(
TransactItems=[
{
"Update": {
"Key": {"id": {"S": "foo"}},
"TableName": "test-table",
"UpdateExpression": "SET #k = :v",
"ConditionExpression": "attribute_exists(id)",
"ExpressionAttributeNames": {"#k": "key"},
"ExpressionAttributeValues": {":v": {"S": "value"}},
}
},
{
"Update": {
"Key": {"id": {"S": "doesnotexist"}},
"TableName": "test-table",
"UpdateExpression": "SET #e = :v",
"ConditionExpression": "attribute_exists(id)",
"ExpressionAttributeNames": {"#e": "key"},
"ExpressionAttributeValues": {":v": {"S": "value"}},
}
},
]
)
assert ex.value.response["Error"]["Code"] == "TransactionCanceledException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert (
ex.value.response["Error"]["Message"]
== "Transaction cancelled, please refer cancellation reasons for specific reasons [None, ConditionalCheckFailed]"
)
@mock_dynamodb
def test_gsi_projection_type_keys_only():
table_schema = {
"KeySchema": [{"AttributeName": "partitionKey", "KeyType": "HASH"}],
"GlobalSecondaryIndexes": [
{
"IndexName": "GSI-K1",
"KeySchema": [
{"AttributeName": "gsiK1PartitionKey", "KeyType": "HASH"},
{"AttributeName": "gsiK1SortKey", "KeyType": "RANGE"},
],
"Projection": {"ProjectionType": "KEYS_ONLY"},
}
],
"AttributeDefinitions": [
{"AttributeName": "partitionKey", "AttributeType": "S"},
{"AttributeName": "gsiK1PartitionKey", "AttributeType": "S"},
{"AttributeName": "gsiK1SortKey", "AttributeType": "S"},
],
}
item = {
"partitionKey": "pk-1",
"gsiK1PartitionKey": "gsi-pk",
"gsiK1SortKey": "gsi-sk",
"someAttribute": "lore ipsum",
}
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
table = dynamodb.Table("test-table")
table.put_item(Item=item)
items = table.query(
KeyConditionExpression=Key("gsiK1PartitionKey").eq("gsi-pk"), IndexName="GSI-K1"
)["Items"]
assert len(items) == 1
# Item should only include GSI Keys and Table Keys, as per the ProjectionType
assert items[0] == {
"gsiK1PartitionKey": "gsi-pk",
"gsiK1SortKey": "gsi-sk",
"partitionKey": "pk-1",
}
@mock_dynamodb
def test_gsi_projection_type_include():
table_schema = {
"KeySchema": [{"AttributeName": "partitionKey", "KeyType": "HASH"}],
"GlobalSecondaryIndexes": [
{
"IndexName": "GSI-INC",
"KeySchema": [
{"AttributeName": "gsiK1PartitionKey", "KeyType": "HASH"},
{"AttributeName": "gsiK1SortKey", "KeyType": "RANGE"},
],
"Projection": {
"ProjectionType": "INCLUDE",
"NonKeyAttributes": ["projectedAttribute"],
},
}
],
"AttributeDefinitions": [
{"AttributeName": "partitionKey", "AttributeType": "S"},
{"AttributeName": "gsiK1PartitionKey", "AttributeType": "S"},
{"AttributeName": "gsiK1SortKey", "AttributeType": "S"},
],
}
item = {
"partitionKey": "pk-1",
"gsiK1PartitionKey": "gsi-pk",
"gsiK1SortKey": "gsi-sk",
"projectedAttribute": "lore ipsum",
"nonProjectedAttribute": "dolor sit amet",
}
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
table = dynamodb.Table("test-table")
table.put_item(Item=item)
items = table.query(
KeyConditionExpression=Key("gsiK1PartitionKey").eq("gsi-pk"),
IndexName="GSI-INC",
)["Items"]
assert len(items) == 1
# Item should only include keys and additionally projected attributes only
assert items[0] == {
"gsiK1PartitionKey": "gsi-pk",
"gsiK1SortKey": "gsi-sk",
"partitionKey": "pk-1",
"projectedAttribute": "lore ipsum",
}
# Same when scanning the table
items = table.scan(IndexName="GSI-INC")["Items"]
assert items[0] == {
"gsiK1PartitionKey": "gsi-pk",
"gsiK1SortKey": "gsi-sk",
"partitionKey": "pk-1",
"projectedAttribute": "lore ipsum",
}
@mock_dynamodb
def test_lsi_projection_type_keys_only():
table_schema = {
"KeySchema": [
{"AttributeName": "partitionKey", "KeyType": "HASH"},
{"AttributeName": "sortKey", "KeyType": "RANGE"},
],
"LocalSecondaryIndexes": [
{
"IndexName": "LSI",
"KeySchema": [
{"AttributeName": "partitionKey", "KeyType": "HASH"},
{"AttributeName": "lsiK1SortKey", "KeyType": "RANGE"},
],
"Projection": {"ProjectionType": "KEYS_ONLY"},
}
],
"AttributeDefinitions": [
{"AttributeName": "partitionKey", "AttributeType": "S"},
{"AttributeName": "sortKey", "AttributeType": "S"},
{"AttributeName": "lsiK1SortKey", "AttributeType": "S"},
],
}
item = {
"partitionKey": "pk-1",
"sortKey": "sk-1",
"lsiK1SortKey": "lsi-sk",
"someAttribute": "lore ipsum",
}
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
table = dynamodb.Table("test-table")
table.put_item(Item=item)
items = table.query(
KeyConditionExpression=Key("partitionKey").eq("pk-1"), IndexName="LSI"
)["Items"]
# Item should only include GSI Keys and Table Keys, as per the ProjectionType
assert items == [
{"partitionKey": "pk-1", "sortKey": "sk-1", "lsiK1SortKey": "lsi-sk"}
]
# Same when scanning the table
items = table.scan(IndexName="LSI")["Items"]
assert items[0] == {
"lsiK1SortKey": "lsi-sk",
"partitionKey": "pk-1",
"sortKey": "sk-1",
}
@mock_dynamodb
@pytest.mark.parametrize(
"attr_name",
["orders", "#placeholder"],
ids=["use attribute name", "use expression attribute name"],
)
def test_set_attribute_is_dropped_if_empty_after_update_expression(attr_name):
table_name, item_key, set_item = "test-table", "test-id", "test-data"
expression_attribute_names = {"#placeholder": "orders"}
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "customer", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "customer", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
client.update_item(
TableName=table_name,
Key={"customer": {"S": item_key}},
UpdateExpression=f"ADD {attr_name} :order",
ExpressionAttributeNames=expression_attribute_names,
ExpressionAttributeValues={":order": {"SS": [set_item]}},
)
resp = client.scan(TableName=table_name, ProjectionExpression="customer, orders")
item = resp["Items"][0]
assert "customer" in item
assert "orders" in item
client.update_item(
TableName=table_name,
Key={"customer": {"S": item_key}},
UpdateExpression=f"DELETE {attr_name} :order",
ExpressionAttributeNames=expression_attribute_names,
ExpressionAttributeValues={":order": {"SS": [set_item]}},
)
resp = client.scan(TableName=table_name, ProjectionExpression="customer, orders")
item = resp["Items"][0]
assert "customer" in item
assert "orders" not in item
@mock_dynamodb
def test_transact_get_items_should_return_empty_map_for_non_existent_item():
client = boto3.client("dynamodb", region_name="us-west-2")
table_name = "test-table"
key_schema = [{"AttributeName": "id", "KeyType": "HASH"}]
attribute_definitions = [{"AttributeName": "id", "AttributeType": "S"}]
client.create_table(
TableName=table_name,
KeySchema=key_schema,
AttributeDefinitions=attribute_definitions,
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
item = {"id": {"S": "1"}}
client.put_item(TableName=table_name, Item=item)
items = client.transact_get_items(
TransactItems=[
{"Get": {"Key": {"id": {"S": "1"}}, "TableName": table_name}},
{"Get": {"Key": {"id": {"S": "2"}}, "TableName": table_name}},
]
).get("Responses", [])
assert len(items) == 2
assert items[0] == {"Item": item}
assert items[1] == {}
@mock_dynamodb
def test_dynamodb_update_item_fails_on_string_sets():
dynamodb = boto3.resource("dynamodb", region_name="eu-west-1")
client = boto3.client("dynamodb", region_name="eu-west-1")
table = dynamodb.create_table(
TableName="test",
KeySchema=[{"AttributeName": "record_id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "record_id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
table.meta.client.get_waiter("table_exists").wait(TableName="test")
attribute = {"test_field": {"Value": {"SS": ["test1", "test2"]}, "Action": "PUT"}}
client.update_item(
TableName="test",
Key={"record_id": {"S": "testrecord"}},
AttributeUpdates=attribute,
)
@mock_dynamodb
def test_update_item_add_to_list_using_legacy_attribute_updates():
resource = boto3.resource("dynamodb", region_name="us-west-2")
resource.create_table(
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
TableName="TestTable",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = resource.Table("TestTable")
table.wait_until_exists()
table.put_item(Item={"id": "list_add", "attr": ["a", "b", "c"]})
table.update_item(
TableName="TestTable",
Key={"id": "list_add"},
AttributeUpdates={"attr": {"Action": "ADD", "Value": ["d", "e"]}},
)
resp = table.get_item(Key={"id": "list_add"})
assert resp["Item"]["attr"] == ["a", "b", "c", "d", "e"]
@mock_dynamodb
def test_update_item_add_to_num_set_using_legacy_attribute_updates():
resource = boto3.resource("dynamodb", region_name="us-west-2")
resource.create_table(
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
TableName="TestTable",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = resource.Table("TestTable")
table.wait_until_exists()
table.put_item(Item={"id": "set_add", "attr": {1, 2}})
table.update_item(
TableName="TestTable",
Key={"id": "set_add"},
AttributeUpdates={"attr": {"Action": "PUT", "Value": {1, 2, 3}}},
)
table.update_item(
TableName="TestTable",
Key={"id": "set_add"},
AttributeUpdates={"attr": {"Action": "ADD", "Value": {4, 5}}},
)
resp = table.get_item(Key={"id": "set_add"})
assert resp["Item"]["attr"] == {1, 2, 3, 4, 5}
table.update_item(
TableName="TestTable",
Key={"id": "set_add"},
AttributeUpdates={"attr": {"Action": "DELETE", "Value": {2, 3}}},
)
resp = table.get_item(Key={"id": "set_add"})
assert resp["Item"]["attr"] == {1, 4, 5}
@mock_dynamodb
def test_get_item_for_non_existent_table_raises_error():
client = boto3.client("dynamodb", "us-east-1")
with pytest.raises(ClientError) as ex:
client.get_item(TableName="non-existent", Key={"site-id": {"S": "foo"}})
assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
assert ex.value.response["Error"]["Message"] == "Requested resource not found"
@mock_dynamodb
def test_error_when_providing_expression_and_nonexpression_params():
client = boto3.client("dynamodb", "eu-central-1")
table_name = "testtable"
client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "pkey", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "pkey", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
with pytest.raises(ClientError) as ex:
client.update_item(
TableName=table_name,
Key={"pkey": {"S": "testrecord"}},
AttributeUpdates={
"test_field": {"Value": {"SS": ["test1", "test2"]}, "Action": "PUT"}
},
UpdateExpression="DELETE orders :order",
ExpressionAttributeValues={":order": {"SS": ["item"]}},
)
err = ex.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "Can not use both expression and non-expression parameters in the same request: Non-expression parameters: {AttributeUpdates} Expression parameters: {UpdateExpression}"
)
@mock_dynamodb
def test_attribute_item_delete():
name = "TestTable"
conn = boto3.client("dynamodb", region_name="eu-west-1")
conn.create_table(
TableName=name,
AttributeDefinitions=[{"AttributeName": "name", "AttributeType": "S"}],
KeySchema=[{"AttributeName": "name", "KeyType": "HASH"}],
BillingMode="PAY_PER_REQUEST",
)
item_name = "foo"
conn.put_item(
TableName=name, Item={"name": {"S": item_name}, "extra": {"S": "bar"}}
)
conn.update_item(
TableName=name,
Key={"name": {"S": item_name}},
AttributeUpdates={"extra": {"Action": "DELETE"}},
)
items = conn.scan(TableName=name)["Items"]
assert items == [{"name": {"S": "foo"}}]
@mock_dynamodb
def test_gsi_key_can_be_updated():
name = "TestTable"
conn = boto3.client("dynamodb", region_name="eu-west-2")
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "main_key", "KeyType": "HASH"}],
AttributeDefinitions=[
{"AttributeName": "main_key", "AttributeType": "S"},
{"AttributeName": "index_key", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
GlobalSecondaryIndexes=[
{
"IndexName": "test_index",
"KeySchema": [{"AttributeName": "index_key", "KeyType": "HASH"}],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1,
},
}
],
)
conn.put_item(
TableName=name,
Item={
"main_key": {"S": "testkey1"},
"extra_data": {"S": "testdata"},
"index_key": {"S": "indexkey1"},
},
)
conn.update_item(
TableName=name,
Key={"main_key": {"S": "testkey1"}},
UpdateExpression="set index_key=:new_index_key",
ExpressionAttributeValues={":new_index_key": {"S": "new_value"}},
)
item = conn.scan(TableName=name)["Items"][0]
assert item["index_key"] == {"S": "new_value"}
assert item["main_key"] == {"S": "testkey1"}
@mock_dynamodb
def test_gsi_key_cannot_be_empty():
name = "TestTable"
conn = boto3.client("dynamodb", region_name="eu-west-2")
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "main_key", "KeyType": "HASH"}],
AttributeDefinitions=[
{"AttributeName": "main_key", "AttributeType": "S"},
{"AttributeName": "index_key", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
GlobalSecondaryIndexes=[
{
"IndexName": "test_index",
"KeySchema": [{"AttributeName": "index_key", "KeyType": "HASH"}],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1,
},
}
],
)
conn.put_item(
TableName=name,
Item={
"main_key": {"S": "testkey1"},
"extra_data": {"S": "testdata"},
"index_key": {"S": "indexkey1"},
},
)
with pytest.raises(ClientError) as ex:
conn.update_item(
TableName=name,
Key={"main_key": {"S": "testkey1"}},
UpdateExpression="set index_key=:new_index_key",
ExpressionAttributeValues={":new_index_key": {"S": ""}},
)
err = ex.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values are not valid. The update expression attempted to update a secondary index key to a value that is not supported. The AttributeValue for a key attribute cannot contain an empty string value."
)
@mock_dynamodb
def test_create_backup_for_non_existent_table_raises_error():
client = boto3.client("dynamodb", "us-east-1")
with pytest.raises(ClientError) as ex:
client.create_backup(TableName="non-existent", BackupName="backup")
error = ex.value.response["Error"]
assert error["Code"] == "TableNotFoundException"
assert error["Message"] == "Table not found: non-existent"
@mock_dynamodb
def test_create_backup():
client = boto3.client("dynamodb", "us-east-1")
table_name = "test-table"
client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
backup_name = "backup-test-table"
resp = client.create_backup(TableName=table_name, BackupName=backup_name)
details = resp.get("BackupDetails")
assert table_name in details["BackupArn"]
assert details["BackupName"] == backup_name
assert isinstance(details["BackupSizeBytes"], int)
assert "BackupStatus" in details
assert details["BackupType"] == "USER"
assert isinstance(details["BackupCreationDateTime"], datetime)
@mock_dynamodb
def test_create_multiple_backups_with_same_name():
client = boto3.client("dynamodb", "us-east-1")
table_name = "test-table"
client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
backup_name = "backup-test-table"
backup_arns = []
for _ in range(4):
backup = client.create_backup(TableName=table_name, BackupName=backup_name).get(
"BackupDetails"
)
assert backup["BackupName"] == backup_name
assert backup["BackupArn"] not in backup_arns
backup_arns.append(backup["BackupArn"])
@mock_dynamodb
def test_describe_backup_for_non_existent_backup_raises_error():
client = boto3.client("dynamodb", "us-east-1")
non_existent_arn = "arn:aws:dynamodb:us-east-1:123456789012:table/table-name/backup/01623095754481-2cfcd6f9"
with pytest.raises(ClientError) as ex:
client.describe_backup(BackupArn=non_existent_arn)
error = ex.value.response["Error"]
assert error["Code"] == "BackupNotFoundException"
assert error["Message"] == f"Backup not found: {non_existent_arn}"
@mock_dynamodb
def test_describe_backup():
client = boto3.client("dynamodb", "us-east-1")
table_name = "test-table"
table = client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
).get("TableDescription")
backup_name = "backup-test-table"
backup_arn = (
client.create_backup(TableName=table_name, BackupName=backup_name)
.get("BackupDetails")
.get("BackupArn")
)
resp = client.describe_backup(BackupArn=backup_arn)
description = resp.get("BackupDescription")
details = description.get("BackupDetails")
assert table_name in details["BackupArn"]
assert details["BackupName"] == backup_name
assert isinstance(details["BackupSizeBytes"], int)
assert "BackupStatus" in details
assert details["BackupType"] == "USER"
assert isinstance(details["BackupCreationDateTime"], datetime)
source = description.get("SourceTableDetails")
assert source["TableName"] == table_name
assert source["TableArn"] == table["TableArn"]
assert isinstance(source["TableSizeBytes"], int)
assert source["KeySchema"] == table["KeySchema"]
assert source["TableCreationDateTime"] == table["CreationDateTime"]
assert isinstance(source["ProvisionedThroughput"], dict)
assert source["ItemCount"] == table["ItemCount"]
@mock_dynamodb
def test_list_backups_for_non_existent_table():
client = boto3.client("dynamodb", "us-east-1")
resp = client.list_backups(TableName="non-existent")
assert len(resp["BackupSummaries"]) == 0
@mock_dynamodb
def test_list_backups():
client = boto3.client("dynamodb", "us-east-1")
table_names = ["test-table-1", "test-table-2"]
backup_names = ["backup-1", "backup-2"]
for table_name in table_names:
client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
for backup_name in backup_names:
client.create_backup(TableName=table_name, BackupName=backup_name)
resp = client.list_backups(BackupType="USER")
assert len(resp["BackupSummaries"]) == 4
for table_name in table_names:
resp = client.list_backups(TableName=table_name)
assert len(resp["BackupSummaries"]) == 2
for summary in resp["BackupSummaries"]:
assert summary["TableName"] == table_name
assert table_name in summary["TableArn"]
assert summary["BackupName"] in backup_names
assert "BackupArn" in summary
assert isinstance(summary["BackupCreationDateTime"], datetime)
assert "BackupStatus" in summary
assert summary["BackupType"] in ["USER", "SYSTEM"]
assert isinstance(summary["BackupSizeBytes"], int)
@mock_dynamodb
def test_restore_table_from_non_existent_backup_raises_error():
client = boto3.client("dynamodb", "us-east-1")
non_existent_arn = "arn:aws:dynamodb:us-east-1:123456789012:table/table-name/backup/01623095754481-2cfcd6f9"
with pytest.raises(ClientError) as ex:
client.restore_table_from_backup(
TargetTableName="from-backup", BackupArn=non_existent_arn
)
error = ex.value.response["Error"]
assert error["Code"] == "BackupNotFoundException"
assert error["Message"] == f"Backup not found: {non_existent_arn}"
@mock_dynamodb
def test_restore_table_from_backup_raises_error_when_table_already_exists():
client = boto3.client("dynamodb", "us-east-1")
table_name = "test-table"
client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
resp = client.create_backup(TableName=table_name, BackupName="backup")
backup = resp.get("BackupDetails")
with pytest.raises(ClientError) as ex:
client.restore_table_from_backup(
TargetTableName=table_name, BackupArn=backup["BackupArn"]
)
error = ex.value.response["Error"]
assert error["Code"] == "TableAlreadyExistsException"
assert error["Message"] == f"Table already exists: {table_name}"
@mock_dynamodb
def test_restore_table_from_backup():
client = boto3.client("dynamodb", "us-east-1")
table_name = "test-table"
resp = client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = resp.get("TableDescription")
for i in range(5):
client.put_item(TableName=table_name, Item={"id": {"S": f"item {i}"}})
backup_arn = (
client.create_backup(TableName=table_name, BackupName="backup")
.get("BackupDetails")
.get("BackupArn")
)
restored_table_name = "restored-from-backup"
restored = client.restore_table_from_backup(
TargetTableName=restored_table_name, BackupArn=backup_arn
).get("TableDescription")
assert restored["AttributeDefinitions"] == table["AttributeDefinitions"]
assert restored["TableName"] == restored_table_name
assert restored["KeySchema"] == table["KeySchema"]
assert "TableStatus" in restored
assert restored["ItemCount"] == 5
assert restored_table_name in restored["TableArn"]
assert isinstance(restored["RestoreSummary"], dict)
summary = restored.get("RestoreSummary")
assert summary["SourceBackupArn"] == backup_arn
assert summary["SourceTableArn"] == table["TableArn"]
assert isinstance(summary["RestoreDateTime"], datetime)
assert summary["RestoreInProgress"] is False
@mock_dynamodb
def test_restore_table_to_point_in_time():
client = boto3.client("dynamodb", "us-east-1")
table_name = "test-table"
resp = client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = resp.get("TableDescription")
for i in range(5):
client.put_item(TableName=table_name, Item={"id": {"S": f"item {i}"}})
restored_table_name = "restored-from-pit"
restored = client.restore_table_to_point_in_time(
TargetTableName=restored_table_name, SourceTableName=table_name
).get("TableDescription")
assert restored["TableName"] == restored_table_name
assert restored["KeySchema"] == table["KeySchema"]
assert "TableStatus" in restored
assert restored["ItemCount"] == 5
assert restored_table_name in restored["TableArn"]
assert isinstance(restored["RestoreSummary"], dict)
summary = restored.get("RestoreSummary")
assert summary["SourceTableArn"] == table["TableArn"]
assert isinstance(summary["RestoreDateTime"], datetime)
assert summary["RestoreInProgress"] is False
@mock_dynamodb
def test_restore_table_to_point_in_time_raises_error_when_source_not_exist():
client = boto3.client("dynamodb", "us-east-1")
table_name = "test-table"
restored_table_name = "restored-from-pit"
with pytest.raises(ClientError) as ex:
client.restore_table_to_point_in_time(
TargetTableName=restored_table_name, SourceTableName=table_name
)
error = ex.value.response["Error"]
assert error["Code"] == "SourceTableNotFoundException"
assert error["Message"] == f"Source table not found: {table_name}"
@mock_dynamodb
def test_restore_table_to_point_in_time_raises_error_when_dest_exist():
client = boto3.client("dynamodb", "us-east-1")
table_name = "test-table"
restored_table_name = "restored-from-pit"
client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
client.create_table(
TableName=restored_table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
with pytest.raises(ClientError) as ex:
client.restore_table_to_point_in_time(
TargetTableName=restored_table_name, SourceTableName=table_name
)
error = ex.value.response["Error"]
assert error["Code"] == "TableAlreadyExistsException"
assert error["Message"] == f"Table already exists: {restored_table_name}"
@mock_dynamodb
def test_delete_non_existent_backup_raises_error():
client = boto3.client("dynamodb", "us-east-1")
non_existent_arn = "arn:aws:dynamodb:us-east-1:123456789012:table/table-name/backup/01623095754481-2cfcd6f9"
with pytest.raises(ClientError) as ex:
client.delete_backup(BackupArn=non_existent_arn)
error = ex.value.response["Error"]
assert error["Code"] == "BackupNotFoundException"
assert error["Message"] == f"Backup not found: {non_existent_arn}"
@mock_dynamodb
def test_delete_backup():
client = boto3.client("dynamodb", "us-east-1")
table_name = "test-table-1"
backup_names = ["backup-1", "backup-2"]
client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
for backup_name in backup_names:
client.create_backup(TableName=table_name, BackupName=backup_name)
resp = client.list_backups(TableName=table_name, BackupType="USER")
assert len(resp["BackupSummaries"]) == 2
backup_to_delete = resp["BackupSummaries"][0]["BackupArn"]
backup_deleted = client.delete_backup(BackupArn=backup_to_delete).get(
"BackupDescription"
)
assert "SourceTableDetails" in backup_deleted
assert "BackupDetails" in backup_deleted
details = backup_deleted["BackupDetails"]
assert details["BackupArn"] == backup_to_delete
assert details["BackupName"] in backup_names
assert details["BackupStatus"] == "DELETED"
resp = client.list_backups(TableName=table_name, BackupType="USER")
assert len(resp["BackupSummaries"]) == 1
@mock_dynamodb
def test_source_and_restored_table_items_are_not_linked():
client = boto3.client("dynamodb", "us-east-1")
def add_guids_to_table(table, num_items):
guids = []
for _ in range(num_items):
guid = str(uuid.uuid4())
client.put_item(TableName=table, Item={"id": {"S": guid}})
guids.append(guid)
return guids
source_table_name = "source-table"
client.create_table(
TableName=source_table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
guids_original = add_guids_to_table(source_table_name, 5)
backup_arn = (
client.create_backup(TableName=source_table_name, BackupName="backup")
.get("BackupDetails")
.get("BackupArn")
)
guids_added_after_backup = add_guids_to_table(source_table_name, 5)
restored_table_name = "restored-from-backup"
client.restore_table_from_backup(
TargetTableName=restored_table_name, BackupArn=backup_arn
)
guids_added_after_restore = add_guids_to_table(restored_table_name, 5)
source_table_items = client.scan(TableName=source_table_name)
assert source_table_items["Count"] == 10
source_table_guids = [x["id"]["S"] for x in source_table_items["Items"]]
assert set(source_table_guids) == set(guids_original) | set(
guids_added_after_backup
)
restored_table_items = client.scan(TableName=restored_table_name)
assert restored_table_items["Count"] == 10
restored_table_guids = [x["id"]["S"] for x in restored_table_items["Items"]]
assert set(restored_table_guids) == set(guids_original) | set(
guids_added_after_restore
)
@mock_dynamodb
@pytest.mark.parametrize("region", ["eu-central-1", "ap-south-1"])
def test_describe_endpoints(region):
client = boto3.client("dynamodb", region)
res = client.describe_endpoints()["Endpoints"]
assert res == [
{
"Address": f"dynamodb.{region}.amazonaws.com",
"CachePeriodInMinutes": 1440,
},
]
@mock_dynamodb
def test_update_non_existing_item_raises_error_and_does_not_contain_item_afterwards():
"""
https://github.com/getmoto/moto/issues/3729
Exception is raised, but item was persisted anyway
Happened because we would create a placeholder, before validating/executing the UpdateExpression
:return:
"""
name = "TestTable"
conn = boto3.client("dynamodb", region_name="us-west-2")
hkey = "primary_partition_key"
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": hkey, "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": hkey, "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
update_expression = {
"Key": {hkey: "some_identification_string"},
"UpdateExpression": "set #AA.#AB = :aa",
"ExpressionAttributeValues": {":aa": "abc"},
"ExpressionAttributeNames": {"#AA": "some_dict", "#AB": "key1"},
"ConditionExpression": "attribute_not_exists(#AA.#AB)",
}
table = boto3.resource("dynamodb", region_name="us-west-2").Table(name)
with pytest.raises(ClientError) as err:
table.update_item(**update_expression)
assert err.value.response["Error"]["Code"] == "ValidationException"
assert len(conn.scan(TableName=name)["Items"]) == 0
@mock_dynamodb
def test_batch_write_item():
conn = boto3.resource("dynamodb", region_name="us-west-2")
tables = [f"table-{i}" for i in range(3)]
for name in tables:
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
conn.batch_write_item(
RequestItems={
tables[0]: [{"PutRequest": {"Item": {"id": "0"}}}],
tables[1]: [{"PutRequest": {"Item": {"id": "1"}}}],
tables[2]: [{"PutRequest": {"Item": {"id": "2"}}}],
}
)
for idx, name in enumerate(tables):
table = conn.Table(f"table-{idx}")
res = table.get_item(Key={"id": str(idx)})
assert res["Item"] == {"id": str(idx)}
assert table.scan()["Count"] == 1
conn.batch_write_item(
RequestItems={
tables[0]: [{"DeleteRequest": {"Key": {"id": "0"}}}],
tables[1]: [{"DeleteRequest": {"Key": {"id": "1"}}}],
tables[2]: [{"DeleteRequest": {"Key": {"id": "2"}}}],
}
)
for idx, name in enumerate(tables):
table = conn.Table(f"table-{idx}")
assert table.scan()["Count"] == 0
@mock_dynamodb
def test_gsi_lastevaluatedkey():
# github.com/getmoto/moto/issues/3968
conn = boto3.resource("dynamodb", region_name="us-west-2")
name = "test-table"
table = conn.Table(name)
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "main_key", "KeyType": "HASH"}],
AttributeDefinitions=[
{"AttributeName": "main_key", "AttributeType": "S"},
{"AttributeName": "index_key", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
GlobalSecondaryIndexes=[
{
"IndexName": "test_index",
"KeySchema": [{"AttributeName": "index_key", "KeyType": "HASH"}],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1,
},
}
],
)
table.put_item(
Item={
"main_key": "testkey1",
"extra_data": "testdata",
"index_key": "indexkey",
}
)
table.put_item(
Item={
"main_key": "testkey2",
"extra_data": "testdata",
"index_key": "indexkey",
}
)
response = table.query(
Limit=1,
KeyConditionExpression=Key("index_key").eq("indexkey"),
IndexName="test_index",
)
items = response["Items"]
assert len(items) == 1
assert items[0] == {
"main_key": "testkey1",
"extra_data": "testdata",
"index_key": "indexkey",
}
last_evaluated_key = response["LastEvaluatedKey"]
assert len(last_evaluated_key) == 2
assert last_evaluated_key == {"main_key": "testkey1", "index_key": "indexkey"}
@mock_dynamodb
def test_filter_expression_execution_order():
# As mentioned here: https://github.com/getmoto/moto/issues/3909
# and documented here: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.FilterExpression
# the filter expression should be evaluated after the query.
# The same applies to scan operations:
# https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.FilterExpression
# If we set limit=1 and apply a filter expression whixh excludes the first result
# then we should get no items in response.
conn = boto3.resource("dynamodb", region_name="us-west-2")
name = "test-filter-expression-table"
table = conn.Table(name)
conn.create_table(
TableName=name,
KeySchema=[
{"AttributeName": "hash_key", "KeyType": "HASH"},
{"AttributeName": "range_key", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "hash_key", "AttributeType": "S"},
{"AttributeName": "range_key", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table.put_item(
Item={"hash_key": "keyvalue", "range_key": "A", "filtered_attribute": "Y"}
)
table.put_item(
Item={"hash_key": "keyvalue", "range_key": "B", "filtered_attribute": "Z"}
)
# test query
query_response_1 = table.query(
Limit=1,
KeyConditionExpression=Key("hash_key").eq("keyvalue"),
FilterExpression=Attr("filtered_attribute").eq("Z"),
)
query_items_1 = query_response_1["Items"]
assert len(query_items_1) == 0
query_last_evaluated_key = query_response_1["LastEvaluatedKey"]
assert len(query_last_evaluated_key) == 2
assert query_last_evaluated_key == {"hash_key": "keyvalue", "range_key": "A"}
query_response_2 = table.query(
Limit=1,
KeyConditionExpression=Key("hash_key").eq("keyvalue"),
FilterExpression=Attr("filtered_attribute").eq("Z"),
ExclusiveStartKey=query_last_evaluated_key,
)
query_items_2 = query_response_2["Items"]
assert len(query_items_2) == 1
assert query_items_2[0] == {
"hash_key": "keyvalue",
"filtered_attribute": "Z",
"range_key": "B",
}
# test scan
scan_response_1 = table.scan(
Limit=1, FilterExpression=Attr("filtered_attribute").eq("Z")
)
scan_items_1 = scan_response_1["Items"]
assert len(scan_items_1) == 0
scan_last_evaluated_key = scan_response_1["LastEvaluatedKey"]
assert len(scan_last_evaluated_key) == 2
assert scan_last_evaluated_key == {"hash_key": "keyvalue", "range_key": "A"}
scan_response_2 = table.scan(
Limit=1,
FilterExpression=Attr("filtered_attribute").eq("Z"),
ExclusiveStartKey=query_last_evaluated_key,
)
scan_items_2 = scan_response_2["Items"]
assert scan_items_2 == [
{"hash_key": "keyvalue", "filtered_attribute": "Z", "range_key": "B"}
]
@mock_dynamodb
def test_projection_expression_execution_order():
# projection expression needs to be applied after calculation of
# LastEvaluatedKey as it is possible for LastEvaluatedKey to
# include attributes which are not projected.
conn = boto3.resource("dynamodb", region_name="us-west-2")
name = "test-projection-expression-with-gsi"
table = conn.Table(name)
conn.create_table(
TableName=name,
KeySchema=[
{"AttributeName": "hash_key", "KeyType": "HASH"},
{"AttributeName": "range_key", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "hash_key", "AttributeType": "S"},
{"AttributeName": "range_key", "AttributeType": "S"},
{"AttributeName": "index_key", "AttributeType": "S"},
],
GlobalSecondaryIndexes=[
{
"IndexName": "test_index",
"KeySchema": [{"AttributeName": "index_key", "KeyType": "HASH"}],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1,
},
}
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table.put_item(Item={"hash_key": "keyvalue", "range_key": "A", "index_key": "Z"})
table.put_item(Item={"hash_key": "keyvalue", "range_key": "B", "index_key": "Z"})
# test query
# if projection expression is applied before LastEvaluatedKey is computed
# then this raises an exception.
table.query(
Limit=1,
IndexName="test_index",
KeyConditionExpression=Key("index_key").eq("Z"),
ProjectionExpression="#a",
ExpressionAttributeNames={"#a": "hashKey"},
)
# if projection expression is applied before LastEvaluatedKey is computed
# then this raises an exception.
table.scan(
Limit=1,
IndexName="test_index",
ProjectionExpression="#a",
ExpressionAttributeNames={"#a": "hashKey"},
)
@mock_dynamodb
def test_projection_expression_with_binary_attr():
dynamo_resource = boto3.resource("dynamodb", region_name="us-east-1")
dynamo_resource.create_table(
TableName="test",
AttributeDefinitions=[
{"AttributeName": "pk", "AttributeType": "S"},
{"AttributeName": "sk", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "pk", "KeyType": "HASH"},
{"AttributeName": "sk", "KeyType": "RANGE"},
],
BillingMode="PAY_PER_REQUEST",
)
table = dynamo_resource.Table("test")
table.put_item(Item={"pk": "pk", "sk": "sk", "key": b"value\xbf"})
item = table.get_item(
Key={"pk": "pk", "sk": "sk"},
ExpressionAttributeNames={"#key": "key"},
ProjectionExpression="#key",
)["Item"]
assert item == {"key": Binary(b"value\xbf")}
item = table.scan()["Items"][0]
assert item["key"] == Binary(b"value\xbf")
item = table.query(KeyConditionExpression=Key("pk").eq("pk"))["Items"][0]
assert item["key"] == Binary(b"value\xbf")
@mock_dynamodb
def test_invalid_projection_expressions():
table_name = "test-projection-expressions-table"
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "customer", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "customer", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
with pytest.raises(
ClientError,
match="ProjectionExpression: Attribute name is a reserved keyword; reserved keyword: name",
):
client.scan(TableName=table_name, ProjectionExpression="name")
with pytest.raises(
ClientError, match="ProjectionExpression: Attribute name starts with a number"
):
client.scan(TableName=table_name, ProjectionExpression="3ame")
with pytest.raises(
ClientError, match="ProjectionExpression: Attribute name contains white space"
):
client.scan(TableName=table_name, ProjectionExpression="na me")
with pytest.raises(
ClientError,
match="ProjectionExpression: Attribute name is a reserved keyword; reserved keyword: name",
):
client.get_item(
TableName=table_name,
Key={"customer": {"S": "a"}},
ProjectionExpression="name",
)
with pytest.raises(
ClientError,
match="ProjectionExpression: Attribute name is a reserved keyword; reserved keyword: name",
):
client.query(
TableName=table_name,
KeyConditionExpression="a",
ProjectionExpression="name",
)
with pytest.raises(
ClientError,
match="ProjectionExpression: Attribute name is a reserved keyword; reserved keyword: name",
):
client.scan(TableName=table_name, ProjectionExpression="not_a_keyword, name")
with pytest.raises(
ClientError, match="ProjectionExpression: Attribute name starts with a number"
):
client.scan(TableName=table_name, ProjectionExpression="not_a_keyword, 3ame")
with pytest.raises(
ClientError, match="ProjectionExpression: Attribute name contains white space"
):
client.scan(TableName=table_name, ProjectionExpression="not_a_keyword, na me")