mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user