From 1111e10b875af3bc69552be0f16b116edb8eb629 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Fri, 7 Apr 2023 21:02:00 +0000 Subject: [PATCH] DynamoDB: Allow attribute name 'S' (#6185) --- moto/dynamodb/models/table.py | 8 +++++--- .../test_dynamodb/exceptions/test_dynamodb_exceptions.py | 9 +++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/moto/dynamodb/models/table.py b/moto/dynamodb/models/table.py index 1be961410..69e25d341 100644 --- a/moto/dynamodb/models/table.py +++ b/moto/dynamodb/models/table.py @@ -484,10 +484,12 @@ class Table(CloudFormationModel): if DynamoType(range_value).size() > RANGE_KEY_MAX_LENGTH: raise RangeKeyTooLong - def _validate_item_types(self, item_attrs: Dict[str, Any]) -> None: + def _validate_item_types( + self, item_attrs: Dict[str, Any], attr: Optional[str] = None + ) -> None: for key, value in item_attrs.items(): if type(value) == dict: - self._validate_item_types(value) + self._validate_item_types(value, attr=key if attr is None else key) elif type(value) == int and key == "N": raise InvalidConversion if key == "S": @@ -497,7 +499,7 @@ class Table(CloudFormationModel): raise SerializationException( "NUMBER_VALUE cannot be converted to String" ) - if type(value) == dict: + if attr and attr in self.table_key_attrs and type(value) == dict: raise SerializationException( "Start of structure or map found where not expected" ) diff --git a/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py b/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py index daad4aa8e..47386a102 100644 --- a/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py +++ b/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py @@ -933,12 +933,21 @@ def test_put_item__string_as_integer_value(): err["Code"].should.equal("SerializationException") err["Message"].should.equal("NUMBER_VALUE cannot be converted to String") + # A primary key cannot be of type S, but then point to a dictionary with pytest.raises(ClientError) as exc: client.put_item(TableName="without_sk", Item={"pk": {"S": {"S": "asdf"}}}) err = exc.value.response["Error"] err["Code"].should.equal("SerializationException") err["Message"].should.equal("Start of structure or map found where not expected") + # Note that a normal attribute name can be an 'S', which follows the same pattern + # Nested 'S'-s like this are allowed for non-key attributes + client.put_item( + TableName="without_sk", Item={"pk": {"S": "val"}, "S": {"S": "asdf"}} + ) + item = client.get_item(TableName="without_sk", Key={"pk": {"S": "val"}})["Item"] + assert item == {"pk": {"S": "val"}, "S": {"S": "asdf"}} + @mock_dynamodb def test_gsi_key_cannot_be_empty():