Пример update_item в динамоде boto3

Следуя документации, я пытаюсь создать оператор обновления, который будет обновлять или добавлять, если не существует, только один атрибут в таблице DynamodB.

Я пытаюсь это

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

Я получаю ошибку:

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

Если кто-то сделал что-то похожее на то, чего я пытаюсь достичь, поделитесь примером.

10 ответов

Решение

Найденный здесь рабочий пример, очень важно перечислить в качестве ключей все индексы таблицы, для этого потребуется дополнительный запрос перед обновлением, но он работает.

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

Подробная информация об обновлениях DynamodB с помощью boto3 Кажется невероятно редким онлайн, поэтому я надеюсь, что эти альтернативные решения полезны.

получить / положить

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)

актуальное обновление

import boto3

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

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

Если вы не хотите, чтобы проверить параметр параметра для обновления я написал функцию прохладной, что бы вернуть необходимые параметры для выполнения update_item метода с использованием 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

Вот краткий пример:

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

Исходный пример кода:

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

Исправлена:

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'}
)

В отмеченном ответе также было обнаружено, что есть ключ диапазона, который также должен быть включен в Key, Метод update_item должен искать точную запись, подлежащую обновлению, пакетных обновлений нет, и вы не можете обновить диапазон значений, отфильтрованных до условия, чтобы добраться до отдельной записи. ConditionExpression есть ли необходимость сделать обновления идемпотентными; т.е. не обновляйте значение, если оно уже является этим значением. Это не похоже на SQL, где пункт.

По поводу конкретной ошибки видно.

ExpressionAttributeNames список ключевых заполнителей для использования в выражении UpdateExpression, полезно, если ключ является зарезервированным словом.

Из документов "Имя атрибута выражения должно начинаться с символа # и сопровождаться одним или несколькими буквенно-цифровыми символами". Ошибка в том, что код не использовал ExpressionAttributeName, который начинается с # а также не использовал его в UpdateExpression,

ExpressionAttributeValues ​​являются заполнителями для значений, которые вы хотите обновить, и они должны начинаться с :

Основываясь на официальном примере, вот простое и полное решение, которое можно использовать для ручного обновления (что я бы не рекомендовал) таблицы, используемой серверной частью terraform S3.

Допустим, это данные таблицы, показанные в интерфейсе командной строки AWS:

$ 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
}

Вы можете обновить его до нового дайджеста (скажем, вы откатили состояние) следующим образом:

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}")

Обратите внимание : в начале ":newDigest": "50a488ee9bac09a50340c02b33beb24b" их легко пропустить или забыть.

Небольшое обновление ответа Jam M. Hernandez Quiceno, которое включаетExpressionAttributeNamesчтобы предотвратить появление ошибок, таких как:

      "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

Пример использования:

      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)

Простой пример с несколькими полями:

      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'},
    }
)

Простым способом вы можете использовать приведенный ниже код для обновления значения элемента новым:

          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
       )

Пример обновления любого количества атрибутов, заданных как dict, и следите за количеством обновлений. Работает с зарезервированными словами (т.е. name).

Не следует использовать следующие имена атрибутов, так как мы перезапишем значение: _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"
    )

Надеюсь, это может быть полезно для кого-то!

используя предыдущий ответ от eltbus, у меня это сработало, за исключением незначительной ошибки,

Вы должны удалить лишнюю запятую, используя update_expression[:-1]

Другие вопросы по тегам