Drop Python 2 and Django 1.11 support

Minimum supported versions are now Django 2.0, Python 3.5.

This touches a lot of code, to:
* Remove obsolete portability code and workarounds
  (six, backports of email parsers, test utils, etc.)
* Use Python 3 syntax (class defs, raise ... from, etc.)
* Correct inheritance for mixin classes
* Fix outdated docs content and links
* Suppress Python 3 "unclosed SSLSocket" ResourceWarnings
  that are beyond our control (in integration tests due to boto3, 
  python-sparkpost)
This commit is contained in:
Mike Edmunds
2020-08-01 14:53:10 -07:00
committed by GitHub
parent c803108481
commit 85cec5e9dc
87 changed files with 672 additions and 1278 deletions

View File

@@ -26,18 +26,18 @@ The first approach is usually the simplest. The other two can be
helpful if you are working with Python development tools that
offer type checking or other static code analysis.
Availability of these features varies by ESP, and there may be additional
limitations even when an ESP does support a particular feature. Be sure
to check Anymail's docs for your :ref:`specific ESP <supported-esps>`.
If you try to use a feature your ESP does not offer, Anymail will raise
an :ref:`unsupported feature <unsupported-features>` error.
.. _anymail-send-options:
ESP send options (AnymailMessage)
---------------------------------
Availability of each of these features varies by ESP, and there may be additional
limitations even when an ESP does support a particular feature. Be sure
to check Anymail's docs for your :ref:`specific ESP <supported-esps>`.
If you try to use a feature your ESP does not offer, Anymail will raise
an :ref:`unsupported feature <unsupported-features>` error.
.. class:: AnymailMessage
A subclass of Django's :class:`~django.core.mail.EmailMultiAlternatives`
@@ -167,7 +167,7 @@ ESP send options (AnymailMessage)
ESPs have differing restrictions on tags. For portability,
it's best to stick with strings that start with an alphanumeric
character. (Also, Postmark only allows a single tag per message.)
character. (Also, a few ESPs allow only a single tag per message.)
.. caution::
@@ -359,7 +359,7 @@ ESP send status
* `'queued'` the ESP has accepted the message
and will try to send it asynchronously
* `'invalid'` the ESP considers the sender or recipient email invalid
* `'rejected'` the recipient is on an ESP blacklist
* `'rejected'` the recipient is on an ESP suppression list
(unsubscribe, previous bounces, etc.)
* `'failed'` the attempt to send failed for some other reason
* `'unknown'` anything else
@@ -402,7 +402,8 @@ ESP send status
.. code-block:: python
# This will work with a requests-based backend:
# This will work with a requests-based backend,
# for an ESP whose send API provides a JSON response:
message.anymail_status.esp_response.json()

View File

@@ -10,7 +10,7 @@ email using Django's default SMTP :class:`~django.core.mail.backends.smtp.EmailB
switching to Anymail will be easy. Anymail is designed to "just work" with Django.
If you're not familiar with Django's email functions, please take a look at
":mod:`sending email <django.core.mail>`" in the Django docs first.
:doc:`django:topics/email` in the Django docs first.
Anymail supports most of the functionality of Django's :class:`~django.core.mail.EmailMessage`
and :class:`~django.core.mail.EmailMultiAlternatives` classes.
@@ -39,8 +39,8 @@ function with the ``html_message`` parameter:
send_mail("Subject", "text body", "from@example.com",
["to@example.com"], html_message="<html>html body</html>")
However, many Django email capabilities -- and additional Anymail features --
are only available when working with an :class:`~django.core.mail.EmailMultiAlternatives`
However, many Django email capabilities---and additional Anymail features---are only
available when working with an :class:`~django.core.mail.EmailMultiAlternatives`
object. Use its :meth:`~django.core.mail.EmailMultiAlternatives.attach_alternative`
method to send HTML:
@@ -168,7 +168,8 @@ raise :exc:`~exceptions.AnymailUnsupportedFeature`.
.. setting:: ANYMAIL_IGNORE_UNSUPPORTED_FEATURES
If you'd like to silently ignore :exc:`~exceptions.AnymailUnsupportedFeature`
errors and send the messages anyway, set :setting:`!ANYMAIL_IGNORE_UNSUPPORTED_FEATURES`
errors and send the messages anyway, set
:setting:`"IGNORE_UNSUPPORTED_FEATURES" <ANYMAIL_IGNORE_UNSUPPORTED_FEATURES>`
to `True` in your settings.py:
.. code-block:: python
@@ -197,15 +198,16 @@ If a single message is sent to multiple recipients, and *any* recipient is valid
You can still examine the message's :attr:`~message.AnymailMessage.anymail_status`
property after the send to determine the status of each recipient.
You can disable this exception by setting :setting:`ANYMAIL_IGNORE_RECIPIENT_STATUS`
to `True` in your settings.py, which will cause Anymail to treat any non-API-error response
from your ESP as a successful send.
You can disable this exception by setting
:setting:`"IGNORE_RECIPIENT_STATUS" <ANYMAIL_IGNORE_RECIPIENT_STATUS>` to `True` in
your settings.py `ANYMAIL` dict, which will cause Anymail to treat *any*
response from your ESP (other than an API error) as a successful send.
.. note::
Many ESPs don't check recipient status during the send API call. For example,
Most ESPs don't check recipient status during the send API call. For example,
Mailgun always queues sent messages, so you'll never catch
:exc:`AnymailRecipientsRefused` with the Mailgun backend.
For those ESPs, use Anymail's :ref:`delivery event tracking <event-tracking>`
if you need to be notified of sends to blacklisted or invalid emails.
You can use Anymail's :ref:`delivery event tracking <event-tracking>`
if you need to be notified of sends to suppression-listed or invalid emails.

View File

@@ -211,7 +211,7 @@ for use as merge data:
# Do something this instead:
message.merge_global_data = {
'PRODUCT': product.name, # assuming name is a CharField
'TOTAL_COST': "%.2f" % total_cost,
'TOTAL_COST': "{cost:0.2f}".format(cost=total_cost),
'SHIP_DATE': ship_date.strftime('%B %d, %Y') # US-style "March 15, 2015"
}

View File

@@ -14,7 +14,7 @@ Webhook support is optional. If you haven't yet, you'll need to
project. (You may also want to review :ref:`securing-webhooks`.)
Once you've enabled webhooks, Anymail will send an ``anymail.signals.tracking``
custom Django :mod:`signal <django.dispatch>` for each ESP tracking event it receives.
custom Django :doc:`signal <django:topics/signals>` for each ESP tracking event it receives.
You can connect your own receiver function to this signal for further processing.
Be sure to read Django's `listening to signals`_ docs for information on defining
@@ -40,7 +40,7 @@ Example:
event.recipient, event.click_url))
You can define individual signal receivers, or create one big one for all
event types, which ever you prefer. You can even handle the same event
event types, whichever you prefer. You can even handle the same event
in multiple receivers, if that makes your code cleaner. These
:ref:`signal receiver functions <signal-receivers>` are documented
in more detail below.
@@ -189,8 +189,8 @@ Normalized tracking event
.. attribute:: mta_response
If available, a `str` with a raw (intended for email administrators) response
from the receiving MTA. Otherwise `None`. Often includes SMTP response codes,
but the exact format varies by ESP (and sometimes receiving MTA).
from the receiving mail transfer agent. Otherwise `None`. Often includes SMTP
response codes, but the exact format varies by ESP (and sometimes receiving MTA).
.. attribute:: user_agent
@@ -203,7 +203,7 @@ Normalized tracking event
.. attribute:: esp_event
The "raw" event data from the ESP, deserialized into a python data structure.
The "raw" event data from the ESP, deserialized into a Python data structure.
For most ESPs this is either parsed JSON (as a `dict`), or HTTP POST fields
(as a Django :class:`~django.http.QueryDict`).
@@ -230,7 +230,7 @@ Your Anymail signal receiver must be a function with this signature:
:param AnymailTrackingEvent event: The normalized tracking event.
Almost anything you'd be interested in
will be in here.
:param str esp_name: e.g., "SendMail" or "Postmark". If you are working
:param str esp_name: e.g., "SendGrid" or "Postmark". If you are working
with multiple ESPs, you can use this to distinguish
ESP-specific handling in your shared event processing.
:param \**kwargs: Required by Django's signal mechanism
@@ -259,7 +259,7 @@ And will retry sending the "failed" events, which could cause duplicate
processing in your code.
If your signal receiver code might be slow, you should instead
queue the event for later, asynchronous processing (e.g., using
something like `Celery`_).
something like :pypi:`celery`).
If your signal receiver function is defined within some other
function or instance method, you *must* use the `weak=False`
@@ -268,7 +268,6 @@ but will unpredictably stop being called at some point---typically
on your production server, in a hard-to-debug way. See Django's
`listening to signals`_ docs for more information.
.. _Celery: http://www.celeryproject.org/
.. _listening to signals:
https://docs.djangoproject.com/en/stable/topics/signals/#listening-to-signals