Example of update_item in dynamodb boto3
Asked Answered
C

10

78

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.

Colville answered 24/12, 2015 at 4:38 Comment(3)
I know is a very old question, but I'm wondering.... if you item already exist the "put_item" will overwrite it. Why should I use update_item if I can't change any index_key ?Archdiocese
@Archdiocese I guess to update not all fieldsMonday
The issue on your code was Key={'ReleaseNumber': '1.0.179'}, You must use the item's primary key. (I assumed the ReleaseNumber is not the primary key ?)Vann
C
90

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"
)
Colville answered 24/12, 2015 at 4:50 Comment(5)
It should be noted that your original error was referring to ExpressionAttributeNames which has been excluded from the sample provided in this answer.... while you do have to include all the values for the key to update an item this was not directly the error you originally had.Anna
Worth mentioning, Key here should have all the keys for that index whether Local Secondary Index or Global Secondary Index. As here, ReleaseNumber is primary key and Timestamp is a sort key by default.Maureenmaureene
This solution did not worked for me - I had to add ExpressionAttributeNames along with ExpressionAttributeValues while updating the item.Camfort
i tried the above but didn't work. tried using ExpressionAttributeNames but still didn't work. Getting the error botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the UpdateItem operation: The provided key element does not match the schemaMahalia
This accepted answer does not work. Edit queue for this answer is full, hence unable to edit. Another answer below should be marked as accepted instead, which uses "ExpressionAttributeNames". https://mcmap.net/q/262767/-example-of-update_item-in-dynamodb-boto3Padrone
T
52

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',
    },
)
Towpath answered 4/4, 2018 at 17:23 Comment(6)
Although using AttributeUpdates like this is correct and should still work (and I like the syntax more than UpdateExpression) , the documentation mentions that this method is legacy (and therefore might not work anymore at some point in time). Source: boto3.readthedocs.io/en/latest/reference/services/…Artful
AttributeUpdates is a legacy param in boto3, so probably better to use UpdateExpression in Dmitry R's answer aboveFelicita
AttributeUpdates seems like a much cleaner API. :(Farcy
@AndyHayden AttributeUpdates is simpler, but Expressions are more powerful because they can be parameterized for handling reserved word collisions, code injection and other issues.Phonation
The fact that they're taking AttributeUpdates away pisses me off significantly.Cipolin
you have no idea how the first get/put method was useful for me!Openhearth
E
28

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
Enthetic answered 26/5, 2020 at 19:59 Comment(5)
+1 since this is great, but note it has two bugs: 1. It doesn't handle reserved keywords (like name) 2. It doesn't handle attributes starting with symbols like __. Both issues can be solved by using ExpressionAttributeNamesOvermaster
Why do you exclude the last item in your join expression?Hautegaronne
@MarkRansom to remove trailing commaEnthetic
Ah, I didn't notice that the slice was on the final string and not the list. Makes sense now, thanks.Hautegaronne
','.join() will string them together with commas without a trailing one.Tiemannite
P
23

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 :

Phonation answered 8/11, 2018 at 3:53 Comment(2)
I have tried all other options mentioned in other posts on this but none of them work if we don't mention ExpressionAttributeNames - thanks!Camfort
The documentation suggests that values for eg Key should be specified as Key={'ReleaseNumber': {'S': '1.0.179'}}Champaign
S
5

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.

Shifty answered 10/9, 2020 at 16:18 Comment(0)
G
5

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)
Gaitan answered 1/11, 2022 at 9:56 Comment(0)
S
4

Simple example with multiple fields:

import boto3
dynamodb_client = boto3.client('dynamodb')

dynamodb_client.update_item(
    TableName=table_name,
    Key={
        'PK1': {'S': 'PRIMARY_KEY_VALUE'},
        'SK1': {'S': 'SECONDARY_KEY_VALUE'}
    }
    UpdateExpression='SET #field1 = :field1, #field2 = :field2',
    ExpressionAttributeNames={
        '#field1': 'FIELD_1_NAME',
        '#field2': 'FIELD_2_NAME',
    },
    ExpressionAttributeValues={
        ':field1': {'S': 'FIELD_1_VALUE'},
        ':field2': {'S': 'FIELD_2_VALUE'},
    }
)
Sabinasabine answered 24/12, 2022 at 18:29 Comment(0)
T
1

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!

Trichroism answered 16/11, 2021 at 15:56 Comment(0)
M
1

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
       )
Metapsychology answered 5/11, 2022 at 21:43 Comment(0)
P
-1

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

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

Premeditate answered 23/12, 2021 at 10:34 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Kantos

© 2022 - 2024 — McMap. All rights reserved.