def _update_field_in_table_if_exists( self, table: Table, field_value: t.Any, field_name: str ) -> bool: """ Only write the field for object in table if the objects with matchig PK and SK already exist (also updates updated_at). Returns true if object existed and therefore update was successful otherwise false. """ try: table.update_item( Key={ "PK": self.get_dynamodb_signal_key( self.SIGNAL_SOURCE_SHORTCODE, self.signal_id ), "SK": self.get_sort_key(self.privacy_group_id), }, # service_resource.Table.update_item's ConditionExpression params is not typed to use its own objects here... ConditionExpression=And(Attr("PK").exists(), Attr("SK").exists()), # type: ignore ExpressionAttributeValues={ ":f": field_value, ":u": self.updated_at.isoformat(), }, ExpressionAttributeNames={ "#F": field_name, "#U": "UpdatedAt", }, UpdateExpression="SET #F = :f, #U = :u", ) except ClientError as e: if e.response["Error"]["Code"] != "ConditionalCheckFailedException": raise e return False return True
def write_to_table(self, table: Table): """ Write operations for this object need to be special cased (to avoid overwritting) Therefore we do not implement `to_dynamodb_item` however basically the body of that method is used in this class's impl of `write_to_table_if_not_found` """ # put_item does not support UpdateExpression table.update_item( Key={ "PK": self.get_dynamodb_content_key(self.content_id), "SK": self.get_dynamodb_content_type_key(), }, # If ContentRef exists it needs to match or BAD THING(tm) can happen... ConditionExpression=Or( Attr("ContentRef").not_exists(), Attr("ContentRef").eq(self.content_ref)), # type: ignore # Unfortunately while prod is happy with this on multiple lines pytest breaks... UpdateExpression= """SET ContentType = :ct, ContentRef = :cr, ContentRefType = :crt, SubmissionTimes = list_append(if_not_exists(SubmissionTimes, :empty_list), :s), CreatedAt = if_not_exists(CreatedAt, :c), UpdatedAt = :u ADD AdditionalFields :af""", ExpressionAttributeValues={ ":ct": self.content_type.get_name(), ":cr": self.content_ref, ":crt": self.content_ref_type.value, ":af": self.additional_fields if self.additional_fields else {self.ADDITIONAL_FIELDS_PLACE_HOLDER}, ":s": [s.isoformat() for s in self.submission_times], ":c": self.created_at.isoformat(), ":u": self.updated_at.isoformat(), ":empty_list": [], }, )
def dec(self, table: Table, by=1): """ Increment count. Default by 1, unless specified. """ table.update_item( Key={"PK": self.get_pkey(), "SK": self.get_skey()}, UpdateExpression="SET CurrentCount = if_not_exists(CurrentCount, :zero) - :by", ExpressionAttributeValues={":by": by, ":zero": 0}, )
def increment_counts(cls, table: Table, counts: t.Dict[str, int]): for pg in counts: table.update_item( # Couldn't find a way to do update_item in batch. Can optimize # if found. Key={ "PK": cls._get_pkey(), "SK": cls._get_skey(pg) }, UpdateExpression= "SET WriteCount = if_not_exists(WriteCount, :zero) + :by", ExpressionAttributeValues={ ":by": counts[pg], ":zero": 0 }, )
def write_to_table(self, table: Table): """ Write operations for this object need to be special cased (to avoid overwritting) Therefore we do not implement `to_dynamodb_item` If you're curious it would ~look like this: def to_dynamodb_item(self) -> dict: return { "PK": self.get_dynamodb_content_key(self.content_id), "SK": self.get_dynamodb_content_type_key(self.content_type), "ContentRef": self.content_ref, "ContentRefType": self.content_ref_type, "AdditionalFields": self.additional_fields, "SubmissionTimes": [s.isoformat() for s in self.submission_times], "CreatedOn": self.created_at.isoformat(), "UpdatedAt": self.updated_at.isoformat(), } """ # put_item does not support UpdateExpression table.update_item( Key={ "PK": self.get_dynamodb_content_key(self.content_id), "SK": self.get_dynamodb_content_type_key(self.content_type), }, # If ContentRef exists it needs to match or BAD THING(tm) can happen... ConditionExpression=Or( Attr("ContentRef").not_exists(), Attr("ContentRef").eq(self.content_ref)), # type: ignore # Unfortunately while prod is happy with this on multiple lines pytest breaks... UpdateExpression= """SET ContentRef = :cr, ContentRefType = :crt, SubmissionTimes = list_append(if_not_exists(SubmissionTimes, :empty_list), :s), CreatedAt = if_not_exists(CreatedAt, :c), UpdatedAt = :u ADD AdditionalFields :af""", ExpressionAttributeValues={ ":cr": self.content_ref, ":crt": self.content_ref_type.value, ":af": self.additional_fields if self.additional_fields else {"Placeholder"}, ":s": [s.isoformat() for s in self.submission_times], ":c": self.created_at.isoformat(), ":u": self.updated_at.isoformat(), ":empty_list": [], }, )
def update_item(table: Table, wizard: str, update_expression: str, attributes: dict, return_values: Any) -> Union[Any, bool]: try: db_response = table.update_item(Key={'username': wizard}, UpdateExpression=update_expression, ExpressionAttributeValues=attributes, ReturnValues=return_values) return db_response except Exception as e: print("Something went wrong: ", e) return False
def get_unique(cls, table: Table, signal_type: t.Type[SignalType], signal_value: str) -> "BankedSignalEntry": """ Write to the table if PK / SK does not exist. In either case (exists, not exists), return the current unique entry. This is a special use-case for BankedSignalEntry. If this is useful to other models, we can move it to a mixin or to dynamodb item. If trying to generify, note how the update_item query needs a custom update query based on what you are trying to write. Generifying may be harder than it seems. """ result = table.update_item( Key={ "PK": cls.get_pk(signal_type), "SK": cls.get_sk(signal_value), }, UpdateExpression= "SET SignalId = if_not_exists(SignalId, :signal_id), SignalType = :signal_type, SignalValue = :signal_value", ExpressionAttributeValues={ # Note we are generating a new uuid even though we don't always # expect it to get written. AFAIK, uuids are inexhaustible, and # generation performance is good enough to not worry about it. ":signal_id": str(uuid.uuid4()), ":signal_type": signal_type.get_name(), ":signal_value": signal_value, }, ReturnValues="ALL_NEW", ).get("Attributes") assert result is not None return BankedSignalEntry( signal_type=signal_type, signal_value=t.cast(str, result["SignalValue"]), signal_id=t.cast(str, result["SignalId"]), )