diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py index 73b09d73c..c4aa10237 100644 --- a/moto/dynamodb2/models.py +++ b/moto/dynamodb2/models.py @@ -176,16 +176,17 @@ class Item(BaseModel): key_parts = key.split('.') attr = key_parts.pop(0) if attr not in self.attrs: - raise ValueError() + raise ValueError last_val = self.attrs[attr].value for key_part in key_parts: # Hack but it'll do, traverses into a dict - if list(last_val.keys())[0] == 'M': - last_val = last_val['M'] + last_val_type = list(last_val.keys()) + if last_val_type and last_val_type[0] == 'M': + last_val = last_val['M'] if key_part not in last_val: - raise ValueError() + last_val[key_part] = {'M': {}} last_val = last_val[key_part] diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index 20ff80167..93188001f 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -6,6 +6,7 @@ import boto3 from boto3.dynamodb.conditions import Attr import sure # noqa import requests +from pytest import raises from moto import mock_dynamodb2, mock_dynamodb2_deprecated from moto.dynamodb2 import dynamodb_backend2 from boto.exception import JSONResponseError @@ -1052,6 +1053,7 @@ def test_query_missing_expr_names(): @mock_dynamodb2 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( @@ -1092,21 +1094,44 @@ def test_update_item_on_map(): 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', + UpdateExpression='SET body.#nested.#data = :tb, body.nested.#nonexistentnested.#data = :tb2', ExpressionAttributeNames={ '#nested': 'nested', + '#nonexistentnested': 'nonexistentnested', '#data': 'data' }, ExpressionAttributeValues={ - ':tb': 'new_value' + ':tb': 'new_value', + ':tb2': 'other_value' }) resp = table.scan() - resp['Items'][0]['body'].should.equal({'nested': {'data': 'new_value'}}) + resp['Items'][0]['body'].should.equal({ + 'nested': { + 'data': 'new_value', + 'nonexistentnested': {'data': 'other_value'} + } + }) + + # Test nested value for a nonexistent attribute. + with raises(client.exceptions.ConditionalCheckFailedException): + 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/spulec/moto/issues/1358