Пример 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]