Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id DB46B200B4C for ; Fri, 22 Jul 2016 13:10:00 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id DA17F160A5A; Fri, 22 Jul 2016 11:10:00 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 0468F160A77 for ; Fri, 22 Jul 2016 13:09:59 +0200 (CEST) Received: (qmail 22622 invoked by uid 500); 22 Jul 2016 11:09:59 -0000 Mailing-List: contact commits-help@airflow.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@airflow.incubator.apache.org Delivered-To: mailing list commits@airflow.incubator.apache.org Received: (qmail 22613 invoked by uid 99); 22 Jul 2016 11:09:59 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd3-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 22 Jul 2016 11:09:59 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd3-us-west.apache.org (ASF Mail Server at spamd3-us-west.apache.org) with ESMTP id C26AC18397F for ; Fri, 22 Jul 2016 11:09:58 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd3-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: -4.646 X-Spam-Level: X-Spam-Status: No, score=-4.646 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, KAM_LAZY_DOMAIN_SECURITY=1, RCVD_IN_DNSWL_HI=-5, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RP_MATCHES_RCVD=-1.426] autolearn=disabled Received: from mx1-lw-us.apache.org ([10.40.0.8]) by localhost (spamd3-us-west.apache.org [10.40.0.10]) (amavisd-new, port 10024) with ESMTP id NrD5UMBJu3mD for ; Fri, 22 Jul 2016 11:09:54 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-lw-us.apache.org (ASF Mail Server at mx1-lw-us.apache.org) with SMTP id CD3A45FDE9 for ; Fri, 22 Jul 2016 11:09:52 +0000 (UTC) Received: (qmail 22242 invoked by uid 99); 22 Jul 2016 11:09:52 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 22 Jul 2016 11:09:52 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id DDE5EE058E; Fri, 22 Jul 2016 11:09:51 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: bolke@apache.org To: commits@airflow.incubator.apache.org Message-Id: <931f71191fd54b8491aa3b6e9854cc53@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: incubator-airflow git commit: AIRFLOW-261 Add bcc and cc fields to EmailOperator Date: Fri, 22 Jul 2016 11:09:51 +0000 (UTC) archived-at: Fri, 22 Jul 2016 11:10:01 -0000 Repository: incubator-airflow Updated Branches: refs/heads/master 189e6b887 -> 7628a8656 AIRFLOW-261 Add bcc and cc fields to EmailOperator Closes #1670 from ajayyadava/261 Project: http://git-wip-us.apache.org/repos/asf/incubator-airflow/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-airflow/commit/7628a865 Tree: http://git-wip-us.apache.org/repos/asf/incubator-airflow/tree/7628a865 Diff: http://git-wip-us.apache.org/repos/asf/incubator-airflow/diff/7628a865 Branch: refs/heads/master Commit: 7628a8656c37110ef9ea33c5142b52305ebe9440 Parents: 189e6b8 Author: Ajay Yadava Authored: Fri Jul 22 13:08:52 2016 +0200 Committer: Bolke de Bruin Committed: Fri Jul 22 13:08:56 2016 +0200 ---------------------------------------------------------------------- airflow/operators/email_operator.py | 10 +++++++- airflow/utils/email.py | 39 +++++++++++++++++++++++--------- tests/core.py | 20 +++++++++++++++- 3 files changed, 56 insertions(+), 13 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-airflow/blob/7628a865/airflow/operators/email_operator.py ---------------------------------------------------------------------- diff --git a/airflow/operators/email_operator.py b/airflow/operators/email_operator.py index 91a8d05..76cc56b 100644 --- a/airflow/operators/email_operator.py +++ b/airflow/operators/email_operator.py @@ -30,6 +30,10 @@ class EmailOperator(BaseOperator): :type html_content: string :param files: file names to attach in email :type files: list + :param cc: list of recipients to be added in CC field + :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) """ template_fields = ('subject', 'html_content') @@ -43,12 +47,16 @@ class EmailOperator(BaseOperator): subject, html_content, files=None, + cc=None, + bcc=None, *args, **kwargs): super(EmailOperator, self).__init__(*args, **kwargs) self.to = to self.subject = subject self.html_content = html_content self.files = files or [] + self.cc = cc + self.bcc = bcc def execute(self, context): - send_email(self.to, self.subject, self.html_content, files=self.files) + send_email(self.to, self.subject, self.html_content, files=self.files, cc=self.cc, bcc=self.bcc) http://git-wip-us.apache.org/repos/asf/incubator-airflow/blob/7628a865/airflow/utils/email.py ---------------------------------------------------------------------- diff --git a/airflow/utils/email.py b/airflow/utils/email.py index c19bb89..6fe8662 100644 --- a/airflow/utils/email.py +++ b/airflow/utils/email.py @@ -33,17 +33,17 @@ from email.utils import formatdate from airflow import configuration -def send_email(to, subject, html_content, files=None, dryrun=False): +def send_email(to, subject, html_content, files=None, dryrun=False, cc=None, bcc=None): """ Send email using backend specified in EMAIL_BACKEND. """ path, attr = configuration.get('email', 'EMAIL_BACKEND').rsplit('.', 1) module = importlib.import_module(path) backend = getattr(module, attr) - return backend(to, subject, html_content, files=files, dryrun=dryrun) + return backend(to, subject, html_content, files=files, dryrun=dryrun, cc=cc, bcc=bcc) -def send_email_smtp(to, subject, html_content, files=None, dryrun=False): +def send_email_smtp(to, subject, html_content, files=None, dryrun=False, cc=None, bcc=None): """ Send an email with html content @@ -51,18 +51,23 @@ def send_email_smtp(to, subject, html_content, files=None, dryrun=False): """ SMTP_MAIL_FROM = configuration.get('smtp', 'SMTP_MAIL_FROM') - if isinstance(to, basestring): - if ',' in to: - to = to.split(',') - elif ';' in to: - to = to.split(';') - else: - to = [to] + to = get_email_address_list(to) msg = MIMEMultipart('alternative') msg['Subject'] = subject msg['From'] = SMTP_MAIL_FROM msg['To'] = ", ".join(to) + recipients = to + if cc: + cc = get_email_address_list(cc) + msg['CC'] = ", ".join(cc) + recipients = recipients + cc + + if bcc: + # don't add bcc in header + bcc = get_email_address_list(bcc) + recipients = recipients + bcc + msg['Date'] = formatdate(localtime=True) mime_text = MIMEText(html_content, 'html') msg.attach(mime_text) @@ -76,7 +81,7 @@ def send_email_smtp(to, subject, html_content, files=None, dryrun=False): Name=basename )) - send_MIME_email(SMTP_MAIL_FROM, to, msg, dryrun) + send_MIME_email(SMTP_MAIL_FROM, recipients, msg, dryrun) def send_MIME_email(e_from, e_to, mime_msg, dryrun=False): @@ -96,3 +101,15 @@ def send_MIME_email(e_from, e_to, mime_msg, dryrun=False): logging.info("Sent an alert email to " + str(e_to)) s.sendmail(e_from, e_to, mime_msg.as_string()) s.quit() + + +def get_email_address_list(address_string): + if isinstance(address_string, basestring): + if ',' in address_string: + address_string = address_string.split(',') + elif ';' in address_string: + address_string = address_string.split(';') + else: + address_string = [address_string] + + return address_string http://git-wip-us.apache.org/repos/asf/incubator-airflow/blob/7628a865/tests/core.py ---------------------------------------------------------------------- diff --git a/tests/core.py b/tests/core.py index 4f3197d..36b484b 100644 --- a/tests/core.py +++ b/tests/core.py @@ -1481,7 +1481,7 @@ class EmailTest(unittest.TestCase): def test_custom_backend(self, mock_send_email): configuration.set('email', 'EMAIL_BACKEND', 'tests.core.send_email_test') utils.email.send_email('to', 'subject', 'content') - send_email_test.assert_called_with('to', 'subject', 'content', files=None, dryrun=False) + send_email_test.assert_called_with('to', 'subject', 'content', files=None, dryrun=False, cc=None, bcc=None) assert not mock_send_email.called @@ -1506,6 +1506,24 @@ class EmailSmtpTest(unittest.TestCase): mimeapp = MIMEApplication('attachment') assert msg.get_payload()[-1].get_payload() == mimeapp.get_payload() + @mock.patch('airflow.utils.email.send_MIME_email') + def test_send_bcc_smtp(self, mock_send_mime): + attachment = tempfile.NamedTemporaryFile() + attachment.write(b'attachment') + attachment.seek(0) + utils.email.send_email_smtp('to', 'subject', 'content', files=[attachment.name], cc='cc', bcc='bcc') + assert mock_send_mime.called + call_args = mock_send_mime.call_args[0] + assert call_args[0] == configuration.get('smtp', 'SMTP_MAIL_FROM') + assert call_args[1] == ['to', 'cc', 'bcc'] + msg = call_args[2] + assert msg['Subject'] == 'subject' + assert msg['From'] == configuration.get('smtp', 'SMTP_MAIL_FROM') + assert len(msg.get_payload()) == 2 + mimeapp = MIMEApplication('attachment') + assert msg.get_payload()[-1].get_payload() == mimeapp.get_payload() + + @mock.patch('smtplib.SMTP_SSL') @mock.patch('smtplib.SMTP') def test_send_mime(self, mock_smtp, mock_smtp_ssl):