From 0b92210e165eb6175f3b0face7b8b4a1dcd15e48 Mon Sep 17 00:00:00 2001 From: Innokentii Konstantinov Date: Fri, 9 Jun 2023 13:21:38 +0800 Subject: [PATCH] Better simple phone provider (#2143) # What this PR does ## Which issue(s) this PR fixes ## Checklist - [ ] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --- .../phone_notifications/models/phone_call.py | 1 + engine/apps/phone_notifications/models/sms.py | 3 ++- .../phone_notifications/phone_provider.py | 14 +++++++++-- .../simple_phone_provider.py | 23 +++++++++++++++++-- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/engine/apps/phone_notifications/models/phone_call.py b/engine/apps/phone_notifications/models/phone_call.py index b4a9182b..cbad80f9 100644 --- a/engine/apps/phone_notifications/models/phone_call.py +++ b/engine/apps/phone_notifications/models/phone_call.py @@ -60,6 +60,7 @@ class PhoneCallRecord(models.Model): class ProviderPhoneCall(models.Model): """ ProviderPhoneCall is an interface between PhoneCallRecord and call data returned from PhoneProvider. + Concrete provider phone call should be inherited from ProviderPhoneCall. Some phone providers allows to track status of call or gather pressed digits (we use it to ack/resolve alert group). It is needed to link phone call and alert group without exposing internals of concrete phone provider to PhoneBackend. diff --git a/engine/apps/phone_notifications/models/sms.py b/engine/apps/phone_notifications/models/sms.py index 4bad9eb4..9763d706 100644 --- a/engine/apps/phone_notifications/models/sms.py +++ b/engine/apps/phone_notifications/models/sms.py @@ -67,8 +67,9 @@ class SMSRecord(models.Model): class ProviderSMS(models.Model): """ ProviderSMS is an interface between SMSRecord and call data returned from PhoneProvider. + Concrete provider sms be inherited from ProviderSMS. - The idea is same as for ProviderCall - to save provider specific data without exposing them to ProheBackend. + The idea is same as for ProviderCall - to save provider specific data without exposing them to PhoneBackend. """ class Meta: diff --git a/engine/apps/phone_notifications/phone_provider.py b/engine/apps/phone_notifications/phone_provider.py index 68f47721..ad72924a 100644 --- a/engine/apps/phone_notifications/phone_provider.py +++ b/engine/apps/phone_notifications/phone_provider.py @@ -15,9 +15,16 @@ class ProviderFlags: """ ProviderFlags is set of feature flags enabled for concrete provider. It is needed to show correct buttons in UI. + + Attributes: + configured: Indicates if provider LiveSettings are valid. If LiveSettings cannot be validated, return True. + test_sms: Indicates if provider allows to send test_sms + test_call: Indicates if provider allows to make test_call + verification_call: Indicates if provider allows to validate number via call + verification_sms: Indicates if provider allows to validate number via sms """ - configured: bool # indicates if provider live settings are present and valid + configured: bool test_sms: bool test_call: bool verification_call: bool @@ -29,7 +36,10 @@ class PhoneProvider(ABC): PhoneProvider is an interface to all phone providers. It is needed to hide details of external phone providers from core code. - New PhoneProviders should be added to settings.PHONE_PROVIDERS dict. + To implement custom phone provider: + 1. Implement your ConcretePhoneProvider inherited from PhoneProvider. + 2. Add needed env variables to django settings and to LiveSettings. + 3. Add your PhoneProvider to settings.PHONE_PROVIDERS dict. For reference, you can check: SimplePhoneProvider as example of tiny, but working provider. diff --git a/engine/apps/phone_notifications/simple_phone_provider.py b/engine/apps/phone_notifications/simple_phone_provider.py index f6d0df03..0333e2d8 100644 --- a/engine/apps/phone_notifications/simple_phone_provider.py +++ b/engine/apps/phone_notifications/simple_phone_provider.py @@ -1,9 +1,13 @@ +import logging from random import randint from django.core.cache import cache +from .exceptions import FailedToSendSMS, FailedToStartVerification from .phone_provider import PhoneProvider, ProviderFlags +logger = logging.getLogger(__name__) + class SimplePhoneProvider(PhoneProvider): """ @@ -15,12 +19,22 @@ class SimplePhoneProvider(PhoneProvider): self.send_sms(number, message) def send_sms(self, number, text): - print(f'SimplePhoneProvider.send_sms: send message "{text}" to {number}') + try: + self._write_to_stdout(number, text) + except Exception as e: + # example of handling provider exceptions and converting them to exceptions from core OnCall code. + logger.error(f"SimplePhoneProvider.send_sms: failed {e}") + raise FailedToSendSMS def send_verification_sms(self, number): code = str(randint(100000, 999999)) cache.set(self._cache_key(number), code, timeout=10 * 60) - self.send_sms(number, f"Your verification code is {code}") + try: + self._write_to_stdout(number, f"Your verification code is {code}") + except Exception as e: + # Example of handling provider exceptions and converting them to exceptions from core OnCall code. + logger.error(f"SimplePhoneProvider.send_verification_sms: failed {e}") + raise FailedToStartVerification def finish_verification(self, number, code): has = cache.get(self._cache_key(number)) @@ -32,6 +46,11 @@ class SimplePhoneProvider(PhoneProvider): def _cache_key(self, number): return f"simple_provider_{number}" + def _write_to_stdout(self, number, text): + # print is just example of sending sms. + # In real-life provider it will be some external api call. + print(f'send message "{text}" to {number}') + @property def flags(self) -> ProviderFlags: return ProviderFlags(