Skip to content

Commit

Permalink
updated as per comments
Browse files Browse the repository at this point in the history
  • Loading branch information
DraKen0009 committed Jan 3, 2025
1 parent 8a8cb4a commit 7e45603
Show file tree
Hide file tree
Showing 10 changed files with 61 additions and 184 deletions.
4 changes: 1 addition & 3 deletions care/emr/api/otp_viewsets/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,13 @@ def send(self, request):
if settings.USE_SMS:
random_otp = rand_pass(settings.OTP_LENGTH)
try:
message = sms.TextMessage(
sms.send_text_message(
content=(
f"Open Healthcare Network Patient Management System Login, OTP is {random_otp} . "
"Please do not share this Confidential Login Token with anyone else"
),
recipients=[data.phone_number],
)
connection = sms.initialize_backend()
connection.send_message(message)
except Exception as e:
import logging

Expand Down
4 changes: 1 addition & 3 deletions care/facility/api/serializers/patient_otp.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,13 @@ def create(self, validated_data):
otp = rand_pass(settings.OTP_LENGTH)

if settings.USE_SMS:
message = sms.TextMessage(
sms.send_text_message(
content=(
f"Open Healthcare Network Patient Management System Login, OTP is {otp} . "
"Please do not share this Confidential Login Token with anyone else"
),
recipients=[otp_obj.phone_number],
)
connection = sms.initialize_backend()
connection.send_message(message)
elif settings.DEBUG:
print(otp, otp_obj.phone_number) # noqa: T201

Expand Down
4 changes: 1 addition & 3 deletions care/utils/notification_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,12 +371,10 @@ def generate(self):
medium == Notification.Medium.SMS.value
and settings.SEND_SMS_NOTIFICATION
):
message = sms.TextMessage(
sms.send_text_message(
content=self.generate_sms_message(),
recipients=self.generate_sms_phone_numbers(),
)
connection = sms.initialize_backend()
connection.send_message(message)
elif medium == Notification.Medium.SYSTEM.value:
if not self.message:
self.message = self.generate_system_message()
Expand Down
68 changes: 7 additions & 61 deletions care/utils/sms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,12 @@
from django.conf import settings
from django.utils.module_loading import import_string

from care.utils.sms.message import TextMessage

if TYPE_CHECKING:
from care.utils.sms.backend.base import SmsBackendBase


class TextMessage:
"""
Represents a text message for transmission to one or more recipients.
"""

def __init__(
self,
content: str = "",
sender: str | None = None,
recipients: list[str] | None = None,
backend: type["SmsBackendBase"] | None = None,
) -> None:
"""
Initialize a TextMessage instance.
Args:
content (str): The message content.
sender (Optional[str]): The sender's phone number.
recipients (Optional[List[str]]): List of recipient phone numbers.
backend (Optional[SmsBackendBase]): Backend for sending the message.
"""
self.content = content
self.sender = sender or getattr(settings, "DEFAULT_SMS_SENDER", "")
self.recipients = recipients or []
self.backend = backend

if isinstance(self.recipients, str):
raise ValueError("Recipients should be a list of phone numbers.")

def establish_backend(self, fail_silently: bool = False) -> "SmsBackendBase":
"""
Obtain or initialize the backend for sending messages.
Args:
fail_silently (bool): Whether to suppress errors during backend initialization.
Returns:
SmsBackendBase: An instance of the configured backend.
"""
if not self.backend:
self.backend = get_sms_backend(fail_silently=fail_silently)
return self.backend

def dispatch(self, fail_silently: bool = False) -> int:
"""
Send the message to all designated recipients.
Args:
fail_silently (bool): Whether to suppress errors during message sending.
Returns:
int: Count of successfully sent messages.
"""
if not self.recipients:
return 0

connection = self.establish_backend(fail_silently)
return connection.send_messages([self])


def initialize_backend(
backend_name: str | None = None, fail_silently: bool = False, **kwargs
) -> "SmsBackendBase":
Expand Down Expand Up @@ -107,7 +49,11 @@ def send_text_message(
if isinstance(recipients, str):
recipients = [recipients]
message = TextMessage(
content=content, sender=sender, recipients=recipients, backend=backend_instance
content=content,
sender=sender,
recipients=recipients,
backend=backend_instance,
fail_silently=fail_silently,
)
return message.dispatch(fail_silently=fail_silently)

Expand Down
3 changes: 2 additions & 1 deletion care/utils/sms/backend/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ def send_message(self, message: TextMessage) -> int:
with self._lock:
for recipient in message.recipients:
self.stream.write(
f"From: {message.sender}\nTo: {recipient}\nContent: {message.content}\n{'-' * 50}\n"
f"From: {message.sender}\nTo: {recipient}\nContent: {message.content}\n{'-' * 100}\n"
)
sent_count += 1
self.stream.flush()
return sent_count
70 changes: 37 additions & 33 deletions care/utils/sms/backend/sns.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,52 +18,57 @@ class SnsBackend(SmsBackendBase):
Sends SMS messages using AWS SNS.
"""

def __init__(self, fail_silently: bool = False, **kwargs) -> None:
"""
Initialize the SNS backend.
Args:
fail_silently (bool): Whether to suppress exceptions during initialization. Defaults to False.
**kwargs: Additional arguments for backend configuration.
_sns_client = None

Raises:
ImproperlyConfigured: If required AWS SNS settings are missing or boto3 is not installed.
@classmethod
def _get_client(cls):
"""
super().__init__(fail_silently=fail_silently, **kwargs)
Get or create the SNS client.
if not HAS_BOTO3 and not self.fail_silently:
raise ImproperlyConfigured("Boto3 library is required but not installed.")
Returns:
boto3.Client: The shared SNS client.
"""
if cls._sns_client is None:
region_name = getattr(settings, "SNS_REGION", None)

self.region_name = getattr(settings, "SNS_REGION", None)
self.access_key_id = getattr(settings, "SNS_ACCESS_KEY", None)
self.secret_access_key = getattr(settings, "SNS_SECRET_KEY", None)
if not HAS_BOTO3:
raise ImproperlyConfigured(
"Boto3 library is required but not installed."
)

self.sns_client = None
if HAS_BOTO3:
if getattr(settings, "SNS_ROLE_BASED_MODE", False):
if not self.region_name:
if not region_name:
raise ImproperlyConfigured(
"AWS SNS is not configured. Check 'SNS_REGION' in settings."
)
self.sns_client = boto3.client(
cls._sns_client = boto3.client(
"sns",
region_name=self.region_name,
region_name=region_name,
)
else:
if (
not self.region_name
or not self.access_key_id
or not self.secret_access_key
):
access_key_id = getattr(settings, "SNS_ACCESS_KEY", None)
secret_access_key = getattr(settings, "SNS_SECRET_KEY", None)
if not region_name or not access_key_id or not secret_access_key:
raise ImproperlyConfigured(
"AWS SNS credentials are not fully configured. Check 'SNS_REGION', 'SNS_ACCESS_KEY', and 'SNS_SECRET_KEY' in settings."
)
self.sns_client = boto3.client(
cls._sns_client = boto3.client(
"sns",
region_name=self.region_name,
aws_access_key_id=self.access_key_id,
aws_secret_access_key=self.secret_access_key,
region_name=region_name,
aws_access_key_id=access_key_id,
aws_secret_access_key=secret_access_key,
)
return cls._sns_client

def __init__(self, fail_silently: bool = False, **kwargs) -> None:
"""
Initialize the SNS backend.
Args:
fail_silently (bool): Whether to suppress exceptions during initialization. Defaults to False.
**kwargs: Additional arguments for backend configuration.
"""
super().__init__(fail_silently=fail_silently, **kwargs)

def send_message(self, message: TextMessage) -> int:
"""
Expand All @@ -75,13 +80,12 @@ def send_message(self, message: TextMessage) -> int:
Returns:
int: The number of messages successfully sent.
"""
if not self.sns_client:
return 0

sns_client = self._get_client()
successful_sends = 0

for recipient in message.recipients:
try:
self.sns_client.publish(
sns_client.publish(
PhoneNumber=recipient,
Message=message.content,
)
Expand Down
26 changes: 8 additions & 18 deletions care/utils/sms/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def __init__(
sender: str | None = None,
recipients: list[str] | None = None,
backend: type["SmsBackendBase"] | None = None,
fail_silently: bool = False,
) -> None:
"""
Initialize a TextMessage instance.
Expand All @@ -32,24 +33,13 @@ def __init__(
self.recipients = recipients or []
self.backend = backend

if isinstance(self.recipients, str):
raise ValueError("Recipients should be a list of phone numbers.")

def establish_backend(self, fail_silently: bool = False) -> "SmsBackendBase":
"""
Obtain or initialize the backend for sending messages.
Args:
fail_silently (bool): Whether to suppress errors during backend initialization.
Returns:
SmsBackendBase: An instance of the configured backend.
"""
from care.utils.sms import get_sms_backend

if not self.backend:
from care.utils.sms import get_sms_backend

self.backend = get_sms_backend(fail_silently=fail_silently)
return self.backend

if isinstance(self.recipients, str):
raise ValueError("Recipients should be a list of phone numbers.")

def dispatch(self, fail_silently: bool = False) -> int:
"""
Expand All @@ -64,5 +54,5 @@ def dispatch(self, fail_silently: bool = False) -> int:
if not self.recipients:
return 0

connection = self.establish_backend(fail_silently)
return connection.send_messages([self])
connection = self.backend
return connection.send_message(self)
62 changes: 0 additions & 62 deletions care/utils/tests/test_sms.py

This file was deleted.

2 changes: 2 additions & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,3 +648,5 @@
SNOWSTORM_DEPLOYMENT_URL = env(
"SNOWSTORM_DEPLOYMENT_URL", default="http://165.22.211.144/fhir"
)

SMS_BACKEND = "care.utils.sms.backend.console.ConsoleBackend"
2 changes: 2 additions & 0 deletions config/settings/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@
SNS_SECRET_KEY = env("SNS_SECRET_KEY", default="")
SNS_REGION = env("SNS_REGION", default="ap-south-1")
SNS_ROLE_BASED_MODE = env.bool("SNS_ROLE_BASED_MODE", default=False)
SMS_BACKEND = "care.utils.sms.backend.sns.SnsBackend"


# open id connect
JWKS = JsonWebKey.import_key_set(
Expand Down

0 comments on commit 7e45603

Please sign in to comment.