|
1 | 1 | """Add the filename to the audit table and check for duplicates.""" |
2 | 2 |
|
3 | | -from typing import Optional |
4 | | - |
5 | 3 | from common.clients import dynamodb_client, logger |
6 | 4 | from common.models.errors import UnhandledAuditTableError |
7 | 5 | from constants import AUDIT_TABLE_NAME, AuditTableKeys, FileStatus |
8 | 6 |
|
| 7 | +CONDITION_EXPRESSION = "attribute_exists(message_id)" |
| 8 | + |
9 | 9 |
|
10 | 10 | def change_audit_table_status_to_processed(file_key: str, message_id: str) -> None: |
11 | 11 | """Updates the status in the audit table to 'Processed' and returns the queue name.""" |
12 | 12 | try: |
13 | 13 | # Update the status in the audit table to "Processed" |
14 | | - dynamodb_client.update_item( |
| 14 | + response = dynamodb_client.update_item( |
15 | 15 | TableName=AUDIT_TABLE_NAME, |
16 | 16 | Key={AuditTableKeys.MESSAGE_ID: {"S": message_id}}, |
17 | 17 | UpdateExpression="SET #status = :status", |
18 | 18 | ExpressionAttributeNames={"#status": "status"}, |
19 | 19 | ExpressionAttributeValues={":status": {"S": FileStatus.PROCESSED}}, |
20 | | - ConditionExpression="attribute_exists(message_id)", |
| 20 | + ConditionExpression=CONDITION_EXPRESSION, |
| 21 | + ReturnValues="UPDATED_NEW", |
21 | 22 | ) |
22 | | - |
| 23 | + result = response.get("Attributes", {}).get("status").get("S") |
23 | 24 | logger.info( |
24 | 25 | "The status of %s file, with message id %s, was successfully updated to %s in the audit table", |
25 | 26 | file_key, |
26 | 27 | message_id, |
27 | | - FileStatus.PROCESSED, |
| 28 | + result, |
28 | 29 | ) |
29 | 30 |
|
30 | 31 | except Exception as error: # pylint: disable = broad-exception-caught |
31 | 32 | logger.error(error) |
32 | 33 | raise UnhandledAuditTableError(error) from error |
33 | 34 |
|
34 | 35 |
|
35 | | -def get_record_count_by_message_id(event_message_id: str) -> Optional[int]: |
36 | | - """Retrieves full audit entry by unique event message ID""" |
| 36 | +def get_record_count_and_failures_by_message_id(event_message_id: str) -> tuple[int, int]: |
| 37 | + """Retrieves total record count and total failures by unique event message ID""" |
37 | 38 | audit_record = dynamodb_client.get_item( |
38 | 39 | TableName=AUDIT_TABLE_NAME, Key={AuditTableKeys.MESSAGE_ID: {"S": event_message_id}} |
39 | 40 | ) |
40 | 41 |
|
41 | 42 | record_count = audit_record.get("Item", {}).get(AuditTableKeys.RECORD_COUNT, {}).get("N") |
| 43 | + failures_count = audit_record.get("Item", {}).get(AuditTableKeys.RECORDS_FAILED, {}).get("N") |
| 44 | + |
| 45 | + return int(record_count) if record_count else 0, int(failures_count) if failures_count else 0 |
| 46 | + |
| 47 | + |
| 48 | +def increment_records_failed_count(message_id: str) -> None: |
| 49 | + """ |
| 50 | + Increment a counter attribute safely, handling the case where it might not exist. |
| 51 | + From https://docs.aws.amazon.com/code-library/latest/ug/dynamodb_example_dynamodb_Scenario_AtomicCounterOperations_section.html |
| 52 | + """ |
| 53 | + increment_value = 1 |
| 54 | + initial_value = 0 |
| 55 | + |
| 56 | + try: |
| 57 | + # Use SET with if_not_exists to safely increment the counter attribute |
| 58 | + dynamodb_client.update_item( |
| 59 | + TableName=AUDIT_TABLE_NAME, |
| 60 | + Key={AuditTableKeys.MESSAGE_ID: {"S": message_id}}, |
| 61 | + UpdateExpression="SET #attribute = if_not_exists(#attribute, :initial) + :increment", |
| 62 | + ExpressionAttributeNames={"#attribute": AuditTableKeys.RECORDS_FAILED}, |
| 63 | + ExpressionAttributeValues={":increment": {"N": str(increment_value)}, ":initial": {"N": str(initial_value)}}, |
| 64 | + ConditionExpression=CONDITION_EXPRESSION, |
| 65 | + ReturnValues="UPDATED_NEW", |
| 66 | + ) |
| 67 | + |
| 68 | + except Exception as error: # pylint: disable = broad-exception-caught |
| 69 | + logger.error(error) |
| 70 | + raise UnhandledAuditTableError(error) from error |
42 | 71 |
|
43 | | - if not record_count: |
44 | | - return None |
45 | 72 |
|
46 | | - return int(record_count) |
| 73 | +def set_audit_record_success_count_and_end_time( |
| 74 | + file_key: str, message_id: str, success_count: int, ingestion_end_time: str |
| 75 | +) -> None: |
| 76 | + """Sets the 'records_succeeded' and 'ingestion_end_time' attributes for the given audit record""" |
| 77 | + update_expression = ( |
| 78 | + f"SET #{AuditTableKeys.INGESTION_END_TIME} = :{AuditTableKeys.INGESTION_END_TIME}" |
| 79 | + f", #{AuditTableKeys.RECORDS_SUCCEEDED} = :{AuditTableKeys.RECORDS_SUCCEEDED}" |
| 80 | + ) |
| 81 | + expression_attr_names = { |
| 82 | + f"#{AuditTableKeys.INGESTION_END_TIME}": AuditTableKeys.INGESTION_END_TIME, |
| 83 | + f"#{AuditTableKeys.RECORDS_SUCCEEDED}": AuditTableKeys.RECORDS_SUCCEEDED, |
| 84 | + } |
| 85 | + expression_attr_values = { |
| 86 | + f":{AuditTableKeys.INGESTION_END_TIME}": {"S": ingestion_end_time}, |
| 87 | + f":{AuditTableKeys.RECORDS_SUCCEEDED}": {"N": str(success_count)}, |
| 88 | + } |
| 89 | + |
| 90 | + try: |
| 91 | + dynamodb_client.update_item( |
| 92 | + TableName=AUDIT_TABLE_NAME, |
| 93 | + Key={AuditTableKeys.MESSAGE_ID: {"S": message_id}}, |
| 94 | + UpdateExpression=update_expression, |
| 95 | + ExpressionAttributeNames=expression_attr_names, |
| 96 | + ExpressionAttributeValues=expression_attr_values, |
| 97 | + ConditionExpression=CONDITION_EXPRESSION, |
| 98 | + ) |
| 99 | + except Exception as error: # pylint: disable = broad-exception-caught |
| 100 | + logger.error(error) |
| 101 | + raise UnhandledAuditTableError(error) from error |
| 102 | + |
| 103 | + logger.info( |
| 104 | + "ingestion_end_time for %s file, with message id %s, was successfully updated to %s in the audit table", |
| 105 | + file_key, |
| 106 | + message_id, |
| 107 | + ingestion_end_time, |
| 108 | + ) |
| 109 | + logger.info( |
| 110 | + "records_succeeded for %s file, with message id %s, was successfully updated to %s in the audit table", |
| 111 | + file_key, |
| 112 | + message_id, |
| 113 | + str(success_count), |
| 114 | + ) |
0 commit comments