3

Hi I am writing a lambda function that will update the DynamoDb using boto3. In this code employee_id is auto-generated but you have to provide last_name or first_name. I am doing it with if-else. If the attribute tends to increase so does the checks. I can't keep on going with if condition. How can I tackle this what changes should I make

import boto3
import json

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

def lambda_handler(event, context):

    employee_id = event['employee_id']

    if 'first_name' in event and 'last_name' not in event:

        first_name = event['first_name']
        UpdateExpression = 'SET first_name = :val1'
        ExpressionAttributeValues = {':val1': first_name }

    elif 'last_name' in event and 'first_name' not in event:

        last_name = event['last_name']
        UpdateExpression = 'SET last_name = :val1'
        ExpressionAttributeValues = {':val1': last_name}

    elif 'first_name' in event and 'last_name' in event:

        last_name = event['last_name']
        first_name= event['first_name']
        UpdateExpression = 'SET last_name = :val1, first_name = :val2'
        ExpressionAttributeValues = {
                ':val1': last_name,
                ':val2': first_name
            }

    else:
        raise ValueError("first_name and last_name not given")


    update = table.update_item(
        Key={
            'employee_id': employee_id
        },
        ConditionExpression= 'attribute_exists(employee_id)',
        UpdateExpression=UpdateExpression,
        ExpressionAttributeValues=ExpressionAttributeValues
    )

The code that I came up with but is not working

import boto3
import json
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Employee')

def lambda_handler(event, context):
    employee_id = event['employee_id']
    last_name= event['last_name']
    first_name= event['first_name']
    column = [first_name,last_name]
    for i in range(0,len(column):
       query = 'SET {} = :val1,:val2'.format(column[i])
       response = table.update_item(
           Key={
            'employee_id': employee_id
            },
            ConditionExpression= 'attribute_exists(employee_id)',
            UpdateExpression = query,
            ExpressionAttributeValues={
                ':val1': first_name,
                ':val2': last_name
            },
            ReturnValues="UPDATED_NEW"
        )

  • See my [JavaScript based answer](https://stackoverflow.com/a/59194533/4632627), if that helps. Also, you have a typo: `for i in range(0,len(column):` – Richard Dunn Aug 29 '20 at 11:04

1 Answers1

3

You should look at storing the update expression and expression values separately, then passing the complete set into the Lambda function.

This would also allow you to validate against each parameter (perhaps breaking this into a validate function to avoid excessive size of function). This way you support both required and optional parameters, then at the end validate that the update expression has valid parameters.

Perhaps something like the below?

import boto3
import json

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

def lambda_handler(event, context):

    update_expression_values = []
    expression_attribute_values = {}
    
    if 'employee_id' in event:
        employee_id = event['employee_id']
    else:
        raise ValueError("employee_id not given")
    
    if 'first_name' in event:
        update_expression_values.append('first_name = :val_first_name')
        expression_attribute_values[':val_first_name'] = event['first_name']

    if 'last_name' in event:

        update_expression_values.append('last_name = :val_last_name')
        expression_attribute_values[':val_last_name'] = event['last_name']

    if len(update_expression_values) < 1:
        raise ValueError("first_name and last_name not given")

    seperator = ','
    
    update = table.update_item(
        Key={
            'employee_id': employee_id
        },
        ConditionExpression= 'attribute_exists(employee_id)',
        UpdateExpression='SET ' + seperator.join(update_expression_values),
        ExpressionAttributeValues=expression_attribute_values
    )

This could be broken down further to reuse the logic through a function that can perform these checks such as the below.

import boto3
import json

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

update_expression_values = []
expression_attribute_values = {}

def lambda_handler(event, context):
    global update_expression_values
    global expression_attribute_values
    
    update_expression_values = []
    expression_attribute_values = {}

    if 'employee_id' in event:
        employee_id = event['employee_id']
    else:
        raise ValueError("employee_id not given")
        
    process_event_key(event, 'first_name')
    process_event_key(event, 'last_name')
    process_event_key(event, 'new_value')

    if len(update_expression_values) < 1:
        raise ValueError("first_name and last_name not given")

    seperator = ','
    
    update = table.update_item(
        Key={
            'employee_id': employee_id
        },
        ConditionExpression= 'attribute_exists(employee_id)',
        UpdateExpression='SET ' + seperator.join(update_expression_values),
        ExpressionAttributeValues=expression_attribute_values
    )
    
def process_event_key(event, key):
    global update_expression_values
    global expression_attribute_values
    
    if key in event:
        update_expression_values.append(key + ' = :val_' + key)
        expression_attribute_values[':val_' + key] = event[key]

Test Event

{
  "new_value": "test",
  "employee_id": "value2",
  "last_name": "value3",
  "first_name": "value4"
}
Chris Williams
  • 32,215
  • 4
  • 30
  • 68
  • Actually what I am trying to do is to remove the `if condition` If I add another attribute like email to my dynamodb and I have to update it then I have to write more checks, I am trying to make my code small and efficient –  Aug 28 '20 at 19:15
  • if I generate following statement dynamically according to the keys in events `UpdateExpression = 'SET first_name = :val1'` It might work but I am unable to do it. Can you help? Something like this https://stackoverflow.com/questions/63283009/updateexpression-taking-dynamic-values-for-update-function-in-dynamodb-table-usi –  Aug 28 '20 at 19:37
  • Are you sure the event keys are correct? I added the test event I used. When I add `print('SET ' + seperator.join(update_expression_values))` just before I get the output `SET first_name = :val_first_name,last_name = :val_last_name,new_value = :val_new_value` – Chris Williams Aug 28 '20 at 19:41
  • I have attached a stackoverflow question link. I need something like that which will automatically update field that are provided. `If check` will keep on getting lengthier when attributes increases. https://stackoverflow.com/questions/63283009/updateexpression-taking-dynamic-values-for-update-function-in-dynamodb-table-usi –  Aug 28 '20 at 19:43
  • I have updated the question I have come up with the code that require some changes `employee_id` is auto generated and is primary key. `employee_id` is used here to fetch the content of that id. The other column in my table are first_name and last_name. It dynamically updates if only first_name is provided and leave last_name as it is. –  Aug 28 '20 at 19:57
  • The problem with your code is that it is looping over each attribute individually but trying to perform as if all attributes are in the update expression but you're only using a single key. In my opinion it is better to build your update statement and do this only once (with all keys that are updating). By doing this you avoid using multiple write credits performing updates to the same item – Chris Williams Aug 28 '20 at 20:10
  • Can you please explain your code I am having trouble understanding it ? –  Aug 28 '20 at 20:15
  • So effectively `process_event_key` checks if that key exists in the event, and if it does adds a new value to the update expression list and a new key to the dictionary `expression_attribute_values`. These contain the dynamic parts of the update query, once the update occurs it will join each value in the `update_expression_values` to make a string like `SET first_name=:val_first_name,last_name=:val_last_name`. The `expression_attribute_values` will contain the values. – Chris Williams Aug 28 '20 at 20:21
  • `global update_expression_values global` `expression_attribute_values` Why they are both in `process_event_key` and `handler` –  Aug 28 '20 at 21:20
  • 1
    By defining as global s it means you can use them in the process function without having to return them everytime. It simply appends to each global each time its processed. – Chris Williams Aug 29 '20 at 05:53
  • if we don't hardcode the values in `process_event_key(event, 'first_name')` and want to take the first_name directly from the json if the value exist update it otherwise create a new column. Can we do that? –  Sep 01 '20 at 20:14
  • You can do this by simply looping but you should try to at least validate its a valid column :) – Chris Williams Sep 02 '20 at 14:34