This is an automated email from the ASF dual-hosted git repository.

ephraimanierobi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new cce4ca5505 Add notifier for Smtp (#31359)
cce4ca5505 is described below

commit cce4ca55058b605d19841bb9d43043f0d45665cb
Author: Utkarsh Sharma <[email protected]>
AuthorDate: Wed Jun 7 11:53:07 2023 +0530

    Add notifier for Smtp (#31359)
    
    * Saving work
    
    * Add doc for smtp notifier
    
    * Fix test
    
    * Fix docstrings
    
    * Fix docstrings
    
    * Update document
    
    * Update tests
    
    * Change import statement for cached_property
    
    ---------
    
    Co-authored-by: Jed Cunningham 
<[email protected]>
---
 airflow/providers/smtp/notifications/__init__.py   |  16 +++
 airflow/providers/smtp/notifications/smtp.py       | 106 ++++++++++++++++++++
 airflow/providers/smtp/provider.yaml               |   3 +
 docs/apache-airflow-providers-smtp/index.rst       |   7 ++
 .../notifications/smtp_notifier_howto_guide.rst    |  61 ++++++++++++
 tests/providers/smtp/notifications/__init__.py     |  16 +++
 tests/providers/smtp/notifications/test_smtp.py    | 108 +++++++++++++++++++++
 7 files changed, 317 insertions(+)

diff --git a/airflow/providers/smtp/notifications/__init__.py 
b/airflow/providers/smtp/notifications/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/airflow/providers/smtp/notifications/__init__.py
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# 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
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
diff --git a/airflow/providers/smtp/notifications/smtp.py 
b/airflow/providers/smtp/notifications/smtp.py
new file mode 100644
index 0000000000..301fe50751
--- /dev/null
+++ b/airflow/providers/smtp/notifications/smtp.py
@@ -0,0 +1,106 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# 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
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from functools import cached_property
+from typing import Any, Iterable
+
+from airflow.exceptions import AirflowOptionalProviderFeatureException
+
+try:
+    from airflow.notifications.basenotifier import BaseNotifier
+except ImportError:
+    raise AirflowOptionalProviderFeatureException(
+        "Failed to import BaseNotifier. This feature is only available in 
Airflow versions >= 2.6.0"
+    )
+
+from airflow.providers.smtp.hooks.smtp import SmtpHook
+
+
+class SmtpNotifier(BaseNotifier):
+    """
+    SMTP Notifier
+
+    :param smtp_conn_id: The :ref:`smtp connection id <howto/connection:smtp>`
+        that contains the information used to authenticate the client.
+    """
+
+    template_fields = (
+        "from_email",
+        "to",
+        "subject",
+        "html_content",
+        "files",
+        "cc",
+        "bcc",
+        "mime_subtype",
+        "mime_charset",
+        "custom_headers",
+    )
+
+    def __init__(
+        self,
+        from_email: str | None,
+        to: str | Iterable[str],
+        subject: str,
+        html_content: str,
+        files: list[str] | None = None,
+        cc: str | Iterable[str] | None = None,
+        bcc: str | Iterable[str] | None = None,
+        mime_subtype: str = "mixed",
+        mime_charset: str = "utf-8",
+        custom_headers: dict[str, Any] | None = None,
+        smtp_conn_id: str = SmtpHook.default_conn_name,
+    ):
+        super().__init__()
+        self.smtp_conn_id = smtp_conn_id
+        self.from_email = from_email
+        self.to = to
+        self.subject = subject
+        self.html_content = html_content
+        self.files = files
+        self.cc = cc
+        self.bcc = bcc
+        self.mime_subtype = mime_subtype
+        self.mime_charset = mime_charset
+        self.custom_headers = custom_headers
+
+    @cached_property
+    def hook(self) -> SmtpHook:
+        """Smtp Events Hook"""
+        return SmtpHook(smtp_conn_id=self.smtp_conn_id)
+
+    def notify(self, context):
+        """Send a email via smtp server"""
+        with self.hook as smtp:
+            smtp.send_email_smtp(
+                smtp_conn_id=self.smtp_conn_id,
+                from_email=self.from_email,
+                to=self.to,
+                subject=self.subject,
+                html_content=self.html_content,
+                files=self.files,
+                cc=self.cc,
+                bcc=self.bcc,
+                mime_subtype=self.mime_subtype,
+                mime_charset=self.mime_charset,
+                custom_headers=self.custom_headers,
+            )
+
+
+send_smtp_notification = SmtpNotifier
diff --git a/airflow/providers/smtp/provider.yaml 
b/airflow/providers/smtp/provider.yaml
index 75617c3272..bf415a7be5 100644
--- a/airflow/providers/smtp/provider.yaml
+++ b/airflow/providers/smtp/provider.yaml
@@ -50,3 +50,6 @@ hooks:
 connection-types:
   - hook-class-name: airflow.providers.smtp.hooks.smtp.SmtpHook
     connection-type: smtp
+
+notifications:
+  - airflow.providers.smtp.notifications.smtp.SmtpNotifier
diff --git a/docs/apache-airflow-providers-smtp/index.rst 
b/docs/apache-airflow-providers-smtp/index.rst
index a5f89a72da..f50fcb83e1 100644
--- a/docs/apache-airflow-providers-smtp/index.rst
+++ b/docs/apache-airflow-providers-smtp/index.rst
@@ -46,6 +46,13 @@ Content
     Detailed list of commits <commits>
 
 
+.. toctree::
+    :maxdepth: 1
+    :caption: Guides
+
+    SMTP Notifications <notifications/smtp_notifier_howto_guide>
+
+
 Package apache-airflow-providers-smtp
 ------------------------------------------------------
 
diff --git 
a/docs/apache-airflow-providers-smtp/notifications/smtp_notifier_howto_guide.rst
 
b/docs/apache-airflow-providers-smtp/notifications/smtp_notifier_howto_guide.rst
new file mode 100644
index 0000000000..9d9d0ac5ee
--- /dev/null
+++ 
b/docs/apache-airflow-providers-smtp/notifications/smtp_notifier_howto_guide.rst
@@ -0,0 +1,61 @@
+ .. Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    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
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+How-to Guide for SMTP notifications
+===================================
+
+Introduction
+------------
+The SMTP notifier 
(:class:`airflow.providers.smtp.notifications.smtp.SmtpNotifier`) allows users 
to send
+messages to SMTP servers using the various ``on_*_callbacks`` at both the DAG 
level and Task level.
+
+Example Code:
+-------------
+
+.. code-block:: python
+
+    from datetime import datetime
+    from airflow import DAG
+    from airflow.operators.bash import BashOperator
+    from airflow.providers.smtp.notifications.smtp import 
send_smtp_notification
+
+    with DAG(
+        dag_id="smtp_notifier",
+        schedule_interval=None,
+        start_date=datetime(2023, 1, 1),
+        catchup=False,
+        on_failure_callback=[
+            send_smtp_notification(
+                from_email="[email protected]",
+                to="[email protected]",
+                subject="[Error] The dag {{ dag.dag_id }} failed",
+                html_content="debug logs",
+            )
+        ],
+    ):
+        BashOperator(
+            task_id="mytask",
+            on_failure_callback=[
+                send_smtp_notification(
+                    from_email="[email protected]",
+                    to="[email protected]",
+                    subject="[Error] The Task {{ ti.task_id }} failed",
+                    html_content="debug logs",
+                )
+            ],
+            bash_command="fail",
+        )
diff --git a/tests/providers/smtp/notifications/__init__.py 
b/tests/providers/smtp/notifications/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/tests/providers/smtp/notifications/__init__.py
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# 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
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
diff --git a/tests/providers/smtp/notifications/test_smtp.py 
b/tests/providers/smtp/notifications/test_smtp.py
new file mode 100644
index 0000000000..44d2ce6b9d
--- /dev/null
+++ b/tests/providers/smtp/notifications/test_smtp.py
@@ -0,0 +1,108 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# 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
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from unittest import mock
+
+from airflow.operators.empty import EmptyOperator
+from airflow.providers.smtp.hooks.smtp import SmtpHook
+from airflow.providers.smtp.notifications.smtp import (
+    SmtpNotifier,
+    send_smtp_notification,
+)
+
+SMTP_API_DEFAULT_CONN_ID = SmtpHook.default_conn_name
+
+
+class TestPagerdutyNotifier:
+    @mock.patch("airflow.providers.smtp.notifications.smtp.SmtpHook")
+    def test_notifier(self, mock_smtphook_hook, dag_maker):
+        with dag_maker("test_notifier") as dag:
+            EmptyOperator(task_id="task1")
+        notifier = send_smtp_notification(
+            from_email="[email protected]",
+            to="[email protected]",
+            subject="subject",
+            html_content="body",
+        )
+        notifier(context={"dag": dag})
+        
mock_smtphook_hook.return_value.__enter__().send_email_smtp.assert_called_once_with(
+            from_email="[email protected]",
+            to="[email protected]",
+            subject="subject",
+            html_content="body",
+            smtp_conn_id="smtp_default",
+            files=None,
+            cc=None,
+            bcc=None,
+            mime_subtype="mixed",
+            mime_charset="utf-8",
+            custom_headers=None,
+        )
+
+    @mock.patch("airflow.providers.smtp.notifications.smtp.SmtpHook")
+    def test_notifier_with_notifier_class(self, mock_smtphook_hook, dag_maker):
+        with dag_maker("test_notifier") as dag:
+            EmptyOperator(task_id="task1")
+        notifier = SmtpNotifier(
+            from_email="[email protected]",
+            to="[email protected]",
+            subject="subject",
+            html_content="body",
+        )
+        notifier(context={"dag": dag})
+        
mock_smtphook_hook.return_value.__enter__().send_email_smtp.assert_called_once_with(
+            from_email="[email protected]",
+            to="[email protected]",
+            subject="subject",
+            html_content="body",
+            smtp_conn_id="smtp_default",
+            files=None,
+            cc=None,
+            bcc=None,
+            mime_subtype="mixed",
+            mime_charset="utf-8",
+            custom_headers=None,
+        )
+
+    @mock.patch("airflow.providers.smtp.notifications.smtp.SmtpHook")
+    def test_notifier_templated(self, mock_smtphook_hook, dag_maker):
+        with dag_maker("test_notifier") as dag:
+            EmptyOperator(task_id="task1")
+
+        notifier = SmtpNotifier(
+            from_email="[email protected] {{dag.dag_id}}",
+            to="[email protected] {{dag.dag_id}}",
+            subject="subject {{dag.dag_id}}",
+            html_content="body {{dag.dag_id}}",
+        )
+        context = {"dag": dag}
+        notifier(context)
+        
mock_smtphook_hook.return_value.__enter__().send_email_smtp.assert_called_once_with(
+            from_email="[email protected] test_notifier",
+            to="[email protected] test_notifier",
+            subject="subject test_notifier",
+            html_content="body test_notifier",
+            smtp_conn_id="smtp_default",
+            files=None,
+            cc=None,
+            bcc=None,
+            mime_subtype="mixed",
+            mime_charset="utf-8",
+            custom_headers=None,
+        )

Reply via email to