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

eladkal 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 b4320026a31 Stop importing airflow.utils.email in the SendGrid emailer 
(#75771)
b4320026a31 is described below

commit b4320026a3188d2639c3ff089f74be33a7ff0aa8
Author: Shivam Rastogi <[email protected]>
AuthorDate: Tue Oct 27 23:58:47 2026 -0700

    Stop importing airflow.utils.email in the SendGrid emailer (#75771)
    
    airflow.utils.email is being deprecated in favor of the SMTP provider. The 
SendGrid emailer only needs get_email_address_list from it, so it now uses a 
private copy instead, which also avoids a new dependency on the SMTP provider.
---
 .../airflow/providers/sendgrid/utils/emailer.py    | 23 +++++++++++---
 .../tests/unit/sendgrid/utils/test_emailer.py      | 35 +++++++++++++++++++++-
 2 files changed, 53 insertions(+), 5 deletions(-)

diff --git a/providers/sendgrid/src/airflow/providers/sendgrid/utils/emailer.py 
b/providers/sendgrid/src/airflow/providers/sendgrid/utils/emailer.py
index 2c3cf4bf325..fe5df8d1b9d 100644
--- a/providers/sendgrid/src/airflow/providers/sendgrid/utils/emailer.py
+++ b/providers/sendgrid/src/airflow/providers/sendgrid/utils/emailer.py
@@ -23,6 +23,7 @@ import base64
 import logging
 import mimetypes
 import os
+import re
 from collections.abc import Iterable
 
 import sendgrid
@@ -39,13 +40,27 @@ from sendgrid.helpers.mail import (
 )
 
 from airflow.providers.common.compat.sdk import BaseHook
-from airflow.utils.email import get_email_address_list
 
 log = logging.getLogger(__name__)
 
 AddressesType = str | Iterable[str]
 
 
+def _get_email_address_list(addresses: AddressesType) -> list[str]:
+    """
+    Return a list of email addresses; a string may separate them with ``,`` or 
``;``.
+
+    Copied from 
``airflow.providers.smtp.utils.emailer.get_email_address_list``.
+    """
+    if isinstance(addresses, str):
+        return re.split(r"\s*[,;]\s*", addresses)
+    if isinstance(addresses, Iterable):
+        if not all(isinstance(item, str) for item in addresses):
+            raise TypeError("The items in your iterable must be strings.")
+        return list(addresses)
+    raise TypeError(f"Unexpected argument type: Received 
'{type(addresses).__name__}'.")
+
+
 def send_email(
     to: AddressesType,
     subject: str,
@@ -78,15 +93,15 @@ def send_email(
 
     # Add the recipient list of to emails.
     personalization = Personalization()
-    to = get_email_address_list(to)
+    to = _get_email_address_list(to)
     for to_address in to:
         personalization.add_to(Email(to_address))
     if cc:
-        cc = get_email_address_list(cc)
+        cc = _get_email_address_list(cc)
         for cc_address in cc:
             personalization.add_cc(Email(cc_address))
     if bcc:
-        bcc = get_email_address_list(bcc)
+        bcc = _get_email_address_list(bcc)
         for bcc_address in bcc:
             personalization.add_bcc(Email(bcc_address))
 
diff --git a/providers/sendgrid/tests/unit/sendgrid/utils/test_emailer.py 
b/providers/sendgrid/tests/unit/sendgrid/utils/test_emailer.py
index d9165a13d30..0b636a450e5 100644
--- a/providers/sendgrid/tests/unit/sendgrid/utils/test_emailer.py
+++ b/providers/sendgrid/tests/unit/sendgrid/utils/test_emailer.py
@@ -19,10 +19,14 @@ from __future__ import annotations
 
 import copy
 import os
+import subprocess
+import sys
 import tempfile
 from unittest import mock
 
-from airflow.providers.sendgrid.utils.emailer import send_email
+import pytest
+
+from airflow.providers.sendgrid.utils.emailer import _get_email_address_list, 
send_email
 
 
 class TestSendEmailSendGrid:
@@ -124,3 +128,32 @@ class TestSendEmailSendGrid:
             from_name="Foo Bar",
         )
         mock_post.assert_called_once_with(self.expected_mail_data_sender, 
"sendgrid_default")
+
+
+class TestGetEmailAddressList:
+    @pytest.mark.parametrize(
+        "addresses", ["[email protected], [email protected]", 
"[email protected];[email protected]"], ids=["comma", "semicolon"]
+    )
+    def test_splits_string(self, addresses):
+        assert _get_email_address_list(addresses) == ["[email protected]", 
"[email protected]"]
+
+    @pytest.mark.parametrize(
+        ("addresses", "match"),
+        [
+            pytest.param(1, "Unexpected argument type: Received 'int'", 
id="not_iterable"),
+            pytest.param(["[email protected]", 2], "The items in your iterable must 
be strings", id="not_string"),
+        ],
+    )
+    def test_rejects_non_strings(self, addresses, match):
+        with pytest.raises(TypeError, match=match):
+            _get_email_address_list(addresses)
+
+
+def test_emailer_imports_without_core_email_utils():
+    """``airflow.utils.email`` is moving to the SMTP provider, so the emailer 
must not import it."""
+    script = (
+        "import sys\n"
+        "sys.modules['airflow.utils.email'] = None\n"
+        "import airflow.providers.sendgrid.utils.emailer\n"
+    )
+    subprocess.run([sys.executable, "-c", script], check=True)

Reply via email to