From 9356f5003583275ce12510eb3d070383cd6c2b49 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 10 Apr 2026 14:22:22 -0400 Subject: [PATCH 1/2] Propagate EmailMessage generics Commit 56db30e8eb4b456e892449491558af81a9eb201f made Message generic, but left EmailMessage inheriting from bare MIMEPart. Propagate the same type parameters to EmailMessage so it is typed consistently, and add a regression test case covering the generic EmailMessage behavior. --- stdlib/@tests/test_cases/email/check_message.py | 7 ++++++- stdlib/email/message.pyi | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/stdlib/@tests/test_cases/email/check_message.py b/stdlib/@tests/test_cases/email/check_message.py index 1bf3c3a2ec23..2b0b945456c1 100644 --- a/stdlib/@tests/test_cases/email/check_message.py +++ b/stdlib/@tests/test_cases/email/check_message.py @@ -1,4 +1,4 @@ -from email.headerregistry import Address +from email.headerregistry import Address, BaseHeader from email.message import EmailMessage from typing_extensions import assert_type @@ -8,3 +8,8 @@ for a in msg.iter_attachments(): assert_type(a, EmailMessage) + +generic_msg: EmailMessage[BaseHeader, str] = EmailMessage() +assert_type(generic_msg.get("To"), BaseHeader | None) +for a in generic_msg.iter_attachments(): + assert_type(a, EmailMessage[BaseHeader, str]) diff --git a/stdlib/email/message.pyi b/stdlib/email/message.pyi index c586c23b9e92..7ff2181d44f8 100644 --- a/stdlib/email/message.pyi +++ b/stdlib/email/message.pyi @@ -171,4 +171,4 @@ class MIMEPart(Message[_HeaderRegistryT_co, _HeaderRegistryParamT_contra]): def as_string(self, unixfrom: bool = False, maxheaderlen: int | None = None, policy: Policy[Any] | None = None) -> str: ... def is_attachment(self) -> bool: ... -class EmailMessage(MIMEPart): ... +class EmailMessage(MIMEPart[_HeaderRegistryT_co, _HeaderRegistryParamT_contra]): ... From 85500e325ae960fae0bc7108a0e8a26850f05f1a Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 10 Apr 2026 18:31:58 -0400 Subject: [PATCH 2/2] Preserve MIMEPart header param in returns Commit 56db30e8eb4b456e892449491558af81a9eb201f made Message generic and switched get_body() and iter_parts() to return MIMEPart[_HeaderRegistryT]. That dropped the second type parameter, so the header parameter type defaulted to Any. Preserve both type parameters in the return types and add a regression test covering get_body() and iter_parts(). --- stdlib/@tests/test_cases/email/check_message.py | 5 ++++- stdlib/email/message.pyi | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/stdlib/@tests/test_cases/email/check_message.py b/stdlib/@tests/test_cases/email/check_message.py index 2b0b945456c1..7939f2981708 100644 --- a/stdlib/@tests/test_cases/email/check_message.py +++ b/stdlib/@tests/test_cases/email/check_message.py @@ -1,5 +1,5 @@ from email.headerregistry import Address, BaseHeader -from email.message import EmailMessage +from email.message import EmailMessage, MIMEPart from typing_extensions import assert_type msg = EmailMessage() @@ -11,5 +11,8 @@ generic_msg: EmailMessage[BaseHeader, str] = EmailMessage() assert_type(generic_msg.get("To"), BaseHeader | None) +assert_type(generic_msg.get_body(), MIMEPart[BaseHeader, str] | None) for a in generic_msg.iter_attachments(): assert_type(a, EmailMessage[BaseHeader, str]) +for p in generic_msg.iter_parts(): + assert_type(p, MIMEPart[BaseHeader, str]) diff --git a/stdlib/email/message.pyi b/stdlib/email/message.pyi index 7ff2181d44f8..08ba88b4ee6d 100644 --- a/stdlib/email/message.pyi +++ b/stdlib/email/message.pyi @@ -151,13 +151,15 @@ class Message(Generic[_HeaderT_co, _HeaderParamT_contra]): class MIMEPart(Message[_HeaderRegistryT_co, _HeaderRegistryParamT_contra]): def __init__(self, policy: Policy[Any] | None = None) -> None: ... - def get_body(self, preferencelist: Sequence[str] = ("related", "html", "plain")) -> MIMEPart[_HeaderRegistryT_co] | None: ... + def get_body( + self, preferencelist: Sequence[str] = ("related", "html", "plain") + ) -> MIMEPart[_HeaderRegistryT_co, _HeaderRegistryParamT_contra] | None: ... def attach(self, payload: Self) -> None: ... # type: ignore[override] # The attachments are created via type(self) in the attach method. It's theoretically # possible to sneak other attachment types into a MIMEPart instance, but could cause # cause unforseen consequences. def iter_attachments(self) -> Iterator[Self]: ... - def iter_parts(self) -> Iterator[MIMEPart[_HeaderRegistryT_co]]: ... + def iter_parts(self) -> Iterator[MIMEPart[_HeaderRegistryT_co, _HeaderRegistryParamT_contra]]: ... def get_content(self, *args: Any, content_manager: ContentManager | None = None, **kw: Any) -> Any: ... def set_content(self, *args: Any, content_manager: ContentManager | None = None, **kw: Any) -> None: ... def make_related(self, boundary: str | None = None) -> None: ...