[Python-modules-commits] [django-anymail] 01/04: Import django-anymail_0.7.orig.tar.gz

Scott Kitterman kitterman at moszumanska.debian.org
Mon Jan 2 13:24:15 UTC 2017


This is an automated email from the git hooks/post-receive script.

kitterman pushed a commit to branch master
in repository django-anymail.

commit 107063a19afd93a49dd2f8de54f47b72261201b6
Author: Scott Kitterman <scott at kitterman.com>
Date:   Mon Jan 2 08:14:47 2017 -0500

    Import django-anymail_0.7.orig.tar.gz
---
 PKG-INFO                         | 12 +++---
 anymail/_version.py              |  2 +-
 anymail/backends/base.py         | 49 +++++++++++++++-------
 anymail/exceptions.py            |  4 ++
 anymail/utils.py                 | 87 +++++++++++++++++++++++++++++++---------
 anymail/webhooks/mandrill.py     | 12 +++++-
 anymail/webhooks/postmark.py     |  4 +-
 django_anymail.egg-info/PKG-INFO | 12 +++---
 8 files changed, 131 insertions(+), 51 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index 4014186..8397941 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: django-anymail
-Version: 0.6.1
+Version: 0.7
 Summary: Django email backends for Mailgun, Postmark, SendGrid and other transactional ESPs
 Home-page: https://github.com/anymail/django-anymail
 Author: Mike Edmunds <medmunds at gmail.com>
@@ -59,17 +59,17 @@ Description: Anymail: Django email backends for Mailgun, Postmark, SendGrid, Spa
         
         .. END shared-intro
         
-        .. image:: https://travis-ci.org/anymail/django-anymail.svg?branch=v0.6
+        .. image:: https://travis-ci.org/anymail/django-anymail.svg?branch=v0.7
                :target: https://travis-ci.org/anymail/django-anymail
                :alt:    build status on Travis-CI
         
-        .. image:: https://readthedocs.org/projects/anymail/badge/?version=v0.6
-               :target: https://anymail.readthedocs.io/en/v0.6/
+        .. image:: https://readthedocs.org/projects/anymail/badge/?version=v0.7
+               :target: https://anymail.readthedocs.io/en/v0.7/
                :alt:    documentation on ReadTheDocs
         
         **Resources**
         
-        * Full documentation: https://anymail.readthedocs.io/en/v0.6/
+        * Full documentation: https://anymail.readthedocs.io/en/v0.7/
         * Package on PyPI: https://pypi.python.org/pypi/django-anymail
         * Project on Github: https://github.com/anymail/django-anymail
         
@@ -157,7 +157,7 @@ Description: Anymail: Django email backends for Mailgun, Postmark, SendGrid, Spa
         .. END quickstart
         
         
-        See the `full documentation <https://anymail.readthedocs.io/en/v0.6/>`_
+        See the `full documentation <https://anymail.readthedocs.io/en/v0.7/>`_
         for more features and options.
         
 Keywords: django,email,email backend,ESP,transactional mail,mailgun,mandrill,postmark,sendgrid
diff --git a/anymail/_version.py b/anymail/_version.py
index 1abae49..783f1a5 100644
--- a/anymail/_version.py
+++ b/anymail/_version.py
@@ -1,3 +1,3 @@
-VERSION = (0, 6, 1)
+VERSION = (0, 7)
 __version__ = '.'.join([str(x) for x in VERSION])  # major.minor.patch or major.minor.devN
 __minor_version__ = '.'.join([str(x) for x in VERSION[:2]])  # Sphinx's X.Y "version"
diff --git a/anymail/backends/base.py b/anymail/backends/base.py
index 877f6db..bf13c80 100644
--- a/anymail/backends/base.py
+++ b/anymail/backends/base.py
@@ -7,7 +7,8 @@ from django.utils.timezone import is_naive, get_current_timezone, make_aware, ut
 from ..exceptions import AnymailCancelSend, AnymailError, AnymailUnsupportedFeature, AnymailRecipientsRefused
 from ..message import AnymailStatus
 from ..signals import pre_send, post_send
-from ..utils import Attachment, ParsedEmail, UNSET, combine, last, get_anymail_setting
+from ..utils import (Attachment, ParsedEmail, UNSET, combine, last, get_anymail_setting,
+                     force_non_lazy, force_non_lazy_list, force_non_lazy_dict)
 
 
 class AnymailBaseBackend(BaseEmailBackend):
@@ -195,31 +196,43 @@ class AnymailBaseBackend(BaseEmailBackend):
 
 
 class BasePayload(object):
-    # attr, combiner, converter
+    # Listing of EmailMessage/EmailMultiAlternatives attributes
+    # to process into Payload. Each item is in the form:
+    #   (attr, combiner, converter)
+    #   attr: the property name
+    #   combiner: optional function(default_value, value) -> value
+    #     to combine settings defaults with the EmailMessage property value
+    #     (usually `combine` to merge, or `last` for message value to override default;
+    #     use `None` if settings defaults aren't supported)
+    #   converter: optional function(value) -> value transformation
+    #     (can be a callable or the string name of a Payload method, or `None`)
+    #     The converter must force any Django lazy translation strings to text.
+    # The Payload's `set_<attr>` method will be called with
+    # the combined/converted results for each attr.
     base_message_attrs = (
         # Standard EmailMessage/EmailMultiAlternatives props
         ('from_email', last, 'parsed_email'),
         ('to', combine, 'parsed_emails'),
         ('cc', combine, 'parsed_emails'),
         ('bcc', combine, 'parsed_emails'),
-        ('subject', last, None),
+        ('subject', last, force_non_lazy),
         ('reply_to', combine, 'parsed_emails'),
-        ('extra_headers', combine, None),
-        ('body', last, None),  # special handling below checks message.content_subtype
-        ('alternatives', combine, None),
+        ('extra_headers', combine, force_non_lazy_dict),
+        ('body', last, force_non_lazy),  # special handling below checks message.content_subtype
+        ('alternatives', combine, 'prepped_alternatives'),
         ('attachments', combine, 'prepped_attachments'),
     )
     anymail_message_attrs = (
         # Anymail expando-props
-        ('metadata', combine, None),
+        ('metadata', combine, force_non_lazy_dict),
         ('send_at', last, 'aware_datetime'),
-        ('tags', combine, None),
+        ('tags', combine, force_non_lazy_list),
         ('track_clicks', last, None),
         ('track_opens', last, None),
-        ('template_id', last, None),
-        ('merge_data', combine, None),
-        ('merge_global_data', combine, None),
-        ('esp_extra', combine, None),
+        ('template_id', last, force_non_lazy),
+        ('merge_data', combine, force_non_lazy_dict),
+        ('merge_global_data', combine, force_non_lazy_dict),
+        ('esp_extra', combine, force_non_lazy_dict),
     )
     esp_message_attrs = ()  # subclasses can override
 
@@ -261,15 +274,21 @@ class BasePayload(object):
     #
 
     def parsed_email(self, address):
-        return ParsedEmail(address, self.message.encoding)
+        return ParsedEmail(address, self.message.encoding)  # (handles lazy address)
 
     def parsed_emails(self, addresses):
         encoding = self.message.encoding
-        return [ParsedEmail(address, encoding) for address in addresses]
+        return [ParsedEmail(address, encoding)  # (handles lazy address)
+                for address in addresses]
+
+    def prepped_alternatives(self, alternatives):
+        return [(force_non_lazy(content), mimetype)
+                for (content, mimetype) in alternatives]
 
     def prepped_attachments(self, attachments):
         str_encoding = self.message.encoding or settings.DEFAULT_CHARSET
-        return [Attachment(attachment, str_encoding) for attachment in attachments]
+        return [Attachment(attachment, str_encoding)  # (handles lazy content, filename)
+                for attachment in attachments]
 
     def aware_datetime(self, value):
         """Converts a date or datetime or timestamp to an aware datetime.
diff --git a/anymail/exceptions.py b/anymail/exceptions.py
index 8e55532..c0af427 100644
--- a/anymail/exceptions.py
+++ b/anymail/exceptions.py
@@ -101,6 +101,10 @@ class AnymailRecipientsRefused(AnymailError):
         super(AnymailRecipientsRefused, self).__init__(message, *args, **kwargs)
 
 
+class AnymailInvalidAddress(AnymailError, ValueError):
+    """Exception when using an invalidly-formatted email address"""
+
+
 class AnymailUnsupportedFeature(AnymailError, ValueError):
     """Exception for Anymail features that the ESP doesn't support.
 
diff --git a/anymail/utils.py b/anymail/utils.py
index 5e84ba2..a4a4483 100644
--- a/anymail/utils.py
+++ b/anymail/utils.py
@@ -2,15 +2,17 @@ import mimetypes
 from base64 import b64encode
 from datetime import datetime
 from email.mime.base import MIMEBase
-from email.utils import formatdate, parseaddr, unquote
+from email.utils import formatdate, getaddresses, unquote
 from time import mktime
 
 import six
 from django.conf import settings
 from django.core.mail.message import sanitize_address, DEFAULT_ATTACHMENT_MIME_TYPE
+from django.utils.encoding import force_text
+from django.utils.functional import Promise
 from django.utils.timezone import utc
 
-from .exceptions import AnymailConfigurationError
+from .exceptions import AnymailConfigurationError, AnymailInvalidAddress
 
 UNSET = object()  # Used as non-None default value
 
@@ -93,31 +95,39 @@ def getfirst(dct, keys, default=UNSET):
         return default
 
 
+def parse_one_addr(address):
+    # This is email.utils.parseaddr, but without silently returning
+    # partial content if there are commas or parens in the string:
+    addresses = getaddresses([address])
+    if len(addresses) > 1:
+        raise ValueError("Multiple email addresses (parses as %r)" % addresses)
+    elif len(addresses) == 0:
+        return ('', '')
+    return addresses[0]
+
+
 class ParsedEmail(object):
-    """A sanitized, full email address with separate name and email properties"""
+    """A sanitized, full email address with separate name and email properties."""
 
     def __init__(self, address, encoding):
-        self.address = sanitize_address(address, encoding)
-        self._name = None
-        self._email = None
-
-    def _parse(self):
-        if self._email is None:
-            self._name, self._email = parseaddr(self.address)
+        if address is None:
+            self.name = self.email = self.address = None
+            return
+        try:
+            self.name, self.email = parse_one_addr(force_text(address))
+            if self.email == '':
+                # normalize sanitize_address py2/3 behavior:
+                raise ValueError('No email found')
+            # Django's sanitize_address is like email.utils.formataddr, but also
+            # escapes as needed for use in email message headers:
+            self.address = sanitize_address((self.name, self.email), encoding)
+        except (IndexError, TypeError, ValueError) as err:
+            raise AnymailInvalidAddress("Invalid email address format %r: %s"
+                                        % (address, str(err)))
 
     def __str__(self):
         return self.address
 
-    @property
-    def name(self):
-        self._parse()
-        return self._name
-
-    @property
-    def email(self):
-        self._parse()
-        return self._email
-
 
 class Attachment(object):
     """A normalized EmailMessage.attachments item with additional functionality
@@ -153,6 +163,9 @@ class Attachment(object):
         else:
             (self.name, self.content, self.mimetype) = attachment
 
+        self.name = force_non_lazy(self.name)
+        self.content = force_non_lazy(self.content)
+
         # Guess missing mimetype from filename, borrowed from
         # django.core.mail.EmailMessage._create_attachment()
         if self.mimetype is None and self.name is not None:
@@ -280,3 +293,37 @@ def rfc2822date(dt):
     # but treats naive datetimes as local rather than "UTC with no information ..."
     timeval = timestamp(dt)
     return formatdate(timeval, usegmt=True)
+
+
+def is_lazy(obj):
+    """Return True if obj is a Django lazy object."""
+    # See django.utils.functional.lazy. (This appears to be preferred
+    # to checking for `not isinstance(obj, six.text_type)`.)
+    return isinstance(obj, Promise)
+
+
+def force_non_lazy(obj):
+    """If obj is a Django lazy object, return it coerced to text; otherwise return it unchanged.
+
+    (Similar to django.utils.encoding.force_text, but doesn't alter non-text objects.)
+    """
+    if is_lazy(obj):
+        return six.text_type(obj)
+
+    return obj
+
+
+def force_non_lazy_list(obj):
+    """Return a (shallow) copy of sequence obj, with all values forced non-lazy."""
+    try:
+        return [force_non_lazy(item) for item in obj]
+    except (AttributeError, TypeError):
+        return force_non_lazy(obj)
+
+
+def force_non_lazy_dict(obj):
+    """Return a (deep) copy of dict obj, with all values forced non-lazy."""
+    try:
+        return {key: force_non_lazy_dict(value) for key, value in obj.items()}
+    except (AttributeError, TypeError):
+        return force_non_lazy(obj)
diff --git a/anymail/webhooks/mandrill.py b/anymail/webhooks/mandrill.py
index b1d0c63..f3d13ce 100644
--- a/anymail/webhooks/mandrill.py
+++ b/anymail/webhooks/mandrill.py
@@ -23,15 +23,23 @@ class MandrillSignatureMixin(object):
     def __init__(self, **kwargs):
         # noinspection PyUnresolvedReferences
         esp_name = self.esp_name
-        webhook_key = get_anymail_setting('webhook_key', esp_name=esp_name,
+        # webhook_key is required for POST, but not for HEAD when Mandrill validates webhook url.
+        # Defer "missing setting" error until we actually try to use it in the POST...
+        webhook_key = get_anymail_setting('webhook_key', esp_name=esp_name, default=None,
                                           kwargs=kwargs, allow_bare=True)
-        self.webhook_key = webhook_key.encode('ascii')  # hmac.new requires bytes key in python 3
+        if webhook_key is not None:
+            self.webhook_key = webhook_key.encode('ascii')  # hmac.new requires bytes key in python 3
         self.webhook_url = get_anymail_setting('webhook_url', esp_name=esp_name, default=None,
                                                kwargs=kwargs, allow_bare=True)
         # noinspection PyArgumentList
         super(MandrillSignatureMixin, self).__init__(**kwargs)
 
     def validate_request(self, request):
+        if self.webhook_key is None:
+            # issue deferred "missing setting" error (re-call get-setting without a default)
+            # noinspection PyUnresolvedReferences
+            get_anymail_setting('webhook_key', esp_name=self.esp_name, allow_bare=True)
+
         try:
             signature = request.META["HTTP_X_MANDRILL_SIGNATURE"]
         except KeyError:
diff --git a/anymail/webhooks/postmark.py b/anymail/webhooks/postmark.py
index be34b4a..466bdfc 100644
--- a/anymail/webhooks/postmark.py
+++ b/anymail/webhooks/postmark.py
@@ -62,6 +62,8 @@ class PostmarkTrackingWebhookView(PostmarkBaseWebhookView):
         except KeyError:
             if 'FirstOpen' in esp_event:
                 event_type = EventType.OPENED
+            elif 'DeliveredAt' in esp_event:
+                event_type = EventType.DELIVERED
             elif 'From' in esp_event:
                 # This is an inbound event
                 raise AnymailConfigurationError(
@@ -73,7 +75,7 @@ class PostmarkTrackingWebhookView(PostmarkBaseWebhookView):
         recipient = getfirst(esp_event, ['Email', 'Recipient'], None)  # Email for bounce; Recipient for open
 
         try:
-            timestr = getfirst(esp_event, ['BouncedAt', 'ReceivedAt'])
+            timestr = getfirst(esp_event, ['DeliveredAt', 'BouncedAt', 'ReceivedAt'])
         except KeyError:
             timestamp = None
         else:
diff --git a/django_anymail.egg-info/PKG-INFO b/django_anymail.egg-info/PKG-INFO
index 4014186..8397941 100644
--- a/django_anymail.egg-info/PKG-INFO
+++ b/django_anymail.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: django-anymail
-Version: 0.6.1
+Version: 0.7
 Summary: Django email backends for Mailgun, Postmark, SendGrid and other transactional ESPs
 Home-page: https://github.com/anymail/django-anymail
 Author: Mike Edmunds <medmunds at gmail.com>
@@ -59,17 +59,17 @@ Description: Anymail: Django email backends for Mailgun, Postmark, SendGrid, Spa
         
         .. END shared-intro
         
-        .. image:: https://travis-ci.org/anymail/django-anymail.svg?branch=v0.6
+        .. image:: https://travis-ci.org/anymail/django-anymail.svg?branch=v0.7
                :target: https://travis-ci.org/anymail/django-anymail
                :alt:    build status on Travis-CI
         
-        .. image:: https://readthedocs.org/projects/anymail/badge/?version=v0.6
-               :target: https://anymail.readthedocs.io/en/v0.6/
+        .. image:: https://readthedocs.org/projects/anymail/badge/?version=v0.7
+               :target: https://anymail.readthedocs.io/en/v0.7/
                :alt:    documentation on ReadTheDocs
         
         **Resources**
         
-        * Full documentation: https://anymail.readthedocs.io/en/v0.6/
+        * Full documentation: https://anymail.readthedocs.io/en/v0.7/
         * Package on PyPI: https://pypi.python.org/pypi/django-anymail
         * Project on Github: https://github.com/anymail/django-anymail
         
@@ -157,7 +157,7 @@ Description: Anymail: Django email backends for Mailgun, Postmark, SendGrid, Spa
         .. END quickstart
         
         
-        See the `full documentation <https://anymail.readthedocs.io/en/v0.6/>`_
+        See the `full documentation <https://anymail.readthedocs.io/en/v0.7/>`_
         for more features and options.
         
 Keywords: django,email,email backend,ESP,transactional mail,mailgun,mandrill,postmark,sendgrid

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/django-anymail.git



More information about the Python-modules-commits mailing list