Example of update_item in dynamodb boto3

Question:

Following the documentation, I’m trying to create an update statement that will update or add if not exists only one attribute in a dynamodb table.

I’m trying this

response = table.update_item(
    Key={'ReleaseNumber': '1.0.179'},
    UpdateExpression='SET',
    ConditionExpression='Attr('ReleaseNumber').eq('1.0.179')',
    ExpressionAttributeNames={'attr1': 'val1'},
    ExpressionAttributeValues={'val1': 'false'}
)

The error I’m getting is:

botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the UpdateItem operation: ExpressionAttributeNames contains invalid key: Syntax error; key: "attr1"

If anyone has done anything similar to what I’m trying to achieve please share example.

Asked By: Dmitry R

||

Answers:

Found working example here, very important to list as Keys all the indexes of the table, this will require additional query before update, but it works.

response = table.update_item(
    Key={
        'ReleaseNumber': releaseNumber,
        'Timestamp': result[0]['Timestamp']
    },
    UpdateExpression="set Sanity = :r",
    ExpressionAttributeValues={
        ':r': 'false',
    },
    ReturnValues="UPDATED_NEW"
)
Answered By: Dmitry R

Details on dynamodb updates using boto3 seem incredibly sparse online, so I’m hoping these alternative solutions are useful.

get / put

import boto3

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

# get item
response = table.get_item(Key={'pkey': 'asdf12345'})
item = response['Item']

# update
item['status'] = 'complete'

# put (idempotent)
table.put_item(Item=item)

actual update

import boto3

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

table.update_item(
    Key={'pkey': 'asdf12345'},
    AttributeUpdates={
        'status': 'complete',
    },
)
Answered By: ryantuck

The original code example:

response = table.update_item(
    Key={'ReleaseNumber': '1.0.179'},
    UpdateExpression='SET',
    ConditionExpression='Attr('ReleaseNumber').eq('1.0.179')',
    ExpressionAttributeNames={'attr1': 'val1'},
    ExpressionAttributeValues={'val1': 'false'}
)

Fixed:

response = table.update_item(
    Key={'ReleaseNumber': '1.0.179'},
    UpdateExpression='SET #attr1 = :val1',
    ConditionExpression=Attr('ReleaseNumber').eq('1.0.179'),
    ExpressionAttributeNames={'#attr1': 'val1'},
    ExpressionAttributeValues={':val1': 'false'}
)

In the marked answer it was also revealed that there is a Range Key so that should also be included in the Key. The update_item method must seek to the exact record to be updated, there’s no batch updates, and you can’t update a range of values filtered to a condition to get to a single record. The ConditionExpression is there to be useful to make updates idempotent; i.e. don’t update the value if it is already that value. It’s not like a sql where clause.

Regarding the specific error seen.

ExpressionAttributeNames is a list of key placeholders for use in the UpdateExpression, useful if the key is a reserved word.

From the docs, “An expression attribute name must begin with a #, and be followed by one or more alphanumeric characters”. The error is because the code hasn’t used an ExpressionAttributeName that starts with a # and also not used it in the UpdateExpression.

ExpressionAttributeValues are placeholders for the values you want to update to, and they must start with :

Answered By: Davos

If you don’t want to check parameter by parameter for the update I wrote a cool function that would return the needed parameters to perform a update_item method using boto3.

def get_update_params(body):
    """Given a dictionary we generate an update expression and a dict of values
    to update a dynamodb table.

    Params:
        body (dict): Parameters to use for formatting.

    Returns:
        update expression, dict of values.
    """
    update_expression = ["set "]
    update_values = dict()

    for key, val in body.items():
        update_expression.append(f" {key} = :{key},")
        update_values[f":{key}"] = val

    return "".join(update_expression)[:-1], update_values

Here is a quick example:

def update(body):
    a, v = get_update_params(body)
    response = table.update_item(
        Key={'uuid':str(uuid)},
        UpdateExpression=a,
        ExpressionAttributeValues=dict(v)
        )
    return response

Based on the official example, here’s a simple and complete solution which could be used to manually update (not something I would recommend) a table used by a terraform S3 backend.

Let’s say this is the table data as shown by the AWS CLI:

$ aws dynamodb scan --table-name terraform_lock --region us-east-1
{
    "Items": [
        {
            "Digest": {
                "S": "2f58b12ae16dfb5b037560a217ebd752"
            },
            "LockID": {
                "S": "tf-aws.tfstate-md5"
            }
        }
    ],
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null
}

You could update it to a new digest (say you rolled back the state) as follows:

import boto3

dynamodb = boto3.resource('dynamodb', 'us-east-1')


try:
    table = dynamodb.Table('terraform_lock')
    response = table.update_item(
        Key={
            "LockID": "tf-aws.tfstate-md5"
        },
        UpdateExpression="set Digest=:newDigest",
        ExpressionAttributeValues={
            ":newDigest": "50a488ee9bac09a50340c02b33beb24b"
        },
        ReturnValues="UPDATED_NEW"
    )
except Exception as msg:
    print(f"Oops, could not update: {msg}")

Note the : at the start of ":newDigest": "50a488ee9bac09a50340c02b33beb24b" they’re easy to miss or forget.

Answered By: Nagev

An example to update any number of attributes given as a dict, and keep track of the number of updates. Works with reserved words (i.e name).

The following attribute names shouldn’t be used as we will overwrite the value: _inc, _start.

from typing import Dict
from boto3 import Session


def getDynamoDBSession(region: str = "eu-west-1"):
    """Connect to DynamoDB resource from boto3."""
    return Session().resource("dynamodb", region_name=region)


DYNAMODB = getDynamoDBSession()


def updateItemAndCounter(db_table: str, item_key: Dict, attributes: Dict) -> Dict:
    """
    Update item or create new. If the item already exists, return the previous value and
    increase the counter: update_counter.
    """
    table = DYNAMODB.Table(db_table)

    # Init update-expression
    update_expression = "SET"

    # Build expression-attribute-names, expression-attribute-values, and the update-expression
    expression_attribute_names = {}
    expression_attribute_values = {}
    for key, value in attributes.items():
        update_expression += f' #{key} = :{key},'  # Notice the "#" to solve issue with reserved keywords
        expression_attribute_names[f'#{key}'] = key
        expression_attribute_values[f':{key}'] = value

    # Add counter start and increment attributes
    expression_attribute_values[':_start'] = 0
    expression_attribute_values[':_inc'] = 1

    # Finish update-expression with our counter
    update_expression += " update_counter = if_not_exists(update_counter, :_start) + :_inc"

    return table.update_item(
        Key=item_key,
        UpdateExpression=update_expression,
        ExpressionAttributeNames=expression_attribute_names,
        ExpressionAttributeValues=expression_attribute_values,
        ReturnValues="ALL_OLD"
    )

Hope it might be useful to someone!

Answered By: eltbus

using previous answer from eltbus , it worked for me , except for minor bug,

You have to delete the extra comma using update_expression[:-1]

Small update of Jam M. Hernandez Quiceno’s answer, which includes ExpressionAttributeNames to prevent encoutering errors such as:

"errorMessage": "An error occurred (ValidationException) when calling the UpdateItem operation: 
Invalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: timestamp",

def get_update_params(body):
    """
    Given a dictionary of key-value pairs to update an item with in DynamoDB,
    generate three objects to be passed to UpdateExpression, ExpressionAttributeValues, 
    and ExpressionAttributeNames respectively.
    """
    update_expression = []
    attribute_values = dict()
    attribute_names = dict()

    for key, val in body.items():
        update_expression.append(f" #{key.lower()} = :{key.lower()}")
        attribute_values[f":{key.lower()}"] = val
        attribute_names[f"#{key.lower()}"] = key

    return "set " + ", ".join(update_expression), attribute_values, attribute_names

Example use:

update_expression, attribute_values, attribute_names = get_update_params(
    {"Status": "declined", "DeclinedBy": "username"}
)

response = table.update_item(
    Key={"uuid": "12345"},
    UpdateExpression=update_expression,
    ExpressionAttributeValues=attribute_values,
    ExpressionAttributeNames=attribute_names,
    ReturnValues="UPDATED_NEW"
)
print(response)
Answered By: Florian

In a simple way you can use below code to update item value with new one:

    response = table.update_item(
       Key={"my_id_name": "my_id_value"}, # to get record
            
       UpdateExpression="set item_key_name=:item_key_value", # Operation action (set)
       ExpressionAttributeValues={":value": "new_value"}, # item that you need to update
       
       ReturnValues="UPDATED_NEW" # optional for declarative message
       )
Answered By: Islam Salah