Repository: getmoto/moto (python)

Issue:
DynamoType has no `__delitem__()` method
## moto==5.0.14

I encountered an error when a dynamodb UpdateItem operation results in a field being deleted from a Map. Here's a test to demonstrate:
```python
from unittest import TestCase

import boto3
from moto import mock_aws


@mock_aws
class TestDeleteEmptyStringSetFromMap(TestCase):
    """
    When you UpdateItem with a DELETE action on an empty string set, DynamoDB should delete the whole field from the Map
    the field is in. In Moto, this produces an AttributeError:

    File ".../python3.12/site-packages/moto/dynamodb/parsing/executors.py", line 194, in execute
        del container[attribute_name]  # type: ignore[union-attr]
            ~~~~~~~~~^^^^^^^^^^^^^^^^
        AttributeError: __delitem__

    You can verify this test passes against AWS by dropping the `@mock_aws` decorator.
    """
    def test_delete_from_empty_string_set(self):
        self._table.put_item(
            Item={
                'pk': 'foo',
                'map': {
                    'stringSet': {'foo'}
                }
            }
        )
        resp = self._table.update_item(
            Key={'pk': 'foo'},
            UpdateExpression='DELETE #map.#stringSet :s',
            ExpressionAttributeNames={
                '#map': 'map',
                '#stringSet': 'stringSet'
            },
            ExpressionAttributeValues={':s': {'foo'}},
            ReturnValues='ALL_NEW'
        )
        self.assertEqual(
            {
                'pk': 'foo',
                'map': {}
            },
            resp['Attributes']
        )


    def setUp(self):
        self._table_name = 'example-test-table'
        self._client = boto3.client('dynamodb')
        self._table = boto3.resource('dynamodb').create_table(
            AttributeDefinitions=[
                {
                    'AttributeName': 'pk',
                    'AttributeType': 'S'
                }
            ],
            TableName=self._table_name,
            KeySchema=[
                {
                    'AttributeName': 'pk',
                    'KeyType': 'HASH'
                }
            ],
            BillingMode='PAY_PER_REQUEST',
        )
        waiter = self._client.get_waiter('table_exists')
        waiter.wait(TableName=self._table_name)

    def tearDown(self):
        self._table.delete()
        waiter = self._client.get_waiter('table_not_exists')
        waiter.wait(TableName=self._table_name)

```

After a quick glance, it looks like this can be resolved by adding a `__delitem__()` like:
```python
    def __delitem__(self, item: "DynamoType") -> "DynamoType":
        if isinstance(item, str):
            # If our DynamoType is a map it should be subscriptable with a key
            if self.type == DDBType.MAP:
                del self.value[item]
                return
        elif isinstance(item, int):
            # If our DynamoType is a list is should be subscriptable with an index
            if self.type == DDBType.LIST:
                del self.value[item]
                return
        raise TypeError(
            f"This DynamoType {self.type} is not subscriptable by a {type(item)}"
        )
```

I didn't get set up to run the full moto test suite, but hopefully this is an easy fix for you.

Pull request description:
DynamoDB: update_item() can now remove last item from set
Fixes #8128 

Patch:
diff --git a/moto/dynamodb/models/dynamo_type.py b/moto/dynamodb/models/dynamo_type.py
index ebebfe6ae..62f87356f 100644
--- a/moto/dynamodb/models/dynamo_type.py
+++ b/moto/dynamodb/models/dynamo_type.py
@@ -148,6 +148,11 @@ class DynamoType(object):
         else:
             raise NotImplementedError(f"No set_item for {type(key)}")
 
+    def __delitem__(self, item: str) -> "DynamoType":
+        if isinstance(item, str) and self.type == DDBType.MAP:
+            del self.value[item]
+        return self
+
     @property
     def cast_value(self) -> Any:  # type: ignore[misc]
         if self.is_number():


Test patch:
diff --git a/tests/test_dynamodb/test_dynamodb_update_expressions.py b/tests/test_dynamodb/test_dynamodb_update_expressions.py
index b14e66987..3d071d901 100644
--- a/tests/test_dynamodb/test_dynamodb_update_expressions.py
+++ b/tests/test_dynamodb/test_dynamodb_update_expressions.py
@@ -92,6 +92,22 @@ def test_update_item_add_float(table_name=None):
     assert table.scan()["Items"][0]["nr"] == Decimal("31.41")
 
 
+@pytest.mark.aws_verified
+@dynamodb_aws_verified()
+def test_delete_last_item_from_map(table_name=None):
+    table = boto3.resource("dynamodb", "us-east-1").Table(table_name)
+
+    table.put_item(Item={"pk": "foo", "map": {"sset": {"foo"}}})
+    resp = table.update_item(
+        Key={"pk": "foo"},
+        UpdateExpression="DELETE #map.#sset :s",
+        ExpressionAttributeNames={"#map": "map", "#sset": "sset"},
+        ExpressionAttributeValues={":s": {"foo"}},
+        ReturnValues="ALL_NEW",
+    )
+    assert {"pk": "foo", "map": {}} == resp["Attributes"]
+
+
 @pytest.mark.aws_verified
 @dynamodb_aws_verified()
 def test_delete_non_existing_item(table_name=None):