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)