import uuid from datetime import datetime from decimal import Decimal import boto3 from boto3.dynamodb.conditions import Attr, Key import re import sure # noqa # pylint: disable=unused-import from moto import mock_dynamodb, settings from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID from moto.dynamodb import dynamodb_backends from botocore.exceptions import ClientError import moto.dynamodb.comparisons import moto.dynamodb.models import pytest @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}, ) conn.list_tables()["TableNames"].should.equal(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) res.should.have.key("TableNames").equal(["name1", "name2"]) res.should.have.key("LastEvaluatedTableName").equal("name2") res = conn.list_tables(Limit=1, ExclusiveStartTableName="name1") res.should.have.key("TableNames").equal(["name2"]) res.should.have.key("LastEvaluatedTableName").equal("name2") res = conn.list_tables(ExclusiveStartTableName="name1") res.should.have.key("TableNames").equal(["name2", "name3"]) res.shouldnt.have.key("LastEvaluatedTableName") @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") ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException") ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) ex.value.response["Error"]["Message"].should.equal( "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"}, }, ) ex.value.response["Error"]["Code"].should.equal("ValidationException") ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) ex.value.response["Error"]["Message"].should.match( "One or more parameter values were invalid: An AttributeValue may not contain an empty string" ) @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": ""}, }, ) ex.value.response["Error"]["Code"].should.equal("ValidationException") ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) ex.value.response["Error"]["Message"].should.match( "One or more parameter values were invalid: An AttributeValue may not contain an empty string" ) @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"}}) result["Item"].should.be.equal( { "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) len(table.stream_shard.items).should.be.equal(1) stream_record = table.stream_shard.items[0].record stream_record["eventName"].should.be.equal("INSERT") stream_record["dynamodb"]["SizeBytes"].should.be.equal(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", ) result["Item"].should.be.equal({"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"}) result["Item"].should.be.equal( {"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"]] bodies.should.contain("some test message") bodies.should.contain("yet another test message") 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"] result.should.equal( {"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"] result.should.equal( { "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": "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 query returning all items result = table.query( KeyConditionExpression=Key("forum_name").eq("key1"), ProjectionExpression="nested.level1.id, nested.level2", )["Items"][0] assert "nested" in result result["nested"].should.equal( {"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("forum_name").eq("key1"))["Items"][ 0 ] result.should.equal( { "foo": "bar", "forum_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"] results.should.equal( [ { "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"] results.should.equal( [ { "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"}, ) result["Item"].should.be.equal( {"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 "body" in results["Items"][0] assert results["Items"][0]["body"] == "some test message" assert "subject" in results["Items"][0] assert results["Items"][0]["subject"] == "123" assert "attachment" in results["Items"][0] 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"}, "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="#nst.level1.id, #nst.#lvl2", ExpressionAttributeNames={"#nst": "nested", "#lvl2": "level2"}, )["Item"] result.should.equal( {"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"] result.should.equal( { "foo": "bar", "forum_name": "key1", "nested": { "level1": {"id": "id1", "att": "irrelevant"}, "level2": {"id": "id2", "include": "all"}, "level3": {"id": "irrelevant"}, }, } ) @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": "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 query returning all items result = table.query( KeyConditionExpression=Key("forum_name").eq("key1"), ProjectionExpression="#nst.level1.id, #nst.#lvl2", ExpressionAttributeNames={"#nst": "nested", "#lvl2": "level2"}, )["Items"][0] assert "nested" in result result["nested"].should.equal( {"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("forum_name").eq("key1"))["Items"][ 0 ] result.should.equal( { "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_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"] results.should.equal( [ { "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"] results.should.equal( [ { "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={}) ex.value.response["Error"]["Message"].should.equal( "One or more parameter values were invalid: Missing the key structure_id in the item" ) ex.value.response["Error"]["Code"].should.equal("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"}) ex.value.response["Error"]["Message"].should.equal( "One or more parameter values were invalid: Missing the key structure_id in the item" ) ex.value.response["Error"]["Code"].should.equal("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"}) ex.value.response["Error"]["Message"].should.equal( "One or more parameter values were invalid: Missing the key added_at in the item" ) ex.value.response["Error"]["Code"].should.equal("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)", {}, {} ) filter_expr.expr(row1).should.be(True) # NOT test 2 filter_expr = moto.dynamodb.comparisons.get_filter_expression( "NOT (Id = :v0)", {}, {":v0": {"N": "8"}} ) filter_expr.expr(row1).should.be(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"}} ) filter_expr.expr(row1).should.be(True) filter_expr.expr(row2).should.be(False) # lowercase AND test filter_expr = moto.dynamodb.comparisons.get_filter_expression( "Id > :v0 and Subs < :v1", {}, {":v0": {"N": "5"}, ":v1": {"N": "7"}} ) filter_expr.expr(row1).should.be(True) filter_expr.expr(row2).should.be(False) # OR test filter_expr = moto.dynamodb.comparisons.get_filter_expression( "Id = :v0 OR Id=:v1", {}, {":v0": {"N": "5"}, ":v1": {"N": "8"}} ) filter_expr.expr(row1).should.be(True) # BETWEEN test filter_expr = moto.dynamodb.comparisons.get_filter_expression( "Id BETWEEN :v0 AND :v1", {}, {":v0": {"N": "5"}, ":v1": {"N": "10"}} ) filter_expr.expr(row1).should.be(True) # BETWEEN integer test filter_expr = moto.dynamodb.comparisons.get_filter_expression( "Id BETWEEN :v0 AND :v1", {}, {":v0": {"N": "0"}, ":v1": {"N": "10"}} ) filter_expr.expr(row1).should.be(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"}}, ) filter_expr.expr(row1).should.be(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"}}, ) filter_expr.expr(row1).should.be(True) # attribute function tests (with extra spaces) filter_expr = moto.dynamodb.comparisons.get_filter_expression( "attribute_exists(Id) AND attribute_not_exists (UnknownAttribute)", {}, {} ) filter_expr.expr(row1).should.be(True) filter_expr = moto.dynamodb.comparisons.get_filter_expression( "attribute_type(Id, :v0)", {}, {":v0": {"S": "N"}} ) filter_expr.expr(row1).should.be(True) # beginswith function test filter_expr = moto.dynamodb.comparisons.get_filter_expression( "begins_with(Des, :v0)", {}, {":v0": {"S": "Some"}} ) filter_expr.expr(row1).should.be(True) filter_expr.expr(row2).should.be(False) # contains function test filter_expr = moto.dynamodb.comparisons.get_filter_expression( "contains(KV, :v0)", {}, {":v0": {"S": "test1"}} ) filter_expr.expr(row1).should.be(True) filter_expr.expr(row2).should.be(False) # size function test filter_expr = moto.dynamodb.comparisons.get_filter_expression( "size(Des) > size(KV)", {}, {} ) filter_expr.expr(row1).should.be(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"}}, ) filter_expr.expr(row1).should.be(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"}} ) filter_expr.expr(row1).should.be(True) filter_expr.expr(row2).should.be(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 response = table.query( KeyConditionExpression=Key("client").eq("client1"), FilterExpression=Attr("app").eq("app2"), ) assert response["Count"] == 1 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 table.scan(FilterExpression=Attr("my-attr").lt(43))["Items"].should.equal([item]) table.scan(FilterExpression=Attr("my-attr").lte(42))["Items"].should.equal([item]) table.scan(FilterExpression=Attr("my-attr").gte(42))["Items"].should.equal([item]) table.scan(FilterExpression=Attr("my-attr").gt(41))["Items"].should.equal([item]) # Sanity check that we can't find the item if the FE is wrong table.scan(FilterExpression=Attr("my-attr").gt(43))["Items"].should.equal([]) @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") exc.value.response["Error"]["Code"].should.equal("ValidationException") @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}, ) try: 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}, ) except ClientError as err: err.response["Error"]["Code"].should.equal("ResourceInUseException") else: raise RuntimeError("Should have raised 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() len(resp["TableNames"]).should.equal(0) try: client.delete_table(TableName="test1") except ClientError as err: err.response["Error"]["Code"].should.equal("ResourceNotFoundException") else: raise RuntimeError("Should have raised 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"] err["Code"].should.equal("ValidationException") err["Message"].should.equal("Return values set to invalid value") # Test deletion and returning old value response = table.delete_item( Key={"client": "client1", "app": "app1"}, ReturnValues="ALL_OLD" ) response["Attributes"].should.contain("client") response["Attributes"].should.contain("app") response = table.scan() assert response["Count"] == 1 # Test deletion returning nothing response = table.delete_item(Key={"client": "client1", "app": "app2"}) len(response["Attributes"]).should.equal(0) response = table.scan() assert response["Count"] == 0 @mock_dynamodb def test_describe_limits(): client = boto3.client("dynamodb", region_name="eu-central-1") resp = client.describe_limits() resp["AccountMaxReadCapacityUnits"].should.equal(20000) resp["AccountMaxWriteCapacityUnits"].should.equal(20000) resp["TableMaxWriteCapacityUnits"].should.equal(10000) resp["TableMaxReadCapacityUnits"].should.equal(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") resp["TimeToLiveDescription"]["TimeToLiveStatus"].should.equal("ENABLED") resp["TimeToLiveDescription"]["AttributeName"].should.equal("expire") client.update_time_to_live( TableName="test1", TimeToLiveSpecification={"Enabled": False, "AttributeName": "expire"}, ) resp = client.describe_time_to_live(TableName="test1") resp["TimeToLiveDescription"]["TimeToLiveStatus"].should.equal("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 response["ContinuousBackupsDescription"].should.equal( { "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(Exception) as e: client.describe_continuous_backups(TableName="not-existing-table") # then ex = e.value ex.operation_name.should.equal("DescribeContinuousBackups") ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) ex.response["Error"]["Code"].should.contain("TableNotFoundException") ex.response["Error"]["Message"].should.equal("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 response["ContinuousBackupsDescription"]["ContinuousBackupsStatus"].should.equal( "ENABLED" ) point_in_time = response["ContinuousBackupsDescription"][ "PointInTimeRecoveryDescription" ] earliest_datetime = point_in_time["EarliestRestorableDateTime"] earliest_datetime.should.be.a(datetime) latest_datetime = point_in_time["LatestRestorableDateTime"] latest_datetime.should.be.a(datetime) point_in_time["PointInTimeRecoveryStatus"].should.equal("ENABLED") # when # a second update should not change anything response = client.update_continuous_backups( TableName=table_name, PointInTimeRecoverySpecification={"PointInTimeRecoveryEnabled": True}, ) # then response["ContinuousBackupsDescription"]["ContinuousBackupsStatus"].should.equal( "ENABLED" ) point_in_time = response["ContinuousBackupsDescription"][ "PointInTimeRecoveryDescription" ] point_in_time["EarliestRestorableDateTime"].should.equal(earliest_datetime) point_in_time["LatestRestorableDateTime"].should.equal(latest_datetime) point_in_time["PointInTimeRecoveryStatus"].should.equal("ENABLED") # when response = client.update_continuous_backups( TableName=table_name, PointInTimeRecoverySpecification={"PointInTimeRecoveryEnabled": False}, ) # then response["ContinuousBackupsDescription"].should.equal( { "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(Exception) as e: client.update_continuous_backups( TableName="not-existing-table", PointInTimeRecoverySpecification={"PointInTimeRecoveryEnabled": True}, ) # then ex = e.value ex.operation_name.should.equal("UpdateContinuousBackups") ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) ex.response["Error"]["Code"].should.contain("TableNotFoundException") ex.response["Error"]["Message"].should.equal("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"}}, ) resp["Count"].should.equal(1) resp["Items"][0]["client"]["S"].should.equal("test1") resp = client.query( TableName="test1", KeyConditionExpression=":name=test2", ExpressionAttributeNames={":name": "client"}, ) resp["Count"].should.equal(1) resp["Items"][0]["client"]["S"].should.equal("test2") # 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"}) resp["Item"].should.equal({"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"}) resp["Item"].should.equal({"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() resp["Items"][0]["body"].should.equal({"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() resp["Items"][0]["body"].should.equal( {"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"] err["Code"].should.equal("ValidationException") err["Message"].should.equal("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"] err["Code"].should.equal("ValidationException") err["Message"].should.equal("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", ) ex.value.response["Error"]["Code"].should.equal("ValidationException") ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) ex.value.response["Error"]["Message"].should.equal( "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"}, }, ) res.should.have.key("Count").equal(1) res.should.have.key("Items") res["Items"][0].should.equal( { "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") ex.value.response["Error"]["Code"].should.equal("ValidationException") ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) ex.value.response["Error"]["Message"].should.equal( "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", ) ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException") ex.value.response["Error"]["Message"].should.equal( "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", ) ex.value.response["Error"]["Code"].should.equal("ValidationException") ex.value.response["Error"]["Message"].should.contain(expected_exception) @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"] result["id"].should.equal({"S": "foo"}) result["itemlist"].should.equal( {"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"] result["id"].should.equal({"S": "foo2"}) result["itemmap"]["M"]["itemlist"]["L"].should.equal( [{"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"} len(result["itemmap"]["M"]["itemlist"]["L"]).should.equal(2) result["itemmap"]["M"]["itemlist"]["L"][0].should.equal( {"M": {"foo": {"S": "bar11"}, "foos": {"S": "bar12"}}} ) # unchanged result["itemmap"]["M"]["itemlist"]["L"][1].should.equal( {"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"] ex.value.response["Error"]["Code"].should.equal("ValidationException") ex.value.response["Error"]["Message"].should.equal( "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"] result.should.equal({"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"] result.should.equal(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"] result["id"].should.equal({"S": "foo"}) result["itemlist"].should.equal({"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"] result["id"].should.equal({"S": "foo2"}) result["itemmap"]["M"]["itemlist"]["L"].should.equal([{"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"} assert result["itemmap"]["M"]["itemlist"]["L"][0]["M"].should.equal( {"foo00": {"S": "bar1"}, "foo01": {"S": "bar2"}} ) # untouched assert result["itemmap"]["M"]["itemlist"]["L"][1]["M"].should.equal( {"foo11": {"S": "bar2"}} ) # changed @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) ex.value.response["Error"]["Code"].should.equal("ValidationException") ex.value.response["Error"]["Message"].should.equal( "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) ex.value.response["Error"]["Code"].should.equal("ValidationException") ex.value.response["Error"]["Message"].should.equal( "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"] result.should.equal( { "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 updated_item["Attributes"].should.equal( {"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"] result.should.equal( { "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 updated_item["Attributes"].should.equal( {"a": {"M": {"b": {"L": [{"S": "bar1"}, {"S": "bar2"}]}}}} ) result = client.get_item( TableName="TestTable", Key={"id": {"S": "nested_list_append"}} )["Item"] result.should.equal( { "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 updated_item["Attributes"].should.equal( {"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"] result.should.equal( { "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 updated_item["Attributes"].should.equal( {"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"] result.should.equal( { "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 updated_item["Attributes"].should.equal( {"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"] result.should.equal( [ { "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 updated_item["Attributes"].should.equal( {"nest1": {"nest2": {"event_history": ["some_value"]}}} ) table.get_item(Key={"Id": "item-id"})["Item"].should.equal( {"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 updated_item["Attributes"].should.equal( {"event_history": ["other_value", "some_value"]} ) table.get_item(Key={"Id": "item-id"})["Item"].should.equal( {"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"}, ) table.scan()["Items"][0]["job_name"].should.equal("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 updated_item["Attributes"].should.equal({"job_details": {"job_name": "updated"}}) table.scan()["Items"][0]["job_details"]["job_name"].should.equal("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 updated_item["Attributes"].should.equal({"job_details": {"job_name": "updated"}}) table.get_item(Key={"job_id": "a"})["Item"]["job_details"][ "job_name" ].should.be.equal("updated") table.get_item(Key={"job_id": "b"})["Item"]["job_details"][ "job_name" ].should.be.equal({"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") ex.value.response["Error"]["Code"].should.equal("ValidationException") ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) ex.value.response["Error"]["Message"].should.equal( "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) ] ) ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) ex.value.response["Error"]["Message"].should.match( r"failed to satisfy constraint: Member must have length less than or equal to 25", re.I, ) 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"}}, ] ) ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException") ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) ex.value.response["Error"]["Message"].should.equal("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", } }, ] ) res["Responses"][0]["Item"].should.equal({"id": {"S": "1"}, "sort_key": {"S": "1"}}) len(res["Responses"]).should.equal(2) res["Responses"][1].should.equal({}) 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", } }, ] ) res["Responses"][0]["Item"].should.equal({"id": {"S": "1"}, "sort_key": {"S": "1"}}) res["Responses"][1]["Item"].should.equal({"id": {"S": "1"}, "sort_key": {"S": "2"}}) res["Responses"][2]["Item"].should.equal({"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", ) res["ConsumedCapacity"][0].should.equal( {"TableName": "test1", "CapacityUnits": 4.0, "ReadCapacityUnits": 4.0} ) res["ConsumedCapacity"][1].should.equal( {"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", ) res["ConsumedCapacity"][0].should.equal( { "TableName": "test1", "CapacityUnits": 4.0, "ReadCapacityUnits": 4.0, "Table": {"CapacityUnits": 4.0, "ReadCapacityUnits": 4.0}, } ) res["ConsumedCapacity"][1].should.equal( { "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 [float(item["gsiK1SortKey"]) for item in resp["Items"]].should.equal( [-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"] items.should.have.length_of(5) @mock_dynamodb def test_transact_write_items_put_conditional_expressions(): 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 ) dynamodb.put_item(TableName="test-table", Item={"id": {"S": "foo2"}}) # Put multiple items with pytest.raises(ClientError) as ex: dynamodb.transact_write_items( TransactItems=[ { "Put": { "Item": { "id": {"S": f"foo{i}"}, "foo": {"S": "bar"}, }, "TableName": "test-table", "ConditionExpression": "#i <> :i", "ExpressionAttributeNames": {"#i": "id"}, "ExpressionAttributeValues": { ":i": { "S": "foo2" } # This item already exist, so the ConditionExpression should fail }, } } for i in range(0, 5) ] ) # Assert the exception is correct ex.value.response["Error"]["Code"].should.equal("TransactionCanceledException") reasons = ex.value.response["CancellationReasons"] reasons.should.have.length_of(5) reasons.should.contain( { "Code": "ConditionalCheckFailed", "Message": "The conditional request failed", "Item": {"id": {"S": "foo2"}, "foo": {"S": "bar"}}, } ) reasons.should.contain({"Code": "None"}) ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) # Assert all are present items = dynamodb.scan(TableName="test-table")["Items"] items.should.have.length_of(1) items[0].should.equal({"id": {"S": "foo2"}}) @mock_dynamodb def test_transact_write_items_put_conditional_expressions_return_values_on_condition_check_failure_all_old(): 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 ) dynamodb.put_item(TableName="test-table", Item={"id": {"S": "foo2"}}) # Put multiple items with pytest.raises(ClientError) as ex: dynamodb.transact_write_items( TransactItems=[ { "Put": { "Item": { "id": {"S": f"foo{i}"}, "foo": {"S": "bar"}, }, "TableName": "test-table", "ConditionExpression": "#i <> :i", "ExpressionAttributeNames": {"#i": "id"}, "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 ex.value.response["Error"]["Code"].should.equal("TransactionCanceledException") reasons = ex.value.response["CancellationReasons"] reasons.should.have.length_of(5) reasons.should.contain( { "Code": "ConditionalCheckFailed", "Message": "The conditional request failed", "Item": {"id": {"S": "foo2"}}, } ) reasons.should.contain({"Code": "None"}) ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) # Assert all are present items = dynamodb.scan(TableName="test-table")["Items"] items.should.have.length_of(1) items[0].should.equal({"id": {"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"] items.should.have.length_of(1) items[0].should.equal({"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 ex.value.response["Error"]["Code"].should.equal("TransactionCanceledException") ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) # Assert the original email address is still present items = dynamodb.scan(TableName="test-table")["Items"] items.should.have.length_of(1) items[0].should.equal({"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"] items.should.have.length_of(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"] items.should.have.length_of(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 ex.value.response["Error"]["Code"].should.equal("TransactionCanceledException") ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) # Assert the original item is still present items = dynamodb.scan(TableName="test-table")["Items"] items.should.have.length_of(1) items[0].should.equal({"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"] items.should.have.length_of(1) items[0].should.equal({"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 ex.value.response["Error"]["Code"].should.equal("TransactionCanceledException") ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) # Assert the original item is still present items = dynamodb.scan(TableName="test-table")["Items"] items.should.have.length_of(1) items[0].should.equal({"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 len(items).should.be.greater_than(response["Count"]) response["LastEvaluatedKey"].shouldnt.be(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.response["Error"]["Code"] == "ValidationException" assert expected_syntax_error == client_error.response["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", ) try: dynamodb.update_item( TableName="moto-test", Key={"id": {"S": "1"}}, UpdateExpression="SET MyStr = myNum + 1", ) assert False, "Validation exception not thrown" except dynamodb.exceptions.ClientError as e: assert_raise_syntax_error(e, "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", ) try: dynamodb.update_item( TableName="moto-test", Key={"id": {"S": "1"}}, UpdateExpression="SET MyStr = myNum Mystr2 myNum2", ) assert False, "Validation exception not thrown" except dynamodb.exceptions.ClientError as e: assert_raise_syntax_error(e, "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") len(resp["TableNames"]).should.equal(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.*){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"}}, ) 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_raise_syntax_error(e, "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"] updated_item["n_i"]["N"].should.equal("6.2") updated_item["n_f"]["N"].should.equal("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", ) assert ( "v" in response["Attributes"] ), "v has been updated, and should be returned here" response["Attributes"]["v"]["N"].should.equal("5") # second update response = ddb_mock.update_item( TableName=table, Key=key, UpdateExpression="set v = v + :inc", ExpressionAttributeValues={":inc": {"N": "1"}}, ReturnValues="UPDATED_OLD", ) assert ( "v" in response["Attributes"] ), "v has been updated, and should be returned here" response["Attributes"]["v"]["N"].should.equal("6") # third update response = ddb_mock.update_item( TableName=table, Key=key, UpdateExpression="set v = v + :inc", ExpressionAttributeValues={":inc": {"N": "1"}}, ReturnValues="UPDATED_NEW", ) assert ( "v" in response["Attributes"] ), "v has been updated, and should be returned here" response["Attributes"]["v"]["N"].should.equal("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"}}, } }, ] ) ex.value.response["Error"]["Code"].should.equal("TransactionCanceledException") ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) ex.value.response["Error"]["Message"].should.equal( "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"] items.should.have.length_of(1) # Item should only include GSI Keys and Table Keys, as per the ProjectionType items[0].should.equal( { "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"] items.should.have.length_of(1) # Item should only include keys and additionally projected attributes only items[0].should.equal( { "gsiK1PartitionKey": "gsi-pk", "gsiK1SortKey": "gsi-sk", "partitionKey": "pk-1", "projectedAttribute": "lore ipsum", } ) # Same when scanning the table items = table.scan(IndexName="GSI-INC")["Items"] items[0].should.equal( { "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"] items.should.have.length_of(1) # Item should only include GSI Keys and Table Keys, as per the ProjectionType items[0].should.equal( {"partitionKey": "pk-1", "sortKey": "sk-1", "lsiK1SortKey": "lsi-sk"} ) # Same when scanning the table items = table.scan(IndexName="LSI")["Items"] items[0].should.equal( {"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] item.should.have.key("customer") item.should.have.key("orders") 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] item.should.have.key("customer") item.should_not.have.key("orders") @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", []) items.should.have.length_of(2) items[0].should.equal({"Item": item}) items[1].should.equal({}) @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"}) resp["Item"]["attr"].should.equal(["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"}) resp["Item"]["attr"].should.equal({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"}) resp["Item"]["attr"].should.equal({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"}}) ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException") ex.value.response["Error"]["Message"].should.equal("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"] err["Code"].should.equal("ValidationException") err["Message"].should.equal( "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"] items.should.equal([{"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] item["index_key"].should.equal({"S": "new_value"}) item["main_key"].should.equal({"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"] err["Code"].should.equal("ValidationException") err["Message"].should.equal( "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"] error["Code"].should.equal("TableNotFoundException") error["Message"].should.equal("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") details.should.have.key("BackupArn").should.contain(table_name) details.should.have.key("BackupName").should.equal(backup_name) details.should.have.key("BackupSizeBytes").should.be.a(int) details.should.have.key("BackupStatus") details.should.have.key("BackupType").should.equal("USER") details.should.have.key("BackupCreationDateTime").should.be.a(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" ) backup["BackupName"].should.equal(backup_name) backup_arns.should_not.contain(backup["BackupArn"]) 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"] error["Code"].should.equal("BackupNotFoundException") error["Message"].should.equal(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") details.should.have.key("BackupArn").should.contain(table_name) details.should.have.key("BackupName").should.equal(backup_name) details.should.have.key("BackupSizeBytes").should.be.a(int) details.should.have.key("BackupStatus") details.should.have.key("BackupType").should.equal("USER") details.should.have.key("BackupCreationDateTime").should.be.a(datetime) source = description.get("SourceTableDetails") source.should.have.key("TableName").should.equal(table_name) source.should.have.key("TableArn").should.equal(table["TableArn"]) source.should.have.key("TableSizeBytes").should.be.a(int) source.should.have.key("KeySchema").should.equal(table["KeySchema"]) source.should.have.key("TableCreationDateTime").should.equal( table["CreationDateTime"] ) source.should.have.key("ProvisionedThroughput").should.be.a(dict) source.should.have.key("ItemCount").should.equal(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") resp["BackupSummaries"].should.have.length_of(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") resp["BackupSummaries"].should.have.length_of(4) for table_name in table_names: resp = client.list_backups(TableName=table_name) resp["BackupSummaries"].should.have.length_of(2) for summary in resp["BackupSummaries"]: summary.should.have.key("TableName").should.equal(table_name) summary.should.have.key("TableArn").should.contain(table_name) summary.should.have.key("BackupName").should.be.within(backup_names) summary.should.have.key("BackupArn") summary.should.have.key("BackupCreationDateTime").should.be.a(datetime) summary.should.have.key("BackupStatus") summary.should.have.key("BackupType").should.be.within(["USER", "SYSTEM"]) summary.should.have.key("BackupSizeBytes").should.be.a(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"] error["Code"].should.equal("BackupNotFoundException") error["Message"].should.equal(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"] error["Code"].should.equal("TableAlreadyExistsException") error["Message"].should.equal(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") restored.should.have.key("AttributeDefinitions").should.equal( table["AttributeDefinitions"] ) restored.should.have.key("TableName").should.equal(restored_table_name) restored.should.have.key("KeySchema").should.equal(table["KeySchema"]) restored.should.have.key("TableStatus") restored.should.have.key("ItemCount").should.equal(5) restored.should.have.key("TableArn").should.contain(restored_table_name) restored.should.have.key("RestoreSummary").should.be.a(dict) summary = restored.get("RestoreSummary") summary.should.have.key("SourceBackupArn").should.equal(backup_arn) summary.should.have.key("SourceTableArn").should.equal(table["TableArn"]) summary.should.have.key("RestoreDateTime").should.be.a(datetime) summary.should.have.key("RestoreInProgress").should.equal(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") restored.should.have.key("TableName").should.equal(restored_table_name) restored.should.have.key("KeySchema").should.equal(table["KeySchema"]) restored.should.have.key("TableStatus") restored.should.have.key("ItemCount").should.equal(5) restored.should.have.key("TableArn").should.contain(restored_table_name) restored.should.have.key("RestoreSummary").should.be.a(dict) summary = restored.get("RestoreSummary") summary.should.have.key("SourceTableArn").should.equal(table["TableArn"]) summary.should.have.key("RestoreDateTime").should.be.a(datetime) summary.should.have.key("RestoreInProgress").should.equal(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"] error["Code"].should.equal("SourceTableNotFoundException") error["Message"].should.equal(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"] error["Code"].should.equal("TableAlreadyExistsException") error["Message"].should.equal(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"] error["Code"].should.equal("BackupNotFoundException") error["Message"].should.equal(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") resp["BackupSummaries"].should.have.length_of(2) backup_to_delete = resp["BackupSummaries"][0]["BackupArn"] backup_deleted = client.delete_backup(BackupArn=backup_to_delete).get( "BackupDescription" ) backup_deleted.should.have.key("SourceTableDetails") backup_deleted.should.have.key("BackupDetails") details = backup_deleted["BackupDetails"] details.should.have.key("BackupArn").should.equal(backup_to_delete) details.should.have.key("BackupName").should.be.within(backup_names) details.should.have.key("BackupStatus").should.equal("DELETED") resp = client.list_backups(TableName=table_name, BackupType="USER") resp["BackupSummaries"].should.have.length_of(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) source_table_items.should.have.key("Count").should.equal(10) source_table_guids = [x["id"]["S"] for x in source_table_items["Items"]] set(source_table_guids).should.equal( set(guids_original) | set(guids_added_after_backup) ) restored_table_items = client.scan(TableName=restored_table_name) restored_table_items.should.have.key("Count").should.equal(10) restored_table_guids = [x["id"]["S"] for x in restored_table_items["Items"]] set(restored_table_guids).should.equal( 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"] res.should.equal( [ { "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) err.value.response["Error"]["Code"].should.equal("ValidationException") conn.scan(TableName=name)["Items"].should.have.length_of(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"].should.equal({"id": str(idx)}) scan = table.scan() assert scan["Count"].should.equal(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}") scan = table.scan() assert scan["Count"].should.equal(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"] items.should.have.length_of(1) items[0].should.equal( {"main_key": "testkey1", "extra_data": "testdata", "index_key": "indexkey"} ) last_evaluated_key = response["LastEvaluatedKey"] last_evaluated_key.should.have.length_of(2) last_evaluated_key.should.equal({"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"] query_items_1.should.have.length_of(0) query_last_evaluated_key = query_response_1["LastEvaluatedKey"] query_last_evaluated_key.should.have.length_of(2) query_last_evaluated_key.should.equal({"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"] query_items_2.should.have.length_of(1) query_items_2[0].should.equal( {"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"] scan_items_1.should.have.length_of(0) scan_last_evaluated_key = scan_response_1["LastEvaluatedKey"] scan_last_evaluated_key.should.have.length_of(2) scan_last_evaluated_key.should.equal({"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"] scan_items_2.should.have.length_of(1) scan_items_2[0].should.equal( {"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_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")