Source code for pyramid_sms.outgoing

"""Outgoing SMS API."""

import logging
import pkg_resources

from pyramid.renderers import render
from pyramid.settings import asbool
from pyramid_sms.utils import get_sms_backend

try:
    pkg_resources.get_distribution('websauna')
    from websauna.system.http import Request
    from websauna.system.task.tasks import task
    from websauna.system.task.tasks import ScheduleOnCommitTask
    HAS_WEBSAUNA = False
except pkg_resources.DistributionNotFound:
    from pyramid.request import Request
    HAS_WEBSAUNA = False


from .interfaces import SMSConfigurationError
from .events import SMSSent

logger = logging.getLogger(__name__)


def _send_sms(request, receiver, text_body, sender, log_failure):
    """Perform actual SMS outbound operation through a configured service."""
    service = get_sms_backend(request)
    service.send_sms(receiver, text_body, sender, log_failure)


if HAS_WEBSAUNA:
    # TODO: Factor this to a separate configurable module
    @task(base=ScheduleOnCommitTask, bind=True)
    def _send_sms_async(self, receiver, from_, text_body, log_failure):
        """Celery task to send the SMS synchronously outside HTTP request proccesing."""
        request = self.request.request
        _send_sms(request, receiver, from_, text_body, log_failure)


[docs]def send_sms(request: Request, receiver: str, text_body: str, sender: str=None, log_failure: bool=True, async: bool=None, user_dialog: bool=False): """Send outgoing SMS message using the default configured SMS service. Example: .. code-block:: python def test_sms_view(request): '''Dummy view to simulate outgoing SMS.''' send_sms(request, "+15551231234", "Test message") :param receiver: Receiver's phone number as international format. You should normalize this number from all user input before passing in. See :py:mod:`pyramid_sms.utils` for examples. :param text_body: Outbound SMS body. Usually up to 1600 characters. :param sender: Envelope from number. Needs to be configured in the service. If none use default configured "sms.default_from". :param log_failure: If there is an exception from the SMS backend then log this using Python logging system. Otherwise raise the error as an exception. :param async: Force asynchronous operation through task subsystem. If ``None`` respect ``sms.async`` settings. If the operation is asynchronous, this function returns instantly and does not block HTTP request due to slow API calls to a third party service. :param user_dialog: This SMS is part of a dialog with a known user. Use this flag to log messages with the user in your conversation dashboard. Set ``False`` to two-factor auth tokens and such. :raise SMSConfigurationError: If configuration settings are missing """ if async is None: async = request.registry.settings.get("sms.async") if async is None: raise SMSConfigurationError("sms.async setting not defined") async = asbool(async) if sender is None: sender = request.registry.settings.get("sms.default_sender") if not sender: raise SMSConfigurationError("sms.default_sender not configured") # https://www.twilio.com/help/faq/sms/does-twilio-support-concatenated-sms-messages-or-messages-over-160-characters if len(text_body) >= 1600: logger.warn("Too long SMS: %s", text_body) logger.info("Queuing sending SMS to: %s, body: %s", receiver, text_body) # Put the actual Twilio operation async queue if async: if not HAS_WEBSAUNA: raise SMSConfigurationError("Async operations are only supported with Websauna framework") _send_sms_async.apply_async(args=(receiver, text_body, sender, log_failure,)) else: _send_sms(request, receiver, text_body, sender, log_failure) request.registry.notify(SMSSent(request, receiver, text_body, sender, user_dialog))
[docs]def send_templated_sms(request: Request, template: str, context: dict, receiver: str, sender: str=None, log_failure: bool=True, async: bool=None, user_dialog: bool=False): """Send out a SMS that is constructed using a page template. Same as :py:meth:`pyramid_sms.outgoing.send_sms`, but uses templates instead of hardcoded messages. :param request: HTTP request :param template: Template name. Like ``welcome_sms.txt.jinja``. :param context: Dictionary passed to template rendering engine """ text_body = render(template, context, request=request) send_sms(request, receiver, text_body, sender, log_failure, async, user_dialog)