EricFr@nkenberger.com

HOME   RESUME/CONTACT   GITHUB

[27-11-2020] | AWS Lambda/DynamoDB/API Gateway


The goal of this is to eventually make an API that interacts with DynamoDB to log a visit count to this site. I'm going to use API Gateway, Lambda, and DynamoDB for this.

Following an Amazon tutorial, I set up the PetStore example API, then used postman to view the POST of it from the URL found in API Gateway's POST URL found in the resources tab:

 
        
    [
        {
            "id": 1,
            "type": "dog",
            "price": 249.99
        },
        {
            "id": 2,
            "type": "cat",
            "price": 124.99
        },
        {
            "id": 3,
            "type": "fish",
            "price": 0.99
        }
    ]
         
        

I can write some Python using Lambda triggered off of an API event to take the data returned, parse it, and write it to a DB. Here's the simple test code I had to get the data and parse it to JSON:

 
    import requests
    import json

    #define how to parse json
    def jprint(obj):
        text = json.dumps(obj, sort_keys=True, indent=4)
        print(text)

    response = requests.get("https://bzy4xhbr70.execute-api.us-east-2.amazonaws.com/test/pets/")

    jprint(response.json())
         

Now that I've gotten the API request in Python locally, I need to figure out how to create and connect to a DynamoDB table and get the data in a format that I can push directly to it.

Following a tutorial, I modified some code to create a table, and use print(table.item_count) to verify if and when it gets created.

 
    import boto3

    dynamodb = boto3.resource('dynamodb')

    table = dynamodb.create_table(
        TableName='pets',
        KeySchema=[
            {
                'AttributeName': 'id',
                'KeyType': 'HASH'
            },
            {
                'AttributeName': 'type',
                'KeyType': 'RANGE'
            }
        ],
        AttributeDefinitions=[
            {
                'AttributeName': 'id',
                'AttributeType': 'S'
            },
            {
                'AttributeName': 'type',
                'AttributeType': 'S'
            },
        ],
        ProvisionedThroughput={
            'ReadCapacityUnits': 5,
            'WriteCapacityUnits': 5
        }
    )

    table.meta.client.get_waiter('table_exists').wait(TableName='PETS')

    print(table.item_count)
         

After this runs it will return 0.

Feeling pretty good at this point after getting the basics going, building some good confidence in this. Now I need to write some data to the table. It looks like boto3 is the library I need to use.

I'm gonna add Frankie and Annabelle as pets for this one.

 
    import boto3

    dynamodb = boto3.resource('dynamodb')

    table=dynamodb.Table('pets')

    #do before running put_items to see the count
    #do again after to verify writing worked
    print(table.item_count)

    table.put_item(
       Item={
            'id': 'frankie',
            'type': 'cat',
            'price': 'free',
            'age': 4,
        }
    )

    table.put_item(
       Item={
            'id': 'annabelle',
            'type': 'dog',
            'price': 'free',
            'age': 7,
        }
    )

    print(table.item_count)
         

I thought I was gonna get crafty with my before and after table.item_count but AWS states that DynamoDB only updates this value every 6 hours, so it doesn't work. I can check everything live on the DynamoDB portal though.

Anyway, now I have 3 small separate parts of code to do what I need, just need to bring it together and write the API call to DynamoDB. I got it working by parsing the JSON into an array, then iterating through the array with a loop to push the data.

 
    import json
    import requests
    import boto3
    from decimal import Decimal

    dynamodb = boto3.resource('dynamodb')
    table=dynamodb.Table('pets')

    response = requests.get("https://bzy4xhbr70.execute-api.us-east-2.amazonaws.com/test/pets/")

    parsed = response.json()
    array = []

    for item in parsed:
        array_items = {"id":None, "type":None, "price":None}
        array_items['id'] = item['id']
        array_items['type'] = item['type']
        array_items['price'] = item['price']

        array.append(array_items)

        table.put_item(
           Item={
            'id': str(array_items['id']),
            'type': array_items['type'],
            'price': Decimal(str(array_items['price']))
        })

    print(array)
         

Hit multiple issues trying to figure this one out. The first issue I had was the 'id' type expected a string. This was easy because all I had to do was wrap the 'id' definition in a str() on the put_item section of the code.

The next issue was price, boto3/dynamodb handle decimals differently than python. Reading up on it, it looks like the float is inaccurate due to Python's base 2 numbering system. Decimal type is what needs to be used.

First error was "TypeError: Float types are not supported. Use Decimal types instead." Then, after trying to wrap everything in a Decimal() I kept getting decimal.Inexact: [<class 'decimal.Inexact'>, <class 'decimal.Rounded'>]

I think my issue was that I was trying to convert another variable to decimal, instead of converting the number itself directly? I can convert to int type but it just rounds to the nearest whole. This might not be an issue for the type of data I need to collect in the next step of this, but it's still kinda ugly.

The solution is - Decimal(str(*variable*)). Seems pretty inelegant but it works. Now we have API call directly into DynamoDB! The next steps will be adapting the API to what I need to get user data, and then adapting the Lambda code with the correct variables to write to DynamoDB.

Now that all that's done, that concludes this section of accomplishing the goal to make a call and write it to DynamoDB in one swoop.