API Documentation

Sending Messages

edx_ace.ace.send(msg)

Send a message to a recipient.

Calling this method will result in an attempt being made to deliver the provided message to the recipient. Depending on the configured policies, it may be transmitted to them over one or more channels (email, sms, push etc).

The message must have valid values for all required fields in order for it to be sent. Different channels have different requirements, so care must be taken to ensure that all of the needed information is present in the message before calling ace.send().

Parameters:msg (Message) – The message to send.

Delivery

class edx_ace.channel.Channel

Bases: object

Channels deliver messages to users that have already passed through the presentation and policy steps.

Examples include email messages, push notifications, or in-browser messages. Implementations of this abstract class should not require any parameters be passed into their constructor since they are instantiated.

channel_type must be a ChannelType.

channel_type = None
deliver(message, rendered_message)

Transmit a rendered message to a recipient.

Parameters:
  • message (Message) – The message to transmit.
  • rendered_message (dict) – The rendered content of the message that has been personalized for this particular recipient.
classmethod enabled()

Validate settings to determine whether this channel can be enabled.

class edx_ace.channel.ChannelType

Bases: enum.Enum

All supported communication channels.

EMAIL = u'email'
PUSH = u'push'
edx_ace.channel.channels()

Gathers all available channels.

Note that this function loads all available channels from entry points. It expects the Django setting ACE_ENABLED_CHANNELS to be a list of plugin names that should be enabled. Only one plugin per channel type should appear in that list.

Raises:ValueError – If multiple plugins are enabled for the same channel type.
Returns:A mapping of channel types to instances of channel objects that can be used to deliver messages.
Return type:dict

edx_ace.channel.sailthru

class edx_ace.channel.sailthru.RecoverableErrorCodes

Bases: enum.Enum

These error codes are present in responses to requests that can (and should) be retried after waiting for a bit.

INTERNAL_ERROR = 9

Something’s gone wrong on Sailthru’s end. Your request was probably not saved - try waiting a moment and trying again.

RATE_LIMIT = 43

Too many [type] requests this minute to /[endpoint] API – You have exceeded the limit of requests per minute for the given type (GET or POST) and endpoint. For limit details, see the Rate Limiting section on the API Technical Details page.

class edx_ace.channel.sailthru.ResponseHeaders

Bases: enum.Enum

These are special headers returned in responses from the Sailthru REST API.

RATE_LIMIT_REMAINING = u'X-Rate-Limit-Remaining'
RATE_LIMIT_RESET = u'X-Rate-Limit-Reset'
class edx_ace.channel.sailthru.SailthruEmailChannel

Bases: edx_ace.channel.Channel

An email channel for delivering messages to users using Sailthru.

This channel makes use of the Sailthru REST API to send messages. It is designed for “at most once” delivery of messages. It will make a reasonable attempt to deliver the message and give up if it can’t. It also only confirms that Sailthru has received the request to send the email, it doesn’t actually confirm that it made it to the recipient.

The integration with Sailthru requires several Django settings to be defined.

Example

Sample settings:

.. settings_start
ACE_CHANNEL_SAILTHRU_DEBUG = False
ACE_CHANNEL_SAILTHRU_TEMPLATE_NAME = "Some template name"
ACE_CHANNEL_SAILTHRU_API_KEY = "1234567890"
ACE_CHANNEL_SAILTHRU_API_SECRET = "this is secret"
.. settings_end

The named template in Sailthru should be minimal, most of the rendering happens within ACE. The “From Name” field should be set to {{ace_template_from_name}}. The “Subject” field should be set to {{ace_template_subject}}. The “Code” for the template should be:

<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
    <head>
        {{ace_template_head_html}}
    </head>
    <body>
        {body_html = replace(ace_template_body_html, '{view_url}', view_url)}
        {body_html = replace(body_html, '{optout_confirm_url}', optout_confirm_url)}
        {body_html = replace(body_html, '{forward_url}', forward_url)}
        {body_html = replace(body_html, '{beacon_src}', beacon_src)}
        {body_html}
        <span id="sailthru-message-id" style="display: none;">{message_id()}</span>
        <a href="{optout_confirm_url}" style="display: none;"></a>
    </body>
</html>
channel_type = u'email'
deliver(message, rendered_message)
classmethod enabled()

Returns: True iff all required settings are not empty and the Sailthru client library is installed.

Exceptions

exception edx_ace.errors.ChannelError

Bases: exceptions.Exception

Indicates something went wrong in a delivery channel.

exception edx_ace.errors.FatalChannelDeliveryError

Bases: edx_ace.errors.ChannelError

A fatal error occurred during channel delivery. Do not retry.

exception edx_ace.errors.InvalidMessageError

Bases: exceptions.Exception

Encountered a message that cannot be sent due to missing or inconsistent information.

exception edx_ace.errors.RecoverableChannelDeliveryError(message, next_attempt_time)

Bases: edx_ace.errors.ChannelError

An error occurred during channel delivery that is non-fatal. The caller should re-attempt at a later time.

exception edx_ace.errors.UnsupportedChannelError

Bases: edx_ace.errors.ChannelError

Raised when an attempt is made to process a message for an unsupported channel.

Messages

class edx_ace.message.Message(app_label, name, recipient, expiration_time=None, context=NOTHING, send_uuid=None, language=None, log_level=None)

Bases: edx_ace.serialization.MessageAttributeSerializationMixin

app_label = Attribute(name='app_label', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
context = Attribute(name='context', default=Factory(factory=<function default_context_value>, takes_self=True), validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
default_context_value()
expiration_time = Attribute(name='expiration_time', default=None, validator=<optional validator for <instance_of validator for type <type 'datetime.datetime'>> or None>, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
generate_uuid()
get_message_specific_logger(logger)
language = Attribute(name='language', default=None, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
log_id
log_level = Attribute(name='log_level', default=None, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
name = Attribute(name='name', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
recipient = Attribute(name='recipient', default=NOTHING, validator=<optional validator for <instance_of validator for type <class 'edx_ace.recipient.Recipient'>> or None>, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
report(key, value)
report_basics()
send_uuid = Attribute(name='send_uuid', default=None, validator=<optional validator for <instance_of validator for type <class 'uuid.UUID'>> or None>, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
unique_name
uuid = Attribute(name='uuid', default=Factory(factory=<function generate_uuid>, takes_self=True), validator=<instance_of validator for type <class 'uuid.UUID'>>, repr=True, cmp=True, hash=None, init=False, convert=None, metadata=mappingproxy({}))
class edx_ace.message.MessageLoggingAdapter(logger, extra)

Bases: logging.LoggerAdapter

debug(msg, *args, **kwargs)
process(msg, kwargs)
class edx_ace.message.MessageType(context=NOTHING, expiration_time=None, app_label=NOTHING, name=NOTHING, log_level=None)

Bases: edx_ace.serialization.MessageAttributeSerializationMixin

APP_LABEL = None
NAME = None
app_label = Attribute(name='app_label', default=Factory(factory=<function default_app_label>, takes_self=True), validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
context = Attribute(name='context', default=Factory(factory=<function default_context_value>, takes_self=True), validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
default_app_label()
default_context_value()
default_name()
expiration_time = Attribute(name='expiration_time', default=None, validator=<optional validator for <instance_of validator for type <type 'datetime.datetime'>> or None>, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
generate_uuid()
log_level = Attribute(name='log_level', default=None, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
name = Attribute(name='name', default=Factory(factory=<function default_name>, takes_self=True), validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
personalize(recipient, language, user_context)
uuid = Attribute(name='uuid', default=Factory(factory=<function generate_uuid>, takes_self=True), validator=<instance_of validator for type <class 'uuid.UUID'>>, repr=True, cmp=True, hash=None, init=False, convert=None, metadata=mappingproxy({}))

Monitoring

edx_ace.monitoring.report(key, value)
edx_ace.monitoring.report_to_newrelic(key, value)

Delivery Policy

class edx_ace.policy.Policy

Bases: object

check(message)

Returns PolicyResult.

classmethod enabled()
class edx_ace.policy.PolicyResult(deny=NOTHING)

Bases: object

check_set_of_channel_types(attribute, set_value)
deny = Attribute(name='deny', default=Factory(factory=<type 'set'>, takes_self=False), validator=<function check_set_of_channel_types>, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
edx_ace.policy.channels_for(message)
edx_ace.policy.policies()

Message Presentation

class edx_ace.renderers.AbstractRenderer

Bases: object

Base class for message renderers.

A message renderer is responsible for taking one, or more, templates, and context, and outputting a rendered message for a specific message channel (e.g. email, SMS, push notification).

channel = None
get_template_for_message(message, filename)
render(message)

Renders the given message.

Parameters:message
rendered_message_cls = None
class edx_ace.renderers.EmailRenderer

Bases: edx_ace.renderers.AbstractRenderer

channel = u'email'
rendered_message_cls

alias of RenderedEmail

class edx_ace.renderers.RenderedEmail(from_name, subject, body_html, head_html, body)

Bases: object

body = Attribute(name='body', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
body_html = Attribute(name='body_html', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
from_name = Attribute(name='from_name', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
head_html = Attribute(name='head_html', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
subject = Attribute(name='subject', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
edx_ace.presentation.render(channel, message)

Returns the rendered content for the given channel and message.

Message Recipients

class edx_ace.recipient.Recipient(username, email_address=None)

Bases: edx_ace.serialization.MessageAttributeSerializationMixin

email_address = Attribute(name='email_address', default=None, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
username = Attribute(name='username', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}))
class edx_ace.recipient_resolver.RecipientResolver

Bases: object

send(msg_type, *args, **kwargs)

Serialization

class edx_ace.serialization.MessageAttributeSerializationMixin

Bases: object

classmethod from_string(string_value)
to_json()
class edx_ace.serialization.MessageEncoder(skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False, indent=None, separators=None, encoding='utf-8', default=None)

Bases: json.encoder.JSONEncoder

default(o)

Utils

edx_ace.utils.date

edx_ace.utils.date.deserialize(timestamp_iso8601_str)

Deserialize a datetime object from an ISO8601 formatted string.

Parameters:timestamp_iso8601_str (basestring) – A timestamp as an ISO8601 formatted string.
Returns:A timezone-aware python datetime object.
Return type:datetime
edx_ace.utils.date.get_current_time()

The current time in the UTC timezone as a timezone-aware datetime object.

edx_ace.utils.date.serialize(timestamp_obj)

Serialize a datetime object to an ISO8601 formatted string.

Parameters:timestamp_obj (datetime) – The timestamp to serialize.
Returns:A string representation of the timestamp in ISO8601 format.
Return type:basestring

edx_ace.utils.once

edx_ace.utils.once.once(fn)

Decorates a function that will be called exactly once.

After the function is called once, its result is stored in memory and immediately returned to subsequent callers instead of calling the decorated function again.

Examples

An incrementing value:

_counter = 0

@once
def get_counter():
    global _counter
    _counter += 1
    return _counter

def get_counter_updating():
    global _counter
    _counter += 1
    return _counter

print(get_counter())  # This will print "0"
print(get_counter_updating()) # This will print "1"
print(get_counter())  # This will also print "0"
print(get_counter_updating()) # This will print "2"

Lazy loading:

@once
def load_config():
    with open('config.json', 'r') as cfg_file:
        return json.load(cfg_file)

cfg = load_config()  # This will do the relatively expensive operation to
                     # read the file from disk.
cfg2 = load_config() # This call will not reload the file from disk, it
                     # will use the value returned by the first invocation
                     # of this function.
Parameters:fn (callable) – The function that should be called exactly once.
Returns:The wrapped function.
Return type:callable

edx_ace.utils.plugins

edx_ace.utils.plugins.check_plugin(extension, namespace, names=None)

Check the extension to see if it’s enabled.

Parameters:
  • extension (stevedore.extension.Extension) – The extension to check.
  • namespace (basestring) – The namespace that the extension was loaded from.
  • names (list) – A whitelist of extensions that should be checked.
Returns:

Whether or not this extension is enabled and should be used.

Return type:

bool

edx_ace.utils.plugins.get_manager(namespace, names=None)

Get the stevedore extension manager for this namespace.

Parameters:
  • namespace (basestring) – The entry point namespace to load plugins for.
  • names (list) – A list of names to load. If this is None then all extension will be loaded from this namespace.
Returns:

Extension manager with all extensions instantiated.

Return type:

stevedore.enabled.EnabledExtensionManager

edx_ace.utils.plugins.get_plugins(namespace, names=None)

Get all extensions for this namespace and list of names.

Parameters:
  • namespace (basestring) – The entry point namespace to load plugins for.
  • names (list) – A list of names to load. If this is None then all extension will be loaded from this namespace.
Returns:

A list of extensions.

Return type:

list

Testing

edx_ace.test_utils

Test utilities.

Since py.test discourages putting __init__.py into test directory (i.e. making tests a package) one cannot import from anywhere under tests folder. However, some utility classes/methods might be useful in multiple test modules (i.e. factoryboy factories, base test classes). So this package is the place to put them.

class edx_ace.test_utils.StubPolicy(deny_value)

Bases: edx_ace.policy.Policy

check(message)
edx_ace.test_utils.patch_channels(test_case, channels)
edx_ace.test_utils.patch_policies(test_case, policies)

Internal

Delivery

Functions for delivering ACE messages.

This is an internal interface used by ace.send().

edx_ace.delivery.deliver(channel_type, rendered_message, message)

Deliver a message via a particular channel.

Parameters:
  • channel_type (ChannelType) – The channel type to deliver the channel over.
  • rendered_message (object) – Each attribute of this object contains rendered content.
  • message (Message) – The message that is being sent.
Raises:

UnsupportedChannelError – If no channel of the requested channel type is available.