Skip to content

Commit

Permalink
Add MessageBird backend (#13)
Browse files Browse the repository at this point in the history
Add initial **sms.backends.messagebird.SmsBackend** backend.

Fixes #6

Signed-off-by: Roald Nefs <info@roaldnefs.com>
  • Loading branch information
roaldnefs authored Feb 13, 2021
1 parent dab1de5 commit 41c4959
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 6 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
All notable changes in **django-sms** are documented below.

## [Unreleased]
### Added
- The **sms.backends.messagebird.SmsBackend** to send text messages using [MessageBird](https://messagebird.com/) ([#6](https://github.com/roaldnefs/django-sms/issues/6)).

### Changed
- Simplified the attributes of the **sms.signals.post_send** signal to include the instance of the originating **Message** instead of all attributes ([#11](https://github.com/roaldnefs/django-sms/pull/11)).

Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- [File backend](#file-backend)
- [In-memory backend](#in-memory-backend)
- [Dummy backend](#dummy-backend)
- [MessageBird backend](#messagebird-backend)
- [Defining a custom SMS backend](#defining-a-custom-sms-backend)
- [Signals](#signals)
- [sms.signals.post_send](#sms.signals.post_send)
Expand Down Expand Up @@ -183,6 +184,20 @@ SMS_BACKEND = 'sms.backends.dummy.SmsBackend'

This backend is not intended for use in production - it is provided as a convenience that can be used during development.

#### MessageBird backend
The [MessageBird](https://messagebird.com/) backend sends text messages using the [MessageBird SMS API](https://developers.messagebird.com/api/sms-messaging/#send-outbound-sms). To specify this backend, put the following in your settings:

```python
SMS_BACKEND = 'sms.backends.messagebird.SmsBackend'
MESSAGEBIRD_ACCESS_KEY = 'live_redacted-messagebird-access-key'
```

Make sure the MessageBird Python SDK is installed by running the following command:

```console
pip install "django-sms[messagebird]"
```

### Defining a custom SMS backend
If you need to change how text messages are sent you can write your own SMS backend. The **SMS_BACKEND** setting in your settings file is then the Python import path for you backend class.

Expand Down
5 changes: 4 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,8 @@ def long_description() -> str:
packages=find_packages(exclude=['tests', 'tests.*']),
include_package_data=True,
test_suite='tests.runtests.main',
install_requires=['Django>=2.2']
install_requires=['Django>=2.2'],
extras_require={
'messagebird': ['messagebird'],
}
)
6 changes: 1 addition & 5 deletions sms/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ class BaseSmsBackend:
# do something with connection
pass
"""
def __init__(
self,
fail_silently: bool = False,
**kwargs
) -> None:
def __init__(self, fail_silently: bool = False, **kwargs) -> None:
self.fail_silently = fail_silently

def open(self) -> bool:
Expand Down
59 changes: 59 additions & 0 deletions sms/backends/messagebird.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
SMS backend for sending text messages using MessageBird.
"""
from typing import List, Optional

from django.conf import settings # type: ignore
from django.core.exceptions import ImproperlyConfigured # type: ignore

from sms.backends.base import BaseSmsBackend
from sms.message import Message

try:
import messagebird # type: ignore
HAS_MESSAGEBIRD = True
except ImportError:
HAS_MESSAGEBIRD = False


class SmsBackend(BaseSmsBackend):
def __init__(self, fail_silently: bool = False, **kwargs) -> None:
super().__init__(fail_silently=fail_silently, **kwargs)

if not HAS_MESSAGEBIRD and not self.fail_silently:
raise ImproperlyConfigured(
"You're using the SMS backend "
"'sms.backends.messagebird.SmsBackend' without having "
"'messagebird' installed. Install 'messagebird' or use "
"another SMS backend."
)

access_key: Optional[str] = getattr(settings, 'MESSAGEBIRD_ACCESS_KEY')
if not access_key and not self.fail_silently:
raise ImproperlyConfigured(
"You're using the SMS backend "
"'sms.backends.messagebird.SmsBackend' without having the "
"setting 'MESSAGEBIRD_ACCESS_KEY' set."
)

self.client = None
if HAS_MESSAGEBIRD:
self.client = messagebird.Client(access_key)

def send_messages(self, messages: List[Message]) -> int:
if not self.client:
return 0

msg_count: int = 0
for message in messages:
try:
self.client.message_create(
message.originator,
message.recipients,
message.body
)
except Exception as exc:
if not self.fail_silently:
raise exc
msg_count += 1
return msg_count
34 changes: 34 additions & 0 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from typing import List, Type, Optional
from io import StringIO

from unittest.mock import MagicMock

from django.dispatch import receiver # type: ignore
from django.test import SimpleTestCase, override_settings # type: ignore

Expand Down Expand Up @@ -179,6 +181,38 @@ def test_file_sessions(self) -> None:
self.assertEqual(message.recipients, ['+441134960000'])


class MessageBirdBackendTests(BaseSmsBackendTests, SimpleTestCase):
sms_backend = 'sms.backends.messagebird.SmsBackend'

def setUp(self) -> None:
super().setUp()
self._settings_override = override_settings(
MESSAGEBIRD_ACCESS_KEY='fake_access_key'
)
self._settings_override.enable()

def tearDown(self) -> None:
self._settings_override.disable()
super().tearDown()

def test_send_messages(self) -> None:
"""Test send_messages with the MessageBird backend."""
message = Message(
'Here is the message',
'+12065550100',
['+441134960000']
)

connection = sms.get_connection()
connection.client.message_create = MagicMock() # type: ignore
connection.send_messages([message]) # type: ignore
connection.client.message_create.assert_called_with( # type: ignore
'+12065550100',
['+441134960000'],
'Here is the message'
)


class SignalTests(SimpleTestCase):

def flush_mailbox(self) -> None:
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ commands =
setenv=
PYTHONWARNINGS=default
deps=
messagebird
coverage
mypy
django22: Django==2.2.*
Expand Down

0 comments on commit 41c4959

Please sign in to comment.