# -*- coding: utf-8 -*-
# pylint: disable=too-few-public-methods
# pylint: disable=too-many-instance-attributes
"""The classes in this module represent objects sent and received from
the XMS REST API.
"""
from __future__ import absolute_import, division, print_function
[docs]class ReportType(object):
"""A collection of known delivery report types.
These values are known to be valid in
:py:attr:`MtSmsBatch.delivery_report`.
"""
NONE = 'none'
SUMMARY = 'summary'
FULL = 'full'
PER_RECIPIENT = 'per_recipient'
[docs]class DeliveryStatus(object):
"""A collection of known delivery statuses.
Note, new statuses may be introduced to the XMS API.
"""
QUEUED = "Queued"
"""Message is queued within REST API system and will be dispatched
according to the rate of the account."""
DISPATCHED = "Dispatched"
"""Message has been dispatched and accepted for delivery by the SMSC."""
ABORTED = "Aborted"
"""Message was aborted before reaching SMSC."""
REJECTED = "Rejected"
"""Message was rejected by SMSC."""
DELIVERED = "Delivered"
"""Message has been delivered."""
FAILED = "Failed"
"""Message failed to be delivered."""
EXPIRED = "Expired"
"""Message expired before delivery."""
UNKNOWN = "Unknown"
"""It is not known if message was delivered or not."""
[docs]class DeliveryReportType(object):
"""The types of delivery reports that can be retrieved."""
SUMMARY = "summary"
"""Indicates a summary batch delivery report.
The summary delivery report does not include the per-recipient
result but rather aggregated statistics about the deliveries.
"""
FULL = "full"
"""Indicates a full batch delivery report.
This includes per-recipient delivery results. For batches with
many destinations such reports may be very large.
"""
[docs]class Reset(object):
"""A class whose instances indicate that a value should be reset.
This is used when updating previously created XMS objects. Note,
it is typically not necessary to created new objects of this type,
instead use the constant :const:`.RESET`.
"""
def __init__(self):
pass
RESET = Reset()
"""Object used to indicate that a XMS field should be reset to its
default value."""
[docs]class MtBatchSms(object):
"""Base class for all SMS batch classes.
Holds fields that are common to both the create and response
classes.
.. attribute:: recipients
One or more MSISDNs indicating the batch recipients.
:type: set[str]
.. attribute:: sender
The batch sender, typically a short code or long number.
:type: str
.. attribute:: delivery_report
The type of delivery report to use for this batch.
:type: str
.. attribute:: send_at
The time at which this batch should be sent.
:type: datetime
.. attribute:: expire_at
The time at which this batch should expire.
:type: datetime
.. attribute:: callback_url
The URL to which callbacks should be sent.
:type: str
"""
def __init__(self):
self.recipients = set()
self.sender = None
self.delivery_report = None
self.send_at = None
self.expire_at = None
self.callback_url = None
[docs]class MtBatchSmsCreate(MtBatchSms):
"""Describes parameters available during batch creation.
We can create two kinds of batches, textual and binary, described
in the child classes :py:class:`MtBatchTextSmsCreate` and
:py:class:`MtBatchTextSmsCreate`, respectively.
.. attribute:: tags
The initial set of tags to give the batch.
:type: set[str]
"""
def __init__(self):
MtBatchSms.__init__(self)
self.tags = set()
[docs]class MtBatchTextSmsCreate(MtBatchSmsCreate):
"""Class whose fields describe a text batch.
.. attribute:: body
The message body or template.
:type: str
.. attribute:: parameters
The template parameters.
This property is only relevant is the :py:attr:`body` property
is a template. This is expected to be an associative array
mapping parameter keys to associative arrays themselves mapping
recipient numbers to substitution strings.
More concretely we may have for the parameterized message
"Hello, ${name}!" have::
batch.parameters = {
'name': {
'123456789': 'Mary',
'987654321': 'Joe',
'default': 'valued customer'
}
}
And the recipient with MSISDN "123456789" would then receive the
message "Hello, Mary!".
Note the use of "default" to indicate the substitution for
recipients not explicitly given. For example, the recipient
"555555555" would receive the message "Hello, valued customer!".
:type: dict[str, dict[str, str]]
"""
def __init__(self):
MtBatchSmsCreate.__init__(self)
self.body = None
self.parameters = {}
[docs]class MtBatchBinarySmsCreate(MtBatchSmsCreate):
"""Describes a binary batch.
This class holds all parameters that can be used when creating a
binary SMS batch.
.. attribute:: body
The body of this binary message.
:type: bytes
.. attribute:: udh
The User Data Header of this binary message.
:type: bytes
"""
def __init__(self):
MtBatchSmsCreate.__init__(self)
self.body = None
self.udh = None
[docs]class MtBatchSmsUpdate(object):
"""Describes updates that can be performed on text and binary SMS
batches.
.. attribute:: recipient_insertions
The message destinations to add to the batch. This should have
zero or more MSISDNs.
:type: set[str]
.. attribute:: recipient_removals
The message destinations to remove from the batch. This should
have zero or more MSISDNs.
:type: set[str]
.. attribute:: sender
The message originator as a long number or short code. If
``None`` then the current value is kept, if :const:`.RESET` then
the value is reset to its XMS default, and if set to a string
the sender is updated.
:type: str or None or Reset
.. attribute:: delivery_report
Description of how to update the batch delivery report value. If
``None`` then the current value is kept, if :const:`.RESET` then
the value is reset to its XMS default, and if set to a string
the delivery report value is updated.
See :class:`ReportType` for valid report types.
:type: str or None or Reset
.. attribute:: send_at
Description of how to update the batch send at value. If
``None`` then the current value is kept, if :const:`.RESET` then
the value is reset to its XMS default, and if set to a date time
the send at value is updated.
:type: datetime or None or Reset
.. attribute:: expire_at
Description of how to update the batch expire at value. If
``None`` then the current value is kept, if :const:`.RESET` then
the value is reset to its XMS default, and if set to a date time
the expire at value is updated.
:type: datetime or None or Reset
.. attribute:: callback_url
Description of how to update the batch callback URL. If ``None``
then the current value is kept, if :const:`.RESET` then the
value is reset to its XMS default, and if set to a string the
callback URL value is updated.
:type: str or None or Reset
"""
def __init__(self):
self.recipient_insertions = set()
self.recipient_removals = set()
self.sender = None
self.delivery_report = None
self.send_at = None
self.expire_at = None
self.callback_url = None
[docs]class MtBatchTextSmsUpdate(MtBatchSmsUpdate):
"""Class that the update operations that can be performed on a text
batch.
.. attribute:: body
The updated batch message body. If ``None`` then the current
batch message is kept.
:type: str or None
.. attribute:: parameters
Description of how to update the batch parameters. If ``None``
then the current value is kept, if :const:`.RESET` then the
value is reset to its XMS default, and if set to a dictionary
the parameters value is updated.
:type: dict or None or Reset
"""
def __init__(self):
MtBatchSmsUpdate.__init__(self)
self.body = None
self.parameters = None
[docs]class MtBatchBinarySmsUpdate(MtBatchSmsUpdate):
"""Describes updates to a binary SMS batch.
.. attribute:: body
The updated binary batch body. If ``None`` then the existing
body is left as-is.
:type: bytes or None
.. attribute:: udh
The updated binary User Data Header. If ``None`` then the
existing UDH is left as-is.
:type: bytes or None
"""
def __init__(self):
MtBatchSmsUpdate.__init__(self)
self.body = None
self.udh = None
[docs]class MtBatchSmsResult(MtBatchSms):
"""Contains the common fields of text and binary batches.
.. attribute:: batch_id
The unique batch identifier.
:type: str
.. attribute:: created_at
Time when this batch was created.
:type: datetime
.. attribute:: modified_at
Time when this batch was last modified.
:type: datetime
.. attribute:: canceled
Whether this batch has been canceled.
:type: bool
"""
def __init__(self):
MtBatchSms.__init__(self)
self.batch_id = None
self.created_at = None
self.modified_at = None
self.canceled = None
[docs]class MtBatchTextSmsResult(MtBatchSmsResult):
"""A textual batch as returned by the XMS endpoint.
This differs from the batch creation definition by the addition
of, for example, the batch identifier and the creation time.
.. attribute:: body
The message body or template. See
:py:attr:`MtBatchTextSmsCreate.parameters`.
:type: str
.. attribute:: parameters
The template parameters.
type *dict[str, dict[str, str]]*
"""
def __init__(self):
MtBatchSmsResult.__init__(self)
self.body = None
self.parameters = None
[docs]class MtBatchBinarySmsResult(MtBatchSmsResult):
"""A binary SMS batch as returned by XMS.
.. attribute:: body
The body of this binary message.
:type: bytes
.. attribute:: udh
The User Data Header of this binary message.
:type: bytes
"""
def __init__(self):
MtBatchSmsResult.__init__(self)
self.body = None
self.udh = None
[docs]class BatchDeliveryReport(object):
"""Batch delivery report.
A batch delivery report is divided into a number of *buckets* and
each such bucket contain statistics about batch messages having a
specific delivery status. The :py:attr:`statuses` property
contains the various buckets.
.. attribute:: batch_id
Identifier of the batch that this report covers.
:type: str
.. attribute:: total_message_count
The total number of messages sent as part of this batch.
:type: int
.. attribute:: statuses
The batch status buckets. This array describes the aggregated
status for the batch where each array element contains
information about messages having a certain delivery status and
delivery code.
:type: list[BatchDeliveryReportStatus]
"""
def __init__(self):
self.batch_id = None
self.total_message_count = None
self.statuses = []
[docs]class BatchDeliveryReportStatus(object):
"""Aggregated statistics for a given batch.
This represents the delivery statistics for a given statistics
*bucket*. See :py:class:`BatchDeliveryReport`.
.. attribute:: code
The delivery status code for this recipient bucket.
:type: int
.. attribute:: status
The delivery status for this recipient bucket.
:type: str
.. attribute:: count
The number of recipients belonging to this bucket.
:type: int
.. attribute:: recipients
The recipients having this status.
Note, this is non-empty only if a `full` delivery report has been
requested.
:type: set[str]
"""
def __init__(self):
self.code = None
self.status = None
self.count = None
self.recipients = set()
[docs]class BatchRecipientDeliveryReport(object):
"""A delivery report for an individual batch recipient.
.. attribute:: batch_id
The batch identifier.
:type: string
.. attribute:: recipient
The recipient address.
:type: string
.. attribute:: code
The delivery code.
:type: int
.. attribute:: status
The delivery status.
:type: int
.. attribute:: status_message
The delivery status message. The status message is not always
available and the attribute is set to *None* in those cases.
:type: string or None
.. attribute:: operator
The recipient's mobile operator. If the operator is not known,
then this is set to *None*.
:type: string or None
.. attribute:: status_at
The time at delivery.
:type: datetime
.. attribute:: operator_status_at
The time of delivery as reported by operator.
:type: datetime or None
"""
def __init__(self):
self.batch_id = None
self.recipient = None
self.code = None
self.status = None
self.status_message = None
self.operator = None
self.status_at = None
self.operator_status_at = None
[docs]class Error(object):
"""Describes error responses given by XMS.
:param str code: the error code
:param str text: the human readable error text
.. attribute:: code
A code that can be used to programmatically recognize the error.
:type: str
.. attribute:: text
Human readable description of the error.
:type: str
"""
def __init__(self, code, text):
self.code = code
self.text = text
[docs]class MtBatchDryRunResult(object):
"""A batch dry run report.
.. attribute:: number_of_recipients
The number of recipients that would receive the batch message.
:type: int
.. attribute:: number_of_message
The number of messages that will be sent.
:type: int
.. attribute:: per_recipient
The per-recipient dry-run result.
:type: list[DryRunPerRecipient]
"""
def __init__(self):
self.number_of_recipients = None
self.number_of_messages = None
self.per_recipient = []
[docs]class DryRunPerRecipient(object):
"""Per-recipient dry-run result.
Object of this class only occur within dry-run results. See
:class:`MtBatchDryRunResult`.
.. attribute:: recipient
The recipient.
:type: str
.. attribute:: number_of_parts
Number of message parts needed for the recipient.
:type: int
.. attribute:: body
Message body sent to this recipient.
:type: str
.. attribute:: encoding
Indicates the text encoding used for this recipient.
This is one of "text" or "unicode". See :const:`ENCODING_TEXT`
and :const:`ENCODING_UNICODE`.
:type: str
"""
ENCODING_TEXT = "text"
"""Constant indicating non-unicode encoding."""
ENCODING_UNICODE = "unicode"
"""Constant indicating unicode encoding."""
def __init__(self):
self.recipient = None
self.number_of_parts = None
self.body = None
self.encoding = None
[docs]class GroupAutoUpdate(object):
"""A description of automatic group updates.
An automatic update is triggered by a mobile originated message to
a given number containing special keywords.
When the given recipient receives a mobile originated SMS
containing keywords (first and/or second) matching the given
``add`` arguments then the sender MSISDN is added to the group.
Similarly, if the MO is matching the given ``remove`` keyword
arguments then the MSISDN is removed from the group.
For example::
GroupAutoUpdate(
recipient='12345',
add_first_word='add',
remove_first_word='remove')
would trigger based solely on the first keyword of the MO message.
On the other hand::
GroupAutoUpdate(
recipient='12345',
add_first_word='alert',
add_second_word='add',
remove_first_word='alert',
remove_second_word='remove')
would trigger only when both the first and second keyword are
given in the MO message.
:param str recipient: recipient that triggers this rule
:param add_first_word: first ``add`` keyword, default is `None`.
:type add_first_word: str or None
:param add_second_word: second ``add`` keyword, default is `None`.
:type add_second_word: str or None
:param remove_first_word: first ``remove`` keyword, default is `None`.
:type remove_first_word: str or None
:param remove_second_word: second ``remove`` keywords, default is `None`.
:type remove_second_word: str or None
.. attribute:: recipient
The recipient of the mobile originated message. A short code or
long number.
:type: str
.. attribute:: add_word_pair
A two-element tuple holding the first and second keyword that
causes the MO sender to be added to the group.
:type: tuple[str or None, str or None]
.. attribute:: remove_word_pair
A two-element tuple holding the first and second keyword that
causes the MO sender to be removed from the group.
:type: tuple[str or None, str or None]
"""
def __init__(self,
recipient,
add_first_word=None,
add_second_word=None,
remove_first_word=None,
remove_second_word=None):
self.recipient = recipient
self.add_word_pair = (add_first_word, add_second_word)
self.remove_word_pair = (remove_first_word, remove_second_word)
[docs]class GroupCreate(object):
"""A description of the fields necessary to create a group.
.. attribute:: name
The group name.
:type: str
.. attribute:: members
A set of MSISDNs that belong to this group.
:type: set[str]
.. attribute:: child_groups
A set of groups that in turn belong to this group.
:type: set[str]
.. attribute:: auto_update
Describes how this group should be auto updated.
If no auto updating should be performed for the group then this
value is ``None``.
:type: GroupAutoUpdate or None
.. attribute:: tags
The tags associated to this group.
:type: set[str]
"""
def __init__(self):
self.name = None
self.members = set()
self.child_groups = set()
self.auto_update = None
self.tags = set()
[docs]class GroupResult(object):
"""This class holds the result of a group fetch operation.
This may be used either standalone or as an element of a paged
result.
.. attribute:: group_id
The unique group identifier.
:type: str
.. attribute:: name
The group name.
:type: str
.. attribute:: size
The number of members of this group.
:type: int
.. attribute:: child_groups
A set of groups that in turn belong to this group.
:type: set[str]
.. attribute:: auto_update
Describes how this group should be auto updated.
If no auto updating should be performed for the group then this
value is ``None``.
:type: GroupAutoUpdate or None
.. attribute:: created_at
The time at which this group was created.
:type: datetime
.. attribute:: modified_at
The time when this group was last modified.
:type: datetime
"""
def __init__(self):
self.group_id = None
self.name = None
self.size = None
self.child_groups = set()
self.auto_update = None
self.created_at = None
self.modified_at = None
[docs]class GroupUpdate(object):
"""Describes updates that can be performed on a group.
.. attribute:: name
Updates the group name.
If ``None`` then the current value is kept, if :const:`.RESET`
then the value is reset to its XMS default, and if set to a
string the name is updated.
:type: None or str or Reset
.. attribute:: member_insertions
The MSISDNs that should be added to this group.
:type: set[str]
.. attribute:: member_removals
The MSISDNs that should be removed from this group.
:type: set[str]
.. attribute:: child_group_insertions
The child groups that should be added to this group.
:type: set[str]
.. attribute:: child_group_removals
The child groups that should be removed from this group.
:type: set[str]
.. attribute:: add_from_group
Identifier of a group whose members should be added to this
group.
:type: str
.. attribute:: remove_from_group
Identifier of a group whose members should be removed from this
group.
:type: str
.. attribute:: auto_update
Describes how this group should be auto updated.
If ``None`` then the current value is kept, if :const:`.RESET`
then the value is reset to its XMS default, and if set to a
``GroupAutoUpdate`` object the value is updated.
:type: None or GroupAutoUpdate or Reset
"""
def __init__(self):
self.name = None
self.member_insertions = set()
self.member_removals = set()
self.child_group_insertions = set()
self.child_group_removals = set()
self.add_from_group = None
self.remove_from_group = None
self.auto_update = None
[docs]class MoSms(object):
"""Base class for SMS mobile originated messages.
Holds fields that are common to both the textual and binary MO
classes.
.. attribute:: message_id
The message identifier.
:type: str
.. attribute:: recipient
The message recipient. This is a short code or long number.
:type: str
.. attribute:: sender
The message sender. This is an MSISDN.
:type: str
.. attribute:: operator
The MCCMNC of the originating operator, if available.
:type: str or None
.. attribute:: sent_at
The time when this message was sent, if available.
:type: datetime or None
.. attribute:: received_at
The time when the messaging system received this message.
:type: datetime
"""
def __init__(self):
self.message_id = None
self.recipient = None
self.sender = None
self.operator = None
self.sent_at = None
self.received_at = None
[docs]class MoTextSms(MoSms):
"""An SMS mobile originated message with textual content.
.. attribute:: body
The message body.
:type: str
.. attribute:: keyword
The message keyword, if available.
:type: str or None
"""
def __init__(self):
MoSms.__init__(self)
self.body = None
self.keyword = None
[docs]class MoBinarySms(MoSms):
"""An SMS mobile originated message with binary content.
.. attribute:: body
The binary message body.
:type: bytes
.. attribute:: udh
The user data header.
:type: bytes
"""
def __init__(self):
MoSms.__init__(self)
self.body = None
self.udh = None
[docs]class Page(object):
"""A page of elements.
The element type depends on the type of page that has been
retrieved. Typically it is one of :class:`MtSmsBatchResponse` or
:class:`GroupResponse`.
.. attribute:: page
The page number, starting from zero.
:type: int
.. attribute:: page
The number of elements on this page.
:type: int
.. attribute:: total_size
The total number of elements across all fetched pages.
:type: int
.. attribute:: content
The page elements.
:type: list[obj]
"""
def __init__(self):
self.page = None
self.size = None
self.total_size = None
self.content = None
def __iter__(self):
"""Returns an iterator over the content of this page.
For example, if the page is the result of a batch listing then
this iterator will yield batch results.
:returns: the page iterator
:rtype: iterator
"""
return iter(self.content)
[docs]class Pages(object):
"""A paged result.
It is possible to, for example, fetch individual pages or iterate
over all pages.
:param worker: worker function that fetches pages
"""
def __init__(self, worker):
self._worker = worker
[docs] def get(self, page):
"""Downloads a specific page.
:param int page: number of the page to fetch
:return: a page
:rtype: Page
"""
return self._worker(page)
def __iter__(self):
"""Iterator across all pages."""
return PagesIterator(self)
[docs]class PagesIterator(object):
"""An iterator over a paged result.
The key is the page number and the value corresponds to the
content of the pages.
:param Pages pages: the pages that we are iterating over
"""
def __init__(self, pages):
self._pages = pages
self._cur_page = None
self._position = 0
[docs] def next(self):
return self.__next__()
def __next__(self):
"""Steps this iterator to the next page."""
if not self._cur_page or self._cur_page.page != self._position:
self._cur_page = self._pages.get(self._position)
self._position += 1
# If we fetched an empty page then the iteration is over.
if self._cur_page.size <= 0:
raise StopIteration
else:
return self._cur_page