Repository: localstack/localstack

Issue:
bug: Response object TableDescription contains a member which is not specified: TableClass
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

> ERROR:localstack.aws.protocol.serializer: Response object TableDescription contains a member which is not specified: TableClass
> 
> Traceback (most recent call last):
> 
>   File "/opt/code/localstack/localstack/aws/protocol/serializer.py", line 1007, in _serialize_type_structure
> 
>     member_shape = members[member_key]
> 
> KeyError: 'TableClass'

### Expected Behavior

_No response_

### How are you starting LocalStack?

With a docker-compose file

### Steps To Reproduce

Create a DynamoDB table via Pulumi.

### Environment

```markdown
- OS: Mac M1
- LocalStack: latest
```


### Anything else?

_No response_
bug: Response object TableDescription contains a member which is not specified: TableClass
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

> ERROR:localstack.aws.protocol.serializer: Response object TableDescription contains a member which is not specified: TableClass
> 
> Traceback (most recent call last):
> 
>   File "/opt/code/localstack/localstack/aws/protocol/serializer.py", line 1007, in _serialize_type_structure
> 
>     member_shape = members[member_key]
> 
> KeyError: 'TableClass'

### Expected Behavior

_No response_

### How are you starting LocalStack?

With a docker-compose file

### Steps To Reproduce

Create a DynamoDB table via Pulumi.

### Environment

```markdown
- OS: Mac M1
- LocalStack: latest
```


### Anything else?

_No response_


Discussion:
Welcome to LocalStack! Thanks for reporting your first issue and our team will be working towards fixing the issue for you or reach out for more background information. We recommend joining our [Slack Community](https://localstack.cloud/contact/) for real-time help and drop a message to LocalStack Pro Support if you are a Pro user! If you are willing to contribute towards fixing this issue, please have a look at our [contributing guidelines](https://github.com/localstack/.github/blob/main/CONTRIBUTING.md) and our [developer guide](https://docs.localstack.cloud/developer-guide/).
Welcome to LocalStack! Thanks for reporting your first issue and our team will be working towards fixing the issue for you or reach out for more background information. We recommend joining our [Slack Community](https://localstack.cloud/contact/) for real-time help and drop a message to LocalStack Pro Support if you are a Pro user! If you are willing to contribute towards fixing this issue, please have a look at our [contributing guidelines](https://github.com/localstack/.github/blob/main/CONTRIBUTING.md) and our [developer guide](https://docs.localstack.cloud/developer-guide/).

Resolving patch:
diff --git a/localstack/services/dynamodb/provider.py b/localstack/services/dynamodb/provider.py
--- a/localstack/services/dynamodb/provider.py
+++ b/localstack/services/dynamodb/provider.py
@@ -421,6 +421,12 @@ def create_table(
         if "StreamSpecification" in table_definitions:
             create_dynamodb_stream(table_definitions, table_description.get("LatestStreamLabel"))
 
+        if "TableClass" in table_definitions:
+            table_class = table_description.pop("TableClass", None) or table_definitions.pop(
+                "TableClass"
+            )
+            table_description["TableClassSummary"] = {"TableClass": table_class}
+
         tags = table_definitions.pop("Tags", [])
         if tags:
             table_arn = table_description["TableArn"]
@@ -477,6 +483,10 @@ def describe_table(self, context: RequestContext, table_name: TableName) -> Desc
             for key in ["TableId", "SSEDescription"]:
                 if table_definitions.get(key):
                     result.get("Table", {})[key] = table_definitions[key]
+            if "TableClass" in table_definitions:
+                result.get("Table", {})["TableClassSummary"] = {
+                    "TableClass": table_definitions["TableClass"]
+                }
 
         return result
 
@@ -491,8 +501,20 @@ def update_table(
             is_no_update_error = (
                 e.code == "ValidationException" and "Nothing to update" in e.message
             )
-            if is_no_update_error and update_table_input.get("ReplicaUpdates"):
-                table_name = update_table_input.get("TableName")
+            if not is_no_update_error or not list(
+                {"TableClass", "ReplicaUpdates"} & set(update_table_input.keys())
+            ):
+                raise
+
+            table_name = update_table_input.get("TableName")
+
+            if update_table_input.get("TableClass"):
+                table_definitions = DynamoDBRegion.get().table_definitions.setdefault(
+                    table_name, {}
+                )
+                table_definitions["TableClass"] = update_table_input.get("TableClass")
+
+            if update_table_input.get("ReplicaUpdates"):
                 # update local table props (replicas)
                 table_properties = DynamoDBRegion.get().table_properties
                 table_properties[table_name] = table_props = table_properties.get(table_name) or {}
@@ -511,10 +533,10 @@ def update_table(
                             table_props["Replicas"] = [
                                 r for r in replicas if r.get("RegionName") != region
                             ]
-                # update response content
-                schema = SchemaExtractor.get_table_schema(table_name)
-                return UpdateTableOutput(TableDescription=schema["Table"])
-            raise
+
+            # update response content
+            schema = SchemaExtractor.get_table_schema(table_name)
+            return UpdateTableOutput(TableDescription=schema["Table"])
 
         if "StreamSpecification" in update_table_input:
             create_dynamodb_stream(


Test patch:
diff --git a/tests/integration/test_dynamodb.py b/tests/integration/test_dynamodb.py
--- a/tests/integration/test_dynamodb.py
+++ b/tests/integration/test_dynamodb.py
@@ -506,6 +506,31 @@ def wait_for_stream_created(table_name):
         )
         assert "ShardIterator" in response
 
+    def test_dynamodb_create_table_with_class(self, dynamodb_client):
+        table_name = "table_with_class_%s" % short_uid()
+        # create table
+        result = dynamodb_client.create_table(
+            TableName=table_name,
+            KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
+            AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
+            ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
+            TableClass="STANDARD",
+        )
+        assert result["TableDescription"]["TableClassSummary"]["TableClass"] == "STANDARD"
+        result = dynamodb_client.describe_table(TableName=table_name)
+        assert result["Table"]["TableClassSummary"]["TableClass"] == "STANDARD"
+        result = dynamodb_client.update_table(
+            TableName=table_name, TableClass="STANDARD_INFREQUENT_ACCESS"
+        )
+        assert (
+            result["TableDescription"]["TableClassSummary"]["TableClass"]
+            == "STANDARD_INFREQUENT_ACCESS"
+        )
+        result = dynamodb_client.describe_table(TableName=table_name)
+        assert result["Table"]["TableClassSummary"]["TableClass"] == "STANDARD_INFREQUENT_ACCESS"
+        # clean resources
+        dynamodb_client.delete_table(TableName=table_name)
+
     def test_dynamodb_execute_transaction(self, dynamodb_client):
         table_name = "table_%s" % short_uid()
         dynamodb_client.create_table(