mirror of
https://github.com/GSA/notifications-api.git
synced 2025-12-20 07:21:13 -05:00
notify-api-412 use black to enforce python style standards
This commit is contained in:
@@ -5,26 +5,28 @@ AWS_CLIENT_CONFIG = Config(
|
||||
# endpoints. See https://aws.amazon.com/compliance/fips/ for more
|
||||
# information.
|
||||
s3={
|
||||
'addressing_style': 'virtual',
|
||||
"addressing_style": "virtual",
|
||||
},
|
||||
use_fips_endpoint=True
|
||||
use_fips_endpoint=True,
|
||||
)
|
||||
STATISTICS_REQUESTED = 'requested'
|
||||
STATISTICS_DELIVERED = 'delivered'
|
||||
STATISTICS_FAILURE = 'failure'
|
||||
STATISTICS_REQUESTED = "requested"
|
||||
STATISTICS_DELIVERED = "delivered"
|
||||
STATISTICS_FAILURE = "failure"
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
'''
|
||||
"""
|
||||
Base Exceptions for sending notifications that fail
|
||||
'''
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class Client(object):
|
||||
'''
|
||||
"""
|
||||
Base client for sending notifications.
|
||||
'''
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -46,10 +48,10 @@ class NotificationProviderClients(object):
|
||||
return self.email_clients.get(name)
|
||||
|
||||
def get_client_by_name_and_type(self, name, notification_type):
|
||||
assert notification_type in ['email', 'sms'] # nosec B101
|
||||
assert notification_type in ["email", "sms"] # nosec B101
|
||||
|
||||
if notification_type == 'email':
|
||||
if notification_type == "email":
|
||||
return self.get_email_client(name)
|
||||
|
||||
if notification_type == 'sms':
|
||||
if notification_type == "sms":
|
||||
return self.get_sms_client(name)
|
||||
|
||||
@@ -19,7 +19,7 @@ class AwsCloudwatchClient(Client):
|
||||
region_name=cloud_config.sns_region,
|
||||
aws_access_key_id=cloud_config.sns_access_key,
|
||||
aws_secret_access_key=cloud_config.sns_secret_key,
|
||||
config=AWS_CLIENT_CONFIG
|
||||
config=AWS_CLIENT_CONFIG,
|
||||
)
|
||||
super(Client, self).__init__(*args, **kwargs)
|
||||
self.current_app = current_app
|
||||
@@ -27,10 +27,9 @@ class AwsCloudwatchClient(Client):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return 'cloudwatch'
|
||||
return "cloudwatch"
|
||||
|
||||
def _get_log(self, my_filter, log_group_name, sent_at):
|
||||
|
||||
# Check all cloudwatch logs from the time the notification was sent (currently 5 minutes previously) until now
|
||||
now = round(time.time() * 1000)
|
||||
beginning = sent_at
|
||||
@@ -43,48 +42,51 @@ class AwsCloudwatchClient(Client):
|
||||
filterPattern=my_filter,
|
||||
nextToken=next_token,
|
||||
startTime=beginning,
|
||||
endTime=now
|
||||
endTime=now,
|
||||
)
|
||||
else:
|
||||
response = self._client.filter_log_events(
|
||||
logGroupName=log_group_name,
|
||||
filterPattern=my_filter,
|
||||
startTime=beginning,
|
||||
endTime=now
|
||||
endTime=now,
|
||||
)
|
||||
log_events = response.get('events', [])
|
||||
log_events = response.get("events", [])
|
||||
all_log_events.extend(log_events)
|
||||
if len(log_events) > 0:
|
||||
# We found it
|
||||
break
|
||||
next_token = response.get('nextToken')
|
||||
next_token = response.get("nextToken")
|
||||
if not next_token:
|
||||
break
|
||||
return all_log_events
|
||||
|
||||
def check_sms(self, message_id, notification_id, created_at):
|
||||
|
||||
# TODO this clumsy approach to getting the account number will be fixed as part of notify-api #258
|
||||
account_number = cloud_config.ses_domain_arn
|
||||
account_number = account_number.replace('arn:aws:ses:us-west-2:', '')
|
||||
account_number = account_number.replace("arn:aws:ses:us-west-2:", "")
|
||||
account_number = account_number.split(":")
|
||||
account_number = account_number[0]
|
||||
|
||||
log_group_name = f'sns/us-west-2/{account_number}/DirectPublishToPhoneNumber'
|
||||
log_group_name = f"sns/us-west-2/{account_number}/DirectPublishToPhoneNumber"
|
||||
filter_pattern = '{$.notification.messageId="XXXXX"}'
|
||||
filter_pattern = filter_pattern.replace("XXXXX", message_id)
|
||||
all_log_events = self._get_log(filter_pattern, log_group_name, created_at)
|
||||
|
||||
if all_log_events and len(all_log_events) > 0:
|
||||
event = all_log_events[0]
|
||||
message = json.loads(event['message'])
|
||||
return "success", message['delivery']['providerResponse']
|
||||
message = json.loads(event["message"])
|
||||
return "success", message["delivery"]["providerResponse"]
|
||||
|
||||
log_group_name = f'sns/us-west-2/{account_number}/DirectPublishToPhoneNumber/Failure'
|
||||
log_group_name = (
|
||||
f"sns/us-west-2/{account_number}/DirectPublishToPhoneNumber/Failure"
|
||||
)
|
||||
all_failed_events = self._get_log(filter_pattern, log_group_name, created_at)
|
||||
if all_failed_events and len(all_failed_events) > 0:
|
||||
event = all_failed_events[0]
|
||||
message = json.loads(event['message'])
|
||||
return "failure", message['delivery']['providerResponse']
|
||||
message = json.loads(event["message"])
|
||||
return "failure", message["delivery"]["providerResponse"]
|
||||
|
||||
raise Exception(f'No event found for message_id {message_id} notification_id {notification_id}')
|
||||
raise Exception(
|
||||
f"No event found for message_id {message_id} notification_id {notification_id}"
|
||||
)
|
||||
|
||||
@@ -9,16 +9,15 @@ class DocumentDownloadError(Exception):
|
||||
|
||||
@classmethod
|
||||
def from_exception(cls, e):
|
||||
message = e.response.json()['error']
|
||||
message = e.response.json()["error"]
|
||||
status_code = e.response.status_code
|
||||
return cls(message, status_code)
|
||||
|
||||
|
||||
class DocumentDownloadClient:
|
||||
|
||||
def init_app(self, app):
|
||||
self.api_host = app.config['DOCUMENT_DOWNLOAD_API_HOST']
|
||||
self.auth_token = app.config['DOCUMENT_DOWNLOAD_API_KEY']
|
||||
self.api_host = app.config["DOCUMENT_DOWNLOAD_API_HOST"]
|
||||
self.auth_token = app.config["DOCUMENT_DOWNLOAD_API_KEY"]
|
||||
|
||||
def get_upload_url(self, service_id):
|
||||
return "{}/services/{}/documents".format(self.api_host, service_id)
|
||||
@@ -28,12 +27,12 @@ class DocumentDownloadClient:
|
||||
response = requests.post(
|
||||
self.get_upload_url(service_id),
|
||||
headers={
|
||||
'Authorization': "Bearer {}".format(self.auth_token),
|
||||
"Authorization": "Bearer {}".format(self.auth_token),
|
||||
},
|
||||
json={
|
||||
'document': file_contents,
|
||||
'is_csv': is_csv or False,
|
||||
}
|
||||
"document": file_contents,
|
||||
"is_csv": is_csv or False,
|
||||
},
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
@@ -42,14 +41,16 @@ class DocumentDownloadClient:
|
||||
# we don't want to tell users about that, so anything that isn't a 400 (virus scan failed or file type
|
||||
# unrecognised) should be raised as a 500 internal server error here.
|
||||
if e.response is None:
|
||||
raise Exception(f'Unhandled document download error: {repr(e)}')
|
||||
raise Exception(f"Unhandled document download error: {repr(e)}")
|
||||
elif e.response.status_code == 400:
|
||||
error = DocumentDownloadError.from_exception(e)
|
||||
current_app.logger.info(
|
||||
'Document download request failed with error: {}'.format(error.message)
|
||||
"Document download request failed with error: {}".format(
|
||||
error.message
|
||||
)
|
||||
)
|
||||
raise error
|
||||
else:
|
||||
raise Exception(f'Unhandled document download error: {e.response.text}')
|
||||
raise Exception(f"Unhandled document download error: {e.response.text}")
|
||||
|
||||
return response.json()['document']['url']
|
||||
return response.json()["document"]["url"]
|
||||
|
||||
@@ -2,32 +2,34 @@ from app.clients import Client, ClientException
|
||||
|
||||
|
||||
class EmailClientException(ClientException):
|
||||
'''
|
||||
"""
|
||||
Base Exception for EmailClients
|
||||
'''
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EmailClientNonRetryableException(ClientException):
|
||||
'''
|
||||
"""
|
||||
Represents an error returned from the email client API with a 4xx response code
|
||||
that should not be retried and should instead be marked as technical failure.
|
||||
An example of this would be an email address that makes it through our
|
||||
validation rules but is rejected by SES. There is no point in retrying this type as
|
||||
it will always fail however many calls to SES. Whereas a throttling error would not
|
||||
use this exception as it may succeed if we retry
|
||||
'''
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EmailClient(Client):
|
||||
'''
|
||||
"""
|
||||
Base Email client for sending emails.
|
||||
'''
|
||||
"""
|
||||
|
||||
def send_email(self, *args, **kwargs):
|
||||
raise NotImplementedError('TODO Need to implement.')
|
||||
raise NotImplementedError("TODO Need to implement.")
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
raise NotImplementedError('TODO Need to implement.')
|
||||
raise NotImplementedError("TODO Need to implement.")
|
||||
|
||||
@@ -17,30 +17,30 @@ from app.clients.email import (
|
||||
from app.cloudfoundry_config import cloud_config
|
||||
|
||||
ses_response_map = {
|
||||
'Permanent': {
|
||||
"message": 'Hard bounced',
|
||||
"Permanent": {
|
||||
"message": "Hard bounced",
|
||||
"success": False,
|
||||
"notification_status": 'permanent-failure',
|
||||
"notification_statistics_status": STATISTICS_FAILURE
|
||||
"notification_status": "permanent-failure",
|
||||
"notification_statistics_status": STATISTICS_FAILURE,
|
||||
},
|
||||
'Temporary': {
|
||||
"message": 'Soft bounced',
|
||||
"Temporary": {
|
||||
"message": "Soft bounced",
|
||||
"success": False,
|
||||
"notification_status": 'temporary-failure',
|
||||
"notification_statistics_status": STATISTICS_FAILURE
|
||||
"notification_status": "temporary-failure",
|
||||
"notification_statistics_status": STATISTICS_FAILURE,
|
||||
},
|
||||
'Delivery': {
|
||||
"message": 'Delivered',
|
||||
"Delivery": {
|
||||
"message": "Delivered",
|
||||
"success": True,
|
||||
"notification_status": 'delivered',
|
||||
"notification_statistics_status": STATISTICS_DELIVERED
|
||||
"notification_status": "delivered",
|
||||
"notification_statistics_status": STATISTICS_DELIVERED,
|
||||
},
|
||||
'Complaint': {
|
||||
"message": 'Complaint',
|
||||
"Complaint": {
|
||||
"message": "Complaint",
|
||||
"success": True,
|
||||
"notification_status": 'delivered',
|
||||
"notification_statistics_status": STATISTICS_DELIVERED
|
||||
}
|
||||
"notification_status": "delivered",
|
||||
"notification_statistics_status": STATISTICS_DELIVERED,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -57,61 +57,57 @@ class AwsSesClientThrottlingSendRateException(AwsSesClientException):
|
||||
|
||||
|
||||
class AwsSesClient(EmailClient):
|
||||
'''
|
||||
"""
|
||||
Amazon SES email client.
|
||||
'''
|
||||
"""
|
||||
|
||||
def init_app(self, *args, **kwargs):
|
||||
self._client = client(
|
||||
'ses',
|
||||
"ses",
|
||||
region_name=cloud_config.ses_region,
|
||||
aws_access_key_id=cloud_config.ses_access_key,
|
||||
aws_secret_access_key=cloud_config.ses_secret_key,
|
||||
config=AWS_CLIENT_CONFIG
|
||||
config=AWS_CLIENT_CONFIG,
|
||||
)
|
||||
super(AwsSesClient, self).__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return 'ses'
|
||||
return "ses"
|
||||
|
||||
def send_email(self,
|
||||
source,
|
||||
to_addresses,
|
||||
subject,
|
||||
body,
|
||||
html_body='',
|
||||
reply_to_address=None):
|
||||
def send_email(
|
||||
self, source, to_addresses, subject, body, html_body="", reply_to_address=None
|
||||
):
|
||||
try:
|
||||
if isinstance(to_addresses, str):
|
||||
to_addresses = [to_addresses]
|
||||
|
||||
reply_to_addresses = [reply_to_address] if reply_to_address else []
|
||||
|
||||
body = {
|
||||
'Text': {'Data': body}
|
||||
}
|
||||
body = {"Text": {"Data": body}}
|
||||
|
||||
if html_body:
|
||||
body.update({
|
||||
'Html': {'Data': html_body}
|
||||
})
|
||||
body.update({"Html": {"Data": html_body}})
|
||||
|
||||
start_time = monotonic()
|
||||
response = self._client.send_email(
|
||||
Source=source,
|
||||
Destination={
|
||||
'ToAddresses': [punycode_encode_email(addr) for addr in to_addresses],
|
||||
'CcAddresses': [],
|
||||
'BccAddresses': []
|
||||
"ToAddresses": [
|
||||
punycode_encode_email(addr) for addr in to_addresses
|
||||
],
|
||||
"CcAddresses": [],
|
||||
"BccAddresses": [],
|
||||
},
|
||||
Message={
|
||||
'Subject': {
|
||||
'Data': subject,
|
||||
"Subject": {
|
||||
"Data": subject,
|
||||
},
|
||||
'Body': body
|
||||
"Body": body,
|
||||
},
|
||||
ReplyToAddresses=[punycode_encode_email(addr) for addr in reply_to_addresses]
|
||||
ReplyToAddresses=[
|
||||
punycode_encode_email(addr) for addr in reply_to_addresses
|
||||
],
|
||||
)
|
||||
except botocore.exceptions.ClientError as e:
|
||||
_do_fancy_exception_handling(e)
|
||||
@@ -120,23 +116,25 @@ class AwsSesClient(EmailClient):
|
||||
raise AwsSesClientException(str(e))
|
||||
else:
|
||||
elapsed_time = monotonic() - start_time
|
||||
current_app.logger.info("AWS SES request finished in {}".format(elapsed_time))
|
||||
return response['MessageId']
|
||||
current_app.logger.info(
|
||||
"AWS SES request finished in {}".format(elapsed_time)
|
||||
)
|
||||
return response["MessageId"]
|
||||
|
||||
|
||||
def punycode_encode_email(email_address):
|
||||
# only the hostname should ever be punycode encoded.
|
||||
local, hostname = email_address.split('@')
|
||||
return '{}@{}'.format(local, hostname.encode('idna').decode('utf-8'))
|
||||
local, hostname = email_address.split("@")
|
||||
return "{}@{}".format(local, hostname.encode("idna").decode("utf-8"))
|
||||
|
||||
|
||||
def _do_fancy_exception_handling(e):
|
||||
# http://docs.aws.amazon.com/ses/latest/DeveloperGuide/api-error-codes.html
|
||||
if e.response['Error']['Code'] == 'InvalidParameterValue':
|
||||
raise EmailClientNonRetryableException(e.response['Error']['Message'])
|
||||
if e.response["Error"]["Code"] == "InvalidParameterValue":
|
||||
raise EmailClientNonRetryableException(e.response["Error"]["Message"])
|
||||
elif (
|
||||
e.response['Error']['Code'] == 'Throttling'
|
||||
and e.response['Error']['Message'] == 'Maximum sending rate exceeded.'
|
||||
e.response["Error"]["Code"] == "Throttling"
|
||||
and e.response["Error"]["Message"] == "Maximum sending rate exceeded."
|
||||
):
|
||||
raise AwsSesClientThrottlingSendRateException(str(e))
|
||||
else:
|
||||
|
||||
@@ -17,23 +17,14 @@ class AwsSesStubClient(EmailClient):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return 'ses'
|
||||
return "ses"
|
||||
|
||||
def send_email(self,
|
||||
source,
|
||||
to_addresses,
|
||||
subject,
|
||||
body,
|
||||
html_body='',
|
||||
reply_to_address=None):
|
||||
def send_email(
|
||||
self, source, to_addresses, subject, body, html_body="", reply_to_address=None
|
||||
):
|
||||
try:
|
||||
start_time = monotonic()
|
||||
response = request(
|
||||
"POST",
|
||||
self.url,
|
||||
data={"id": "dummy-data"},
|
||||
timeout=60
|
||||
)
|
||||
response = request("POST", self.url, data={"id": "dummy-data"}, timeout=60)
|
||||
response.raise_for_status()
|
||||
response_json = json.loads(response.text)
|
||||
|
||||
@@ -41,5 +32,7 @@ class AwsSesStubClient(EmailClient):
|
||||
raise AwsSesStubClientException(str(e))
|
||||
else:
|
||||
elapsed_time = monotonic() - start_time
|
||||
current_app.logger.info("AWS SES stub request finished in {}".format(elapsed_time))
|
||||
return response_json['MessageId']
|
||||
current_app.logger.info(
|
||||
"AWS SES stub request finished in {}".format(elapsed_time)
|
||||
)
|
||||
return response_json["MessageId"]
|
||||
|
||||
@@ -6,45 +6,49 @@ from flask import current_app
|
||||
|
||||
|
||||
class PerformancePlatformClient:
|
||||
|
||||
@property
|
||||
def active(self):
|
||||
return self._active
|
||||
|
||||
def init_app(self, app):
|
||||
self._active = app.config.get('PERFORMANCE_PLATFORM_ENABLED')
|
||||
self._active = app.config.get("PERFORMANCE_PLATFORM_ENABLED")
|
||||
if self.active:
|
||||
self.performance_platform_url = app.config.get('PERFORMANCE_PLATFORM_URL')
|
||||
self.performance_platform_endpoints = app.config.get('PERFORMANCE_PLATFORM_ENDPOINTS')
|
||||
self.performance_platform_url = app.config.get("PERFORMANCE_PLATFORM_URL")
|
||||
self.performance_platform_endpoints = app.config.get(
|
||||
"PERFORMANCE_PLATFORM_ENDPOINTS"
|
||||
)
|
||||
|
||||
def send_stats_to_performance_platform(self, payload):
|
||||
if self.active:
|
||||
bearer_token = self.performance_platform_endpoints[payload['dataType']]
|
||||
bearer_token = self.performance_platform_endpoints[payload["dataType"]]
|
||||
headers = {
|
||||
'Content-Type': "application/json",
|
||||
'Authorization': 'Bearer {}'.format(bearer_token)
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer {}".format(bearer_token),
|
||||
}
|
||||
resp = requests.post(
|
||||
self.performance_platform_url + payload['dataType'],
|
||||
self.performance_platform_url + payload["dataType"],
|
||||
json=payload,
|
||||
headers=headers
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
if resp.status_code == 200:
|
||||
current_app.logger.info(
|
||||
"Updated performance platform successfully with payload {}".format(json.dumps(payload))
|
||||
"Updated performance platform successfully with payload {}".format(
|
||||
json.dumps(payload)
|
||||
)
|
||||
)
|
||||
else:
|
||||
current_app.logger.error(
|
||||
"Performance platform update request failed for payload with response details: {} '{}'".format(
|
||||
json.dumps(payload),
|
||||
resp.status_code
|
||||
json.dumps(payload), resp.status_code
|
||||
)
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
@staticmethod
|
||||
def format_payload(*, dataset, start_time, group_name, group_value, count, period='day'):
|
||||
def format_payload(
|
||||
*, dataset, start_time, group_name, group_value, count, period="day"
|
||||
):
|
||||
"""
|
||||
:param dataset - the name of the overall graph, as referred to in the endpoint.
|
||||
:param start_time - UTC midnight of the day we're sending stats for
|
||||
@@ -54,14 +58,16 @@ class PerformancePlatformClient:
|
||||
:param period - the period that this data covers - "day", "week", "month", "quarter".
|
||||
"""
|
||||
payload = {
|
||||
'_timestamp': start_time,
|
||||
'service': 'govuk-notify',
|
||||
'dataType': dataset,
|
||||
'period': period,
|
||||
'count': count,
|
||||
"_timestamp": start_time,
|
||||
"service": "govuk-notify",
|
||||
"dataType": dataset,
|
||||
"period": period,
|
||||
"count": count,
|
||||
group_name: group_value,
|
||||
}
|
||||
payload['_id'] = PerformancePlatformClient.generate_payload_id(payload, group_name)
|
||||
payload["_id"] = PerformancePlatformClient.generate_payload_id(
|
||||
payload, group_name
|
||||
)
|
||||
return payload
|
||||
|
||||
@staticmethod
|
||||
@@ -69,12 +75,12 @@ class PerformancePlatformClient:
|
||||
"""
|
||||
group_name is the name of the group - eg "channel" or "status"
|
||||
"""
|
||||
payload_string = '{}{}{}{}{}'.format(
|
||||
payload['_timestamp'],
|
||||
payload['service'],
|
||||
payload_string = "{}{}{}{}{}".format(
|
||||
payload["_timestamp"],
|
||||
payload["service"],
|
||||
payload[group_name],
|
||||
payload['dataType'],
|
||||
payload['period']
|
||||
payload["dataType"],
|
||||
payload["period"],
|
||||
)
|
||||
_id = base64.b64encode(payload_string.encode('utf-8'))
|
||||
return _id.decode('utf-8')
|
||||
_id = base64.b64encode(payload_string.encode("utf-8"))
|
||||
return _id.decode("utf-8")
|
||||
|
||||
@@ -21,7 +21,7 @@ class AwsSnsClient(SmsClient):
|
||||
region_name=cloud_config.sns_region,
|
||||
aws_access_key_id=cloud_config.sns_access_key,
|
||||
aws_secret_access_key=cloud_config.sns_secret_key,
|
||||
config=AWS_CLIENT_CONFIG
|
||||
config=AWS_CLIENT_CONFIG,
|
||||
)
|
||||
super(SmsClient, self).__init__(*args, **kwargs)
|
||||
self.current_app = current_app
|
||||
@@ -29,7 +29,7 @@ class AwsSnsClient(SmsClient):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return 'sns'
|
||||
return "sns"
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
||||
@@ -42,7 +42,9 @@ class AwsSnsClient(SmsClient):
|
||||
|
||||
for match in phonenumbers.PhoneNumberMatcher(to, "US"):
|
||||
matched = True
|
||||
to = phonenumbers.format_number(match.number, phonenumbers.PhoneNumberFormat.E164)
|
||||
to = phonenumbers.format_number(
|
||||
match.number, phonenumbers.PhoneNumberFormat.E164
|
||||
)
|
||||
|
||||
# See documentation
|
||||
# https://docs.aws.amazon.com/sns/latest/dg/sms_publish-to-phone.html#sms_publish_sdk
|
||||
@@ -66,14 +68,18 @@ class AwsSnsClient(SmsClient):
|
||||
|
||||
try:
|
||||
start_time = monotonic()
|
||||
response = self._client.publish(PhoneNumber=to, Message=content, MessageAttributes=attributes)
|
||||
response = self._client.publish(
|
||||
PhoneNumber=to, Message=content, MessageAttributes=attributes
|
||||
)
|
||||
except botocore.exceptions.ClientError as e:
|
||||
raise str(e)
|
||||
except Exception as e:
|
||||
raise str(e)
|
||||
finally:
|
||||
elapsed_time = monotonic() - start_time
|
||||
self.current_app.logger.info("AWS SNS request finished in {}".format(elapsed_time))
|
||||
self.current_app.logger.info(
|
||||
"AWS SNS request finished in {}".format(elapsed_time)
|
||||
)
|
||||
return response["MessageId"]
|
||||
|
||||
if not matched:
|
||||
|
||||
Reference in New Issue
Block a user