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

@@ -1,15 +1,15 @@
from base64 import b64decode
from email.message import Message
from email.parser import BytesParser, Parser
from email.policy import default as default_policy
from email.utils import unquote
import six
from django.core.files.uploadedfile import SimpleUploadedFile
from ._email_compat import EmailParser, EmailBytesParser
from .utils import angle_wrap, get_content_disposition, parse_address_list, parse_rfc2822date
from .utils import angle_wrap, parse_address_list, parse_rfc2822date
class AnymailInboundMessage(Message, object): # `object` ensures new-style class in Python 2)
class AnymailInboundMessage(Message):
"""
A normalized, parsed inbound email message.
@@ -31,7 +31,7 @@ class AnymailInboundMessage(Message, object): # `object` ensures new-style clas
def __init__(self, *args, **kwargs):
# Note: this must accept zero arguments, for use with message_from_string (email.parser)
super(AnymailInboundMessage, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
# Additional attrs provided by some ESPs:
self.envelope_sender = None
@@ -125,14 +125,7 @@ class AnymailInboundMessage(Message, object): # `object` ensures new-style clas
return part.get_content_text()
return None
# Backport from Python 3.5 email.message.Message
def get_content_disposition(self):
try:
return super(AnymailInboundMessage, self).get_content_disposition()
except AttributeError:
return get_content_disposition(self)
# Backport from Python 3.4.2 email.message.MIMEPart
# Hoisted from email.message.MIMEPart
def is_attachment(self):
return self.get_content_disposition() == 'attachment'
@@ -148,10 +141,7 @@ class AnymailInboundMessage(Message, object): # `object` ensures new-style clas
# (Note that self.is_multipart() misleadingly returns True in this case.)
payload = self.get_payload()
assert len(payload) == 1 # should be exactly one message
try:
return payload[0].as_bytes() # Python 3
except AttributeError:
return payload[0].as_string().encode('utf-8')
return payload[0].as_bytes()
elif maintype == 'multipart':
# The attachment itself is multipart; the payload is a list of parts,
# and it's not clear which one is the "content".
@@ -199,24 +189,24 @@ class AnymailInboundMessage(Message, object): # `object` ensures new-style clas
@classmethod
def parse_raw_mime(cls, s):
"""Returns a new AnymailInboundMessage parsed from str s"""
if isinstance(s, six.text_type):
if isinstance(s, str):
# Avoid Python 3.x issue https://bugs.python.org/issue18271
# (See test_inbound: test_parse_raw_mime_8bit_utf8)
return cls.parse_raw_mime_bytes(s.encode('utf-8'))
return EmailParser(cls).parsestr(s)
return Parser(cls, policy=default_policy).parsestr(s)
@classmethod
def parse_raw_mime_bytes(cls, b):
"""Returns a new AnymailInboundMessage parsed from bytes b"""
return EmailBytesParser(cls).parsebytes(b)
return BytesParser(cls, policy=default_policy).parsebytes(b)
@classmethod
def parse_raw_mime_file(cls, fp):
"""Returns a new AnymailInboundMessage parsed from file-like object fp"""
if isinstance(fp.read(0), six.binary_type):
return EmailBytesParser(cls).parse(fp)
if isinstance(fp.read(0), bytes):
return BytesParser(cls, policy=default_policy).parse(fp)
else:
return EmailParser(cls).parse(fp)
return Parser(cls, policy=default_policy).parse(fp)
@classmethod
def construct(cls, raw_headers=None, from_email=None, to=None, cc=None, subject=None, headers=None,
@@ -242,7 +232,7 @@ class AnymailInboundMessage(Message, object): # `object` ensures new-style clas
:return: {AnymailInboundMessage}
"""
if raw_headers is not None:
msg = EmailParser(cls).parsestr(raw_headers, headersonly=True)
msg = Parser(cls, policy=default_policy).parsestr(raw_headers, headersonly=True)
msg.set_payload(None) # headersonly forces an empty string payload, which breaks things later
else:
msg = cls()
@@ -336,7 +326,7 @@ class AnymailInboundMessage(Message, object): # `object` ensures new-style clas
if part.get_content_maintype() == 'message':
# email.Message parses message/rfc822 parts as a "multipart" (list) payload
# whose single item is the recursively-parsed message attachment
if isinstance(content, six.binary_type):
if isinstance(content, bytes):
content = content.decode()
payload = [cls.parse_raw_mime(content)]
charset = None