Repository: getmoto/moto (python)

Issue:
DynamoDB transact write items update throws without expression attribute names
In the latest release 5.0.10, DynamoDB `transact_write_items` update operation fails if `ExpressionAttributeNames` is absent. Works fine with 5.0.9.

Error: `TypeError: 'NoneType' object is not iterable`

This is caused by the for loop in https://github.com/getmoto/moto/blob/5.0.10/moto/dynamodb/models/__init__.py#L549 when `expression_attribute_names` is None.

Code to reproduce error:
```py
import boto3
from moto import mock_aws

if __name__ == "__main__":
    with mock_aws():
        ddb_client = boto3.client("dynamodb", region_name="us-east-1")
        ddb_client.create_table(
            AttributeDefinitions=[{"AttributeName": "name", "AttributeType": "S"}],
            TableName="test",
            KeySchema=[{"AttributeName": "name", "KeyType": "HASH"}],
            BillingMode="PROVISIONED",
            ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
        )

        ddb_client.transact_write_items(
            TransactItems=[
                {
                    "Update": {
                        "TableName": "test",
                        "Key": {"name": {"S": "test"}},
                        "UpdateExpression": "SET xxx = :xxx",
                        "ConditionExpression": "attribute_not_exists(xxx)",
                        "ExpressionAttributeValues": {":xxx": {"S": "123"}},
                    }
                }
            ]
        )
```


Pull request description:
DynamoDB: transact_write_items() should not require ExprAttrNames
Fixes #7819 

Patch:
diff --git a/moto/dynamodb/models/__init__.py b/moto/dynamodb/models/__init__.py
index 593c7b675..9b114cbe3 100644
--- a/moto/dynamodb/models/__init__.py
+++ b/moto/dynamodb/models/__init__.py
@@ -546,7 +546,7 @@ class DynamoDBBackend(BaseBackend):
                 attr.get_attribute_name_placeholder() for attr in attr_name_clauses
             ]
             attr_names_in_condition = condition_expression_parser.expr_attr_names_found
-            for attr_name in expression_attribute_names:
+            for attr_name in expression_attribute_names or []:
                 if (
                     attr_name not in attr_names_in_expression
                     and attr_name not in attr_names_in_condition


Test patch:
diff --git a/tests/test_dynamodb/test_dynamodb.py b/tests/test_dynamodb/test_dynamodb.py
index dc32454a2..7624d051d 100644
--- a/tests/test_dynamodb/test_dynamodb.py
+++ b/tests/test_dynamodb/test_dynamodb.py
@@ -5005,45 +5005,6 @@ def test_update_non_existing_item_raises_error_and_does_not_contain_item_afterwa
     assert len(conn.scan(TableName=name)["Items"]) == 0
 
 
-@mock_aws
-def test_batch_write_item():
-    conn = boto3.resource("dynamodb", region_name="us-west-2")
-    tables = [f"table-{i}" for i in range(3)]
-    for name in tables:
-        conn.create_table(
-            TableName=name,
-            KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
-            AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
-            BillingMode="PAY_PER_REQUEST",
-        )
-
-    conn.batch_write_item(
-        RequestItems={
-            tables[0]: [{"PutRequest": {"Item": {"id": "0"}}}],
-            tables[1]: [{"PutRequest": {"Item": {"id": "1"}}}],
-            tables[2]: [{"PutRequest": {"Item": {"id": "2"}}}],
-        }
-    )
-
-    for idx, name in enumerate(tables):
-        table = conn.Table(f"table-{idx}")
-        res = table.get_item(Key={"id": str(idx)})
-        assert res["Item"] == {"id": str(idx)}
-        assert table.scan()["Count"] == 1
-
-    conn.batch_write_item(
-        RequestItems={
-            tables[0]: [{"DeleteRequest": {"Key": {"id": "0"}}}],
-            tables[1]: [{"DeleteRequest": {"Key": {"id": "1"}}}],
-            tables[2]: [{"DeleteRequest": {"Key": {"id": "2"}}}],
-        }
-    )
-
-    for idx, name in enumerate(tables):
-        table = conn.Table(f"table-{idx}")
-        assert table.scan()["Count"] == 0
-
-
 @mock_aws
 def test_gsi_lastevaluatedkey():
     # github.com/getmoto/moto/issues/3968
diff --git a/tests/test_dynamodb/test_dynamodb_batch_write.py b/tests/test_dynamodb/test_dynamodb_batch_write.py
new file mode 100644
index 000000000..7ee0a27ac
--- /dev/null
+++ b/tests/test_dynamodb/test_dynamodb_batch_write.py
@@ -0,0 +1,79 @@
+from uuid import uuid4
+
+import boto3
+import pytest
+
+from . import dynamodb_aws_verified
+
+
+@pytest.mark.aws_verified
+@dynamodb_aws_verified()
+def test_batch_write_single_set(table_name=None):
+    ddb_client = boto3.client("dynamodb", region_name="us-east-1")
+
+    ddb_client.transact_write_items(
+        TransactItems=[
+            {
+                "Update": {
+                    "TableName": table_name,
+                    "Key": {"pk": {"S": "test"}},
+                    "UpdateExpression": "SET xxx = :xxx",
+                    "ConditionExpression": "attribute_not_exists(xxx)",
+                    "ExpressionAttributeValues": {":xxx": {"S": "123"}},
+                }
+            }
+        ]
+    )
+
+    results = ddb_client.scan(TableName=table_name)["Items"]
+    assert results == [{"pk": {"S": "test"}, "xxx": {"S": "123"}}]
+
+
+@pytest.mark.aws_verified
+@dynamodb_aws_verified(create_table=False)
+def test_batch_write_item_to_multiple_tables():
+    conn = boto3.resource("dynamodb", region_name="us-west-2")
+    tables = [f"table-{str(uuid4())[0:6]}-{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",
+        )
+    for name in tables:
+        waiter = boto3.client("dynamodb", "us-west-2").get_waiter("table_exists")
+        waiter.wait(TableName=name)
+
+    try:
+        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(name)
+            res = table.get_item(Key={"id": str(idx)})
+            assert res["Item"] == {"id": str(idx)}
+            assert table.scan()["Count"] == 1
+
+        conn.batch_write_item(
+            RequestItems={
+                tables[0]: [{"DeleteRequest": {"Key": {"id": "0"}}}],
+                tables[1]: [{"DeleteRequest": {"Key": {"id": "1"}}}],
+                tables[2]: [{"DeleteRequest": {"Key": {"id": "2"}}}],
+            }
+        )
+
+        for idx, name in enumerate(tables):
+            assert conn.Table(name).scan()["Count"] == 0
+    finally:
+        for name in tables:
+            try:
+                conn.Table(name).delete()
+            except Exception as e:
+                print(f"Failed to delete table {name}")  # noqa
+                print(e)  # noqa