mirror of
https://github.com/GSA/notifications-api.git
synced 2025-12-28 19:31:40 -05:00
In response to: [^1].
The stacktrace conveys the same and more information. We don't do
anything different for each exception class, so there's no value
in having three of them over one exception.
I did think about DRYing-up the duplicate exception behaviour into
the base class one. This isn't ideal because the base class would
be making assumptions about how inheriting classes make requests,
which might change with future providers. Although it might be nice
to have more info in the top-level message, we'll still get it in
the stacktrace e.g.
ValueError: Expected 'code' to be '0'
During handling of the above exception, another exception occurred:
app.clients.sms.SmsClientResponseException: SMS client error (Invalid response JSON)
requests.exceptions.ReadTimeout
During handling of the above exception, another exception occurred:
app.clients.sms.SmsClientResponseException: SMS client error (Request failed)
[^1]: https://github.com/alphagov/notifications-api/pull/3493#discussion_r837363717
89 lines
3.3 KiB
Python
89 lines
3.3 KiB
Python
import json
|
|
import logging
|
|
|
|
from requests import RequestException, request
|
|
|
|
from app.clients.sms import SmsClient, SmsClientResponseException
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Firetext will send a delivery receipt with three different status codes.
|
|
# The `firetext_response` maps these codes to the notification statistics status and notification status.
|
|
# If we get a pending (status = 2) delivery receipt followed by a declined (status = 1) delivery receipt we will set
|
|
# the notification status to temporary-failure rather than permanent failure.
|
|
# See the code in the notification_dao.update_notifications_status_by_id
|
|
firetext_responses = {
|
|
'0': 'delivered',
|
|
'1': 'permanent-failure',
|
|
'2': 'pending'
|
|
}
|
|
|
|
firetext_codes = {
|
|
# code '000' means 'No errors reported'
|
|
'000': {'status': 'temporary-failure', 'reason': 'No error reported'},
|
|
'101': {'status': 'permanent-failure', 'reason': 'Unknown Subscriber'},
|
|
'102': {'status': 'temporary-failure', 'reason': 'Absent Subscriber'},
|
|
'103': {'status': 'temporary-failure', 'reason': 'Subscriber Busy'},
|
|
'104': {'status': 'temporary-failure', 'reason': 'No Subscriber Memory'},
|
|
'201': {'status': 'permanent-failure', 'reason': 'Invalid Number'},
|
|
'301': {'status': 'permanent-failure', 'reason': 'SMS Not Supported'},
|
|
'302': {'status': 'temporary-failure', 'reason': 'SMS Not Supported'},
|
|
'401': {'status': 'permanent-failure', 'reason': 'Message Rejected'},
|
|
'900': {'status': 'temporary-failure', 'reason': 'Routing Error'},
|
|
}
|
|
|
|
|
|
def get_firetext_responses(status, detailed_status_code=None):
|
|
detailed_status = firetext_codes[detailed_status_code]['reason'] if firetext_codes.get(
|
|
detailed_status_code, None
|
|
) else None
|
|
return (firetext_responses[status], detailed_status)
|
|
|
|
|
|
def get_message_status_and_reason_from_firetext_code(detailed_status_code):
|
|
return firetext_codes[detailed_status_code]['status'], firetext_codes[detailed_status_code]['reason']
|
|
|
|
|
|
class FiretextClient(SmsClient):
|
|
'''
|
|
FireText sms client.
|
|
'''
|
|
|
|
def init_app(self, *args, **kwargs):
|
|
super().init_app(*args, **kwargs)
|
|
self.api_key = self.current_app.config.get('FIRETEXT_API_KEY')
|
|
self.international_api_key = self.current_app.config.get('FIRETEXT_INTERNATIONAL_API_KEY')
|
|
self.url = self.current_app.config.get('FIRETEXT_URL')
|
|
|
|
@property
|
|
def name(self):
|
|
return 'firetext'
|
|
|
|
def try_send_sms(self, to, content, reference, international, sender):
|
|
data = {
|
|
"apiKey": self.international_api_key if international else self.api_key,
|
|
"from": sender,
|
|
"to": to.replace('+', ''),
|
|
"message": content,
|
|
"reference": reference
|
|
}
|
|
|
|
try:
|
|
response = request(
|
|
"POST",
|
|
self.url,
|
|
data=data,
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
try:
|
|
json.loads(response.text)
|
|
if response.json()['code'] != 0:
|
|
raise ValueError("Expected 'code' to be '0'")
|
|
except (ValueError, AttributeError):
|
|
raise SmsClientResponseException("Invalid response JSON")
|
|
except RequestException:
|
|
raise SmsClientResponseException("Request failed")
|
|
|
|
return response
|