Challenge: Create DynamoDB Table using Python (Boto3)
Tags: aws, programming, boto3
Published: 2022-12-04 11:13:19

# Challenge: Create DynamoDB Table using Python (Boto3)

This was an idea from the AWS Certified Global Community to create challenges by breaking a certain part of code, so the users can learn by fixing the code.

I have written this challenge for the AWS Certified Global Community and have also used it to mentor some of my mentees.
I thought that it might be a good idea to share with the broader community as well.

**Boto3** is the name of the Python SDK for AWS. It allows you to directly create, update, and delete AWS resources from your Python scripts.

**Amazon DynamoDB** is a fully managed, serverless, key-value NoSQL database designed to run high-performance applications at any scale. DynamoDB offers built-in security, continuous backups, automated multi-Region replication, in-memory caching, and data import and export tools.

Let's start the challenge!

**Pre-Requisites:**
* Lambda with Python 3.9
* Lambda Execution role with access to DynamoDB and CloudWatch access (IAM)

----

**Step 1. Create an IAM Policy:**

> Note: Replace [Region] and [AccountID] in the Policy

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:CreateTable",
                "dynamodb:DescribeTable",
                "dynamodb:BatchGetItem",
                "dynamodb:GetItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:BatchWriteItem",
                "dynamodb:PutItem",
                "dynamodb:UpdateItem"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:[Region]:[AccountID]:*"
        },
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "*"
        }
    ]
}
```

----

**Step 2. Create a role for Lambda and attach the policy created in Step 1**

```json
Trusted entities:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "lambda.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

----

**Step 3. Broken Code:** Create a Lambda function with the following code and execute the function.

```python
import boto3
import json

def lambda_handler(event, context):
    client = boto3.resource('dynamodb')
   
    table_name = 'VisitsCount'
    params = {
        'TableName': table_name,
        'KeySchema': [
            {'AttributeName': 'partition_key', 'KeyType': 'HASH'},
            {'AttributeName': 'sort_key', 'KeyType': 'RANGE'},
        ],
        'AttributeDefinitions': [
            {'AttributeName': 'Date', 'AttributeType': 'S'},
            {'AttributeName': 'Count', 'AttributeType': 'N'}
        ],
        'ProvisionedThroughput': {
            'ReadCapacityUnits': 10,
            'WriteCapacityUnits': 10
        }
    }
    table = client.create_table(**params)
    print(f"Creating {table_name}...")
    table.wait_until_exists()

    return {
        'statusCode': 200,
        'body': table
    }    
```

----

**First Error:** We get the following error when we execute the code.

```json
 'KeySchema': [
            {'AttributeName': 'partition_key', 'KeyType': 'HASH'},
            {'AttributeName': 'sort_key', 'KeyType': 'RANGE'},
        ],
```

```json
Error Message:
{
  "errorMessage": "An error occurred (ValidationException) when calling the CreateTable operation: One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [partition_key, sort_key], AttributeDefinitions: [Date, Count]",
  "errorType": "ClientError",
  "requestId": "b4f6cf89-8efb-470b-a52e-81d364d5e7a2",
  "stackTrace": [
    "  File \"/var/task/lambda_function.py\", line 23, in lambda_handler\n    table = client.create_table(**params)\n",
    "  File \"/var/runtime/boto3/resources/factory.py\", line 520, in do_action\n    response = action(self, *args, **kwargs)\n",
    "  File \"/var/runtime/boto3/resources/action.py\", line 83, in __call__\n    response = getattr(parent.meta.client, operation_name)(*args, **params)\n",
    "  File \"/var/runtime/botocore/client.py\", line 391, in _api_call\n    return self._make_api_call(operation_name, kwargs)\n",
    "  File \"/var/runtime/botocore/client.py\", line 719, in _make_api_call\n    raise error_class(parsed_response, operation_name)\n"
  ]
}
```

**Fix for the error:** Upate the following code block.
```json
'KeySchema': [
            {'AttributeName': 'Date', 'KeyType': 'HASH'},
            {'AttributeName': 'Count', 'KeyType': 'RANGE'},
        ],
```

**Reference/Hint:** [Ceating dynamodb table says "invalid One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions"](https://stackoverflow.com/questions/62918389/ceating-dynamodb-table-says-invalid-one-or-more-parameter-values-were-invalid)

----

So when we execute the code, this time we get another error as following

**Second Error:** We get the following error when we execute the code after applying fix for the first error.
The issue is while prinintg the table name in the output

```python
'body': table
```

```json
Error Message:
{
  "errorMessage": "Unable to marshal response: Object of type dynamodb.Table is not JSON serializable",
  "errorType": "Runtime.MarshalError",
  "requestId": "e60ca4f2-eccd-410c-a93a-7cf7499a0a9d",
  "stackTrace": []
}
```

**Fix for the error:** Upate the following code block.

```python
'body': json.dumps(table_name)
```

**Reference/Hint:**  [AWS Lambda "Unable to marshal response" Error](https://stackoverflow.com/questions/61758398/aws-lambda-unable-to-marshal-response-error)   

----

**Step 4. Final Code:**

```python
import boto3
import json

def lambda_handler(event, context):
    client = boto3.resource('dynamodb')
   
    table_name = 'VisitsCount'
    params = {
        'TableName': table_name,
        'KeySchema': [
            {'AttributeName': 'Date', 'KeyType': 'HASH'},
            {'AttributeName': 'Count', 'KeyType': 'RANGE'},
        ],
        'AttributeDefinitions': [
            {'AttributeName': 'Date', 'AttributeType': 'S'},
            {'AttributeName': 'Count', 'AttributeType': 'N'}
        ],
        'ProvisionedThroughput': {
            'ReadCapacityUnits': 10,
            'WriteCapacityUnits': 10
        }
    }
    table = client.create_table(**params)
    print(f"Creating {table_name}...")
    table.wait_until_exists()

    return {
        'statusCode': 200,
        'body': json.dumps(table_name)
    }
```

```json
Output:
Test Event Name
createtable

Response
{
  "statusCode": 200,
  "body": "\"VisitsCount\""
}
```

Hope you liked the challenge! Please let me know your thoughts in the comments section.

See you next time :wave:

-----

### References: 

[DynamoDB CreateTable](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html)

-----