[AIRFLOW-1914] Add other charset support to email utils

The built-in email utils does not support
multibyte string content, for example,
Japanese or emojis. The fix is to add
mime_charset parameter to allow for other
values such as `utf-8`.

Closes #3308 from wolfier/AIRFLOW-1914
This commit is contained in:
Alan Ma 2018-05-06 11:16:55 +02:00 коммит произвёл Fokko Driesprong
Родитель 29ae02a070
Коммит c0cf73d27b
3 изменённых файлов: 34 добавлений и 15 удалений

Просмотреть файл

@ -7,9 +7,9 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@ -39,6 +39,11 @@ class EmailOperator(BaseOperator):
:type cc: list or string (comma or semicolon delimited)
:param bcc: list of recipients to be added in BCC field
:type bcc: list or string (comma or semicolon delimited)
:param mime_subtype: MIME sub content type
:type mime_subtype: string
:param mime_charset: character set parameter added to the Content-Type
header.
:type mime_charset: string
"""
template_fields = ('to', 'subject', 'html_content')
@ -55,6 +60,7 @@ class EmailOperator(BaseOperator):
cc=None,
bcc=None,
mime_subtype='mixed',
mime_charset='us_ascii',
*args, **kwargs):
super(EmailOperator, self).__init__(*args, **kwargs)
self.to = to
@ -64,6 +70,9 @@ class EmailOperator(BaseOperator):
self.cc = cc
self.bcc = bcc
self.mime_subtype = mime_subtype
self.mime_charset = mime_charset
def execute(self, context):
send_email(self.to, self.subject, self.html_content, files=self.files, cc=self.cc, bcc=self.bcc, mime_subtype=self.mime_subtype)
send_email(self.to, self.subject, self.html_content,
files=self.files, cc=self.cc, bcc=self.bcc,
mime_subtype=self.mime_subtype, mine_charset=self.mime_charset)

Просмотреть файл

@ -7,9 +7,9 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@ -39,9 +39,9 @@ from airflow.exceptions import AirflowConfigException
from airflow.utils.log.logging_mixin import LoggingMixin
def send_email(to, subject, html_content, files=None,
dryrun=False, cc=None, bcc=None,
mime_subtype='mixed', **kwargs):
def send_email(to, subject, html_content,
files=None, dryrun=False, cc=None, bcc=None,
mime_subtype='mixed', mime_charset='us-ascii', **kwargs):
"""
Send email using backend specified in EMAIL_BACKEND.
"""
@ -50,12 +50,13 @@ def send_email(to, subject, html_content, files=None,
backend = getattr(module, attr)
return backend(to, subject, html_content, files=files,
dryrun=dryrun, cc=cc, bcc=bcc,
mime_subtype=mime_subtype, **kwargs)
mime_subtype=mime_subtype, mime_charset=mime_charset, **kwargs)
def send_email_smtp(to, subject, html_content, files=None,
dryrun=False, cc=None, bcc=None,
mime_subtype='mixed', **kwargs):
mime_subtype='mixed', mime_charset='us-ascii',
**kwargs):
"""
Send an email with html content
@ -81,7 +82,7 @@ def send_email_smtp(to, subject, html_content, files=None,
recipients = recipients + bcc
msg['Date'] = formatdate(localtime=True)
mime_text = MIMEText(html_content, 'html')
mime_text = MIMEText(html_content, 'html', mime_charset)
msg.attach(mime_text)
for fname in files or []:

Просмотреть файл

@ -37,6 +37,7 @@ from datetime import timedelta
from dateutil.relativedelta import relativedelta
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from freezegun import freeze_time
from numpy.testing import assert_array_almost_equal
from six.moves.urllib.parse import urlencode
@ -2409,8 +2410,7 @@ class EmailTest(unittest.TestCase):
utils.email.send_email('to', 'subject', 'content')
send_email_test.assert_called_with(
'to', 'subject', 'content', files=None, dryrun=False,
cc=None, bcc=None, mime_subtype='mixed'
)
cc=None, bcc=None, mime_charset='us-ascii', mime_subtype='mixed')
self.assertFalse(mock_send_email.called)
@ -2432,11 +2432,20 @@ class EmailSmtpTest(unittest.TestCase):
self.assertEqual('subject', msg['Subject'])
self.assertEqual(configuration.conf.get('smtp', 'SMTP_MAIL_FROM'), msg['From'])
self.assertEqual(2, len(msg.get_payload()))
self.assertEqual(u'attachment; filename="' + os.path.basename(attachment.name) + '"',
msg.get_payload()[-1].get(u'Content-Disposition'))
filename = u'attachment; filename="' + os.path.basename(attachment.name) + '"'
self.assertEqual(filename, msg.get_payload()[-1].get(u'Content-Disposition'))
mimeapp = MIMEApplication('attachment')
self.assertEqual(mimeapp.get_payload(), msg.get_payload()[-1].get_payload())
@mock.patch('airflow.utils.email.send_MIME_email')
def test_send_smtp_with_multibyte_content(self, mock_send_mime):
utils.email.send_email_smtp('to', 'subject', '🔥', mime_charset='utf-8')
self.assertTrue(mock_send_mime.called)
call_args = mock_send_mime.call_args[0]
msg = call_args[2]
mimetext = MIMEText('🔥', 'mixed', 'utf-8')
self.assertEqual(mimetext.get_payload(), msg.get_payload()[0].get_payload())
@mock.patch('airflow.utils.email.send_MIME_email')
def test_send_bcc_smtp(self, mock_send_mime):
attachment = tempfile.NamedTemporaryFile()