-
Notifications
You must be signed in to change notification settings - Fork 329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
added configarable sms support #2697
Open
DraKen0009
wants to merge
8
commits into
ohcnetwork:develop
Choose a base branch
from
DraKen0009:adding-sms-providers
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
f673760
added configarable sms support
DraKen0009 72c9741
Merge branch 'develop' into adding-sms-providers
DraKen0009 5a034c6
fixed function mismatching
DraKen0009 e956dba
Merge branch 'develop' into adding-sms-providers
DraKen0009 8a8cb4a
Merge branch 'develop' into adding-sms-providers
DraKen0009 7e45603
updated as per comments
DraKen0009 98cfe0f
added tests
DraKen0009 617b8ae
Merge branch 'develop' into adding-sms-providers
DraKen0009 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
from typing import TYPE_CHECKING | ||
|
||
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 | ||
|
||
|
||
def initialize_backend( | ||
backend_name: str | None = None, fail_silently: bool = False, **kwargs | ||
) -> "SmsBackendBase": | ||
""" | ||
Load and configure an SMS backend. | ||
|
||
Args: | ||
backend_name (Optional[str]): The dotted path to the backend class. If None, the default backend from settings is used. | ||
fail_silently (bool): Whether to handle exceptions quietly. Defaults to False. | ||
|
||
Returns: | ||
SmsBackendBase: An initialized instance of the specified SMS backend. | ||
""" | ||
backend_class = import_string(backend_name or settings.SMS_BACKEND) | ||
return backend_class(fail_silently=fail_silently, **kwargs) | ||
|
||
|
||
def send_text_message( | ||
content: str = "", | ||
sender: str | None = None, | ||
recipients: str | list[str] | None = None, | ||
fail_silently: bool = False, | ||
backend_instance: type["SmsBackendBase"] | None = None, | ||
) -> int: | ||
""" | ||
Send a single SMS message to one or more recipients. | ||
|
||
Args: | ||
content (str): The message content to be sent. Defaults to an empty string. | ||
sender (Optional[str]): The sender's phone number. Defaults to None. | ||
recipients (Union[str, List[str], None]): A single recipient or a list of recipients. Defaults to None. | ||
fail_silently (bool): Whether to suppress exceptions during sending. Defaults to False. | ||
backend_instance (Optional[SmsBackendBase]): A pre-configured SMS backend instance. Defaults to None. | ||
|
||
Returns: | ||
int: The number of messages successfully sent. | ||
""" | ||
if isinstance(recipients, str): | ||
recipients = [recipients] | ||
message = TextMessage( | ||
content=content, | ||
sender=sender, | ||
recipients=recipients, | ||
backend=backend_instance, | ||
fail_silently=fail_silently, | ||
) | ||
return message.dispatch(fail_silently=fail_silently) | ||
|
||
|
||
def get_sms_backend( | ||
backend_name: str | None = None, fail_silently: bool = False, **kwargs | ||
) -> "SmsBackendBase": | ||
""" | ||
Load and return an SMS backend instance. | ||
|
||
Args: | ||
backend_name (Optional[str]): The dotted path to the backend class. If None, the default backend from settings is used. | ||
fail_silently (bool): Whether to suppress exceptions quietly. Defaults to False. | ||
**kwargs: Additional arguments passed to the backend constructor. | ||
|
||
Returns: | ||
SmsBackendBase: An initialized instance of the specified SMS backend. | ||
""" | ||
return initialize_backend( | ||
backend_name=backend_name or settings.SMS_BACKEND, | ||
fail_silently=fail_silently, | ||
**kwargs, | ||
) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
from care.utils.sms.message import TextMessage | ||
|
||
|
||
class SmsBackendBase: | ||
""" | ||
Base class for all SMS backends. | ||
|
||
Subclasses should override the `send_message` method to provide the logic | ||
for sending SMS messages. | ||
""" | ||
|
||
def __init__(self, fail_silently: bool = False, **kwargs) -> None: | ||
""" | ||
Initialize the SMS backend. | ||
|
||
Args: | ||
fail_silently (bool): Whether to suppress exceptions during message sending. Defaults to False. | ||
**kwargs: Additional arguments for backend configuration. | ||
""" | ||
self.fail_silently = fail_silently | ||
|
||
def send_message(self, message: TextMessage) -> int: | ||
""" | ||
Send a text message. | ||
|
||
Subclasses must implement this method to handle the logic for sending | ||
messages using the specific backend. | ||
|
||
Args: | ||
message (TextMessage): The message to be sent. | ||
|
||
Raises: | ||
NotImplementedError: If the method is not implemented in a subclass. | ||
|
||
Returns: | ||
int: The number of messages successfully sent. | ||
""" | ||
raise NotImplementedError("Subclasses must implement `send_message`.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import sys | ||
import threading | ||
|
||
from care.utils.sms.backend.base import SmsBackendBase | ||
from care.utils.sms.message import TextMessage | ||
|
||
|
||
class ConsoleBackend(SmsBackendBase): | ||
""" | ||
Outputs SMS messages to the console for debugging purposes. | ||
""" | ||
|
||
def __init__(self, *args, stream=None, **kwargs) -> None: | ||
""" | ||
Initialize the ConsoleBackend. | ||
|
||
Args: | ||
stream (Optional[TextIO]): The output stream to write messages to. Defaults to sys.stdout. | ||
*args: Additional arguments for the superclass. | ||
**kwargs: Additional keyword arguments for the superclass. | ||
""" | ||
super().__init__(*args, **kwargs) | ||
self.stream = stream or sys.stdout | ||
self._lock = threading.RLock() | ||
|
||
def send_message(self, message: TextMessage) -> int: | ||
""" | ||
Write the SMS message to the console. | ||
|
||
Args: | ||
message (TextMessage): The message to be sent. | ||
|
||
Returns: | ||
int: The number of messages successfully "sent" (i.e., written to the console). | ||
""" | ||
sent_count = 0 | ||
with self._lock: | ||
for recipient in message.recipients: | ||
self.stream.write( | ||
f"From: {message.sender}\nTo: {recipient}\nContent: {message.content}\n{'-' * 100}\n" | ||
) | ||
sent_count += 1 | ||
self.stream.flush() | ||
return sent_count |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
from django.conf import settings | ||
from django.core.exceptions import ImproperlyConfigured | ||
|
||
from care.utils.sms.backend.base import SmsBackendBase | ||
from care.utils.sms.message import TextMessage | ||
|
||
try: | ||
import boto3 | ||
from botocore.exceptions import ClientError | ||
|
||
HAS_BOTO3 = True | ||
except ImportError: | ||
HAS_BOTO3 = False | ||
|
||
|
||
class SnsBackend(SmsBackendBase): | ||
""" | ||
Sends SMS messages using AWS SNS. | ||
""" | ||
|
||
_sns_client = None | ||
|
||
@classmethod | ||
def _get_client(cls): | ||
""" | ||
Get or create the SNS client. | ||
|
||
Returns: | ||
boto3.Client: The shared SNS client. | ||
""" | ||
if cls._sns_client is None: | ||
region_name = getattr(settings, "SNS_REGION", None) | ||
|
||
if not HAS_BOTO3: | ||
raise ImproperlyConfigured( | ||
"Boto3 library is required but not installed." | ||
) | ||
|
||
if getattr(settings, "SNS_ROLE_BASED_MODE", False): | ||
if not region_name: | ||
raise ImproperlyConfigured( | ||
"AWS SNS is not configured. Check 'SNS_REGION' in settings." | ||
) | ||
cls._sns_client = boto3.client( | ||
"sns", | ||
region_name=region_name, | ||
) | ||
else: | ||
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." | ||
) | ||
cls._sns_client = boto3.client( | ||
"sns", | ||
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: | ||
""" | ||
Send a text message using AWS SNS. | ||
|
||
Args: | ||
message (TextMessage): The message to be sent. | ||
|
||
Returns: | ||
int: The number of messages successfully sent. | ||
""" | ||
sns_client = self._get_client() | ||
successful_sends = 0 | ||
|
||
for recipient in message.recipients: | ||
try: | ||
sns_client.publish( | ||
PhoneNumber=recipient, | ||
Message=message.content, | ||
) | ||
successful_sends += 1 | ||
except ClientError as error: | ||
if not self.fail_silently: | ||
raise error | ||
DraKen0009 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return successful_sends |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
from typing import TYPE_CHECKING | ||
|
||
from django.conf import settings | ||
|
||
if TYPE_CHECKING: | ||
from care.utils.sms.backend.base import SmsBackendBase | ||
|
||
|
||
class TextMessage: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this class created twice, am i missing something? |
||
""" | ||
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, | ||
fail_silently: bool = False, | ||
) -> 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 not self.backend: | ||
from care.utils.sms import get_sms_backend | ||
|
||
self.backend = get_sms_backend(fail_silently=fail_silently) | ||
|
||
if isinstance(self.recipients, str): | ||
raise ValueError("Recipients should be a list of phone numbers.") | ||
|
||
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.backend | ||
return connection.send_message(self) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can create an instance of the backend class in the settings file itself and re-use it, we dont have to go through the configuration everytime.