fix get_job_from_s3

This commit is contained in:
Kenneth Kehl
2025-09-30 12:19:47 -07:00
parent 5cf08af58e
commit f4ba3cd7bf
9 changed files with 40 additions and 70 deletions

View File

@@ -389,7 +389,6 @@ def setup_sqlalchemy_events(app):
@event.listens_for(db.engine, "connect") @event.listens_for(db.engine, "connect")
def connect(dbapi_connection, connection_record): def connect(dbapi_connection, connection_record):
current_app.logger.debug(f"Using {dbapi_connection} {connection_record}")
pass pass
@event.listens_for(db.engine, "close") @event.listens_for(db.engine, "close")
@@ -398,7 +397,6 @@ def setup_sqlalchemy_events(app):
@event.listens_for(db.engine, "checkout") @event.listens_for(db.engine, "checkout")
def checkout(dbapi_connection, connection_record, connection_proxy): def checkout(dbapi_connection, connection_record, connection_proxy):
current_app.logger.debug(f"Using {dbapi_connection} {connection_proxy}")
try: try:
# this will overwrite any previous checkout_at timestamp # this will overwrite any previous checkout_at timestamp

View File

@@ -412,6 +412,10 @@ def get_job_from_s3(service_id, job_id):
that indicates things are permanently broken, we want to give up right away that indicates things are permanently broken, we want to give up right away
to save time. to save time.
""" """
job = get_job_cache(job_id)
if job:
return job
# We have to make sure the retries don't take up to much time, because # We have to make sure the retries don't take up to much time, because
# we might be retrieving dozens of jobs. So max time is: # we might be retrieving dozens of jobs. So max time is:
# 0.2 + 0.4 + 0.8 + 1.6 = 3.0 seconds # 0.2 + 0.4 + 0.8 + 1.6 = 3.0 seconds
@@ -479,7 +483,9 @@ def extract_phones(job, service_id, job_id):
try: try:
first_row = next(csv_reader) first_row = next(csv_reader)
except StopIteration: except StopIteration:
current_app.logger.warning(f"Empty CSV file for job {job_id} in service {service_id}") current_app.logger.warning(
f"Empty CSV file for job {job_id} in service {service_id}"
)
return {} return {}
phone_index = 0 phone_index = 0
@@ -511,7 +517,9 @@ def extract_phones(job, service_id, job_id):
def extract_personalisation(job): def extract_personalisation(job):
if job is None: if job is None:
current_app.logger.warning("No job data provided for personalisation extraction") current_app.logger.warning(
"No job data provided for personalisation extraction"
)
return {} return {}
if isinstance(job, dict): if isinstance(job, dict):
job = job[0] job = job[0]
@@ -520,7 +528,9 @@ def extract_personalisation(job):
return {} return {}
job = job.split("\r\n") job = job.split("\r\n")
if not job or not job[0]: if not job or not job[0]:
current_app.logger.warning("Empty job data after split for personalisation extraction") current_app.logger.warning(
"Empty job data after split for personalisation extraction"
)
return {} return {}
first_row = job[0] first_row = job[0]
job.pop(0) job.pop(0)
@@ -554,10 +564,8 @@ def get_phone_number_from_s3(service_id, job_id, job_row_number):
phones = get_job_cache(f"{job_id}_phones") phones = get_job_cache(f"{job_id}_phones")
if phones is None: if phones is None:
current_app.logger.debug("HAVE TO REEXTRACT PHONES!")
phones = extract_phones(job, service_id, job_id) phones = extract_phones(job, service_id, job_id)
set_job_cache(f"{job_id}_phones", phones) set_job_cache(f"{job_id}_phones", phones)
current_app.logger.debug(f"SETTING PHONES TO {phones}")
else: else:
phones = phones[ phones = phones[
0 0
@@ -606,9 +614,7 @@ def get_personalisation_from_s3(service_id, job_id, job_row_number):
def get_job_metadata_from_s3(service_id, job_id): def get_job_metadata_from_s3(service_id, job_id):
current_app.logger.debug(
f"#notify-debug-s3-partitioning CALLING GET_JOB_METADATA with {service_id}, {job_id}"
)
obj = get_s3_object(*get_job_location(service_id, job_id)) obj = get_s3_object(*get_job_location(service_id, job_id))
return obj.get()["Metadata"] return obj.get()["Metadata"]

View File

@@ -23,9 +23,6 @@ from notifications_utils.clients.redis import total_limit_cache_key
def deliver_sms(self, notification_id): def deliver_sms(self, notification_id):
"""Branch off to the final step in delivering the notification to sns and get delivery receipts.""" """Branch off to the final step in delivering the notification to sns and get delivery receipts."""
try: try:
current_app.logger.info(
"Start sending SMS for notification id: {}".format(notification_id)
)
notification = notifications_dao.get_notification_by_id(notification_id) notification = notifications_dao.get_notification_by_id(notification_id)
ansi_green = "\033[32m" ansi_green = "\033[32m"
ansi_reset = "\033[0m" ansi_reset = "\033[0m"

View File

@@ -5,10 +5,12 @@ from time import monotonic
import botocore import botocore
import phonenumbers import phonenumbers
from boto3 import client from boto3 import client
from flask import current_app
from app.clients import AWS_CLIENT_CONFIG from app.clients import AWS_CLIENT_CONFIG
from app.clients.sms import SmsClient from app.clients.sms import SmsClient
from app.cloudfoundry_config import cloud_config from app.cloudfoundry_config import cloud_config
from app.utils import hilite
class AwsSnsClient(SmsClient): class AwsSnsClient(SmsClient):
@@ -66,32 +68,13 @@ class AwsSnsClient(SmsClient):
} }
} }
default_num = " ".join(self.current_app.config["AWS_US_TOLL_FREE_NUMBER"])
if isinstance(sender, str):
non_scrubbable = " ".join(sender)
self.current_app.logger.info(
f"notify-debug-api-1385 sender {non_scrubbable} is a {type(sender)} \
default is a {type(default_num)}"
)
else:
self.current_app.logger.warning(
f"notify-debug-api-1385 sender is type {type(sender)}!! {sender}"
)
if self._valid_sender_number(sender): if self._valid_sender_number(sender):
self.current_app.logger.info(
f"notify-debug-api-1385 use valid sender {non_scrubbable} instead of default {default_num}"
)
attributes["AWS.MM.SMS.OriginationNumber"] = { attributes["AWS.MM.SMS.OriginationNumber"] = {
"DataType": "String", "DataType": "String",
"StringValue": sender, "StringValue": sender,
} }
else: else:
self.current_app.logger.info(
f"notify-debug-api-1385 use default {default_num} instead of invalid sender"
)
attributes["AWS.MM.SMS.OriginationNumber"] = { attributes["AWS.MM.SMS.OriginationNumber"] = {
"DataType": "String", "DataType": "String",
"StringValue": self.current_app.config["AWS_US_TOLL_FREE_NUMBER"], "StringValue": self.current_app.config["AWS_US_TOLL_FREE_NUMBER"],
@@ -102,6 +85,7 @@ class AwsSnsClient(SmsClient):
response = self._client.publish( response = self._client.publish(
PhoneNumber=to, Message=content, MessageAttributes=attributes PhoneNumber=to, Message=content, MessageAttributes=attributes
) )
current_app.logger.info(hilite(f"send response = {response}"))
except botocore.exceptions.ClientError as e: except botocore.exceptions.ClientError as e:
self.current_app.logger.exception("An error occurred sending sms") self.current_app.logger.exception("An error occurred sending sms")
raise str(e) raise str(e)

View File

@@ -7,7 +7,6 @@ from cachetools import TTLCache, cached
from flask import current_app from flask import current_app
from app import ( from app import (
aws_pinpoint_client,
create_uuid, create_uuid,
db, db,
notification_provider_clients, notification_provider_clients,
@@ -101,10 +100,6 @@ def send_sms_to_provider(notification):
notification.job_row_number, notification.job_row_number,
) )
# TODO This is temporary to test the capability of validating phone numbers
# The future home of the validation is TBD
_experimentally_validate_phone_numbers(recipient)
# TODO current we allow US phone numbers to be uploaded without the country code (1) # TODO current we allow US phone numbers to be uploaded without the country code (1)
# This will break certain international phone numbers (Norway, Denmark, East Timor) # This will break certain international phone numbers (Norway, Denmark, East Timor)
# When we officially announce support for international numbers, US numbers must contain # When we officially announce support for international numbers, US numbers must contain
@@ -134,9 +129,6 @@ def send_sms_to_provider(notification):
# interleave spaces to bypass PII scrubbing since sender number is not PII # interleave spaces to bypass PII scrubbing since sender number is not PII
arr = list(real_sender_number) arr = list(real_sender_number)
real_sender_number = " ".join(arr) real_sender_number = " ".join(arr)
current_app.logger.info(
f"#notify-debug-api-1701 real sender number going to AWS is {real_sender_number}"
)
message_id = provider.send_sms(**send_sms_kwargs) message_id = provider.send_sms(**send_sms_kwargs)
update_notification_message_id(notification.id, message_id) update_notification_message_id(notification.id, message_id)
@@ -162,18 +154,6 @@ def send_sms_to_provider(notification):
return message_id return message_id
def _experimentally_validate_phone_numbers(recipient):
if "+" not in recipient:
recipient_lookup = f"+{recipient}"
else:
recipient_lookup = recipient
if recipient_lookup in current_app.config["SIMULATED_SMS_NUMBERS"] and os.getenv(
"NOTIFY_ENVIRONMENT"
) in ["development", "test"]:
current_app.logger.info(hilite("#notify-debug-validate-phone-number fired"))
aws_pinpoint_client.validate_phone_number("01", recipient)
def _get_verify_code(notification): def _get_verify_code(notification):
key = f"2facode-{notification.id}".replace(" ", "") key = f"2facode-{notification.id}".replace(" ", "")
recipient = redis_store.get(key) recipient = redis_store.get(key)

View File

@@ -66,7 +66,9 @@ def get_job_by_service_and_job_id(service_id, job_id):
@job_blueprint.route("/<job_id>/status", methods=["GET"]) @job_blueprint.route("/<job_id>/status", methods=["GET"])
def get_job_status(service_id, job_id): def get_job_status(service_id, job_id):
"""Lightweight endpoint for polling job status with aggressive caching.""" """Lightweight endpoint for polling job status with aggressive caching."""
current_app.logger.info(f"Getting job status for service_id={service_id}, job_id={job_id}") current_app.logger.info(
f"Getting job status for service_id={service_id}, job_id={job_id}"
)
check_suspicious_id(service_id, job_id) check_suspicious_id(service_id, job_id)
# Check cache first (10 second TTL) # Check cache first (10 second TTL)
@@ -86,21 +88,29 @@ def get_job_status(service_id, job_id):
pending_count = 0 pending_count = 0
status_mapping = { status_mapping = {
'sent': ['sent'], "sent": ["sent"],
'delivered': ['sent'], "delivered": ["sent"],
'failed': ['failed', 'permanent-failure', 'temporary-failure', 'technical-failure', 'virus-scan-failed'], "failed": [
'pending': ['created', 'sending', 'pending', 'pending-virus-check'] "failed",
"permanent-failure",
"temporary-failure",
"technical-failure",
"virus-scan-failed",
],
"pending": ["created", "sending", "pending", "pending-virus-check"],
} }
for stat in statistics: for stat in statistics:
count = stat[0] if isinstance(stat, tuple) else stat.count count = stat[0] if isinstance(stat, tuple) else stat.count
status = stat[1] if isinstance(stat, tuple) else stat.status status = stat[1] if isinstance(stat, tuple) else stat.status
if status in status_mapping.get('delivered', []) or status in status_mapping.get('sent', []): if status in status_mapping.get(
"delivered", []
) or status in status_mapping.get("sent", []):
sent_count += count sent_count += count
elif status in status_mapping.get('failed', []): elif status in status_mapping.get("failed", []):
failed_count += count failed_count += count
elif status in status_mapping.get('pending', []): elif status in status_mapping.get("pending", []):
pending_count += count pending_count += count
else: else:
if job.processing_finished: if job.processing_finished:
@@ -114,7 +124,7 @@ def get_job_status(service_id, job_id):
"pending_count": pending_count, "pending_count": pending_count,
"total_count": job.notification_count, "total_count": job.notification_count,
"job_status": job.job_status, "job_status": job.job_status,
"processing_finished": job.processing_finished is not None "processing_finished": job.processing_finished is not None,
} }
if job.processing_finished: if job.processing_finished:
@@ -331,9 +341,6 @@ def create_job(service_id):
original_file_name = data.get("original_file_name") original_file_name = data.get("original_file_name")
data.update({"service": service_id}) data.update({"service": service_id})
try: try:
current_app.logger.info(
f"#notify-debug-s3-partitioning DATA IN CREATE_JOB: {data}"
)
data.update(**get_job_metadata_from_s3(service_id, data["id"])) data.update(**get_job_metadata_from_s3(service_id, data["id"]))
except KeyError: except KeyError:
raise InvalidRequest( raise InvalidRequest(

View File

@@ -149,7 +149,6 @@ def debug_not_production(msg):
def emit_job_update_summary(job): def emit_job_update_summary(job):
from app import socketio from app import socketio
current_app.logger.info(f"Emitting summary for job {job.id}")
socketio.emit( socketio.emit(
"job_updated", "job_updated",
{ {

View File

@@ -84,9 +84,6 @@ class RecipientCSV:
try: try:
self._guestlist = list(value) self._guestlist = list(value)
except TypeError: except TypeError:
current_app.logger.exception(
f"Type error setting the guest list to {value}"
)
self._guestlist = [] self._guestlist = []
@property @property

View File

@@ -1238,7 +1238,7 @@ def test_get_job_status_returns_lightweight_response(admin_request, sample_job):
"pending_count", "pending_count",
"total_count", "total_count",
"job_status", "job_status",
"processing_finished" "processing_finished",
} }
# Verify counts are correct # Verify counts are correct
@@ -1247,7 +1247,9 @@ def test_get_job_status_returns_lightweight_response(admin_request, sample_job):
assert resp_json["pending_count"] == 2 assert resp_json["pending_count"] == 2
assert resp_json["total_count"] == sample_job.notification_count assert resp_json["total_count"] == sample_job.notification_count
assert resp_json["job_status"] == sample_job.job_status assert resp_json["job_status"] == sample_job.job_status
assert resp_json["processing_finished"] == (sample_job.processing_finished is not None) assert resp_json["processing_finished"] == (
sample_job.processing_finished is not None
)
def test_get_job_status_caches_response(admin_request, sample_job, mocker): def test_get_job_status_caches_response(admin_request, sample_job, mocker):