Skip to content

Commit e224b28

Browse files
authored
Fix variance of a few email-related classes (#13952)
Closes #13919
1 parent 41b2f9e commit e224b28

4 files changed

Lines changed: 35 additions & 28 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from email.mime.text import MIMEText
2+
from email.policy import SMTP
3+
4+
msg = MIMEText("", policy=SMTP)

stdlib/email/_policybase.pyi

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ from typing_extensions import Self
88
__all__ = ["Policy", "Compat32", "compat32"]
99

1010
_MessageT = TypeVar("_MessageT", bound=Message[Any, Any], default=Message[str, str])
11+
_MessageT_co = TypeVar("_MessageT_co", covariant=True, bound=Message[Any, Any], default=Message[str, str])
1112

1213
@type_check_only
1314
class _MessageFactory(Protocol[_MessageT]):
@@ -16,13 +17,13 @@ class _MessageFactory(Protocol[_MessageT]):
1617
# Policy below is the only known direct subclass of _PolicyBase. We therefore
1718
# assume that the __init__ arguments and attributes of _PolicyBase are
1819
# the same as those of Policy.
19-
class _PolicyBase(Generic[_MessageT]):
20+
class _PolicyBase(Generic[_MessageT_co]):
2021
max_line_length: int | None
2122
linesep: str
2223
cte_type: str
2324
raise_on_defect: bool
2425
mangle_from_: bool
25-
message_factory: _MessageFactory[_MessageT] | None
26+
message_factory: _MessageFactory[_MessageT_co] | None
2627
# Added in Python 3.9.20, 3.10.15, 3.11.10, 3.12.5
2728
verify_generated_headers: bool
2829

@@ -34,7 +35,7 @@ class _PolicyBase(Generic[_MessageT]):
3435
cte_type: str = "8bit",
3536
raise_on_defect: bool = False,
3637
mangle_from_: bool = ..., # default depends on sub-class
37-
message_factory: _MessageFactory[_MessageT] | None = None,
38+
message_factory: _MessageFactory[_MessageT_co] | None = None,
3839
# Added in Python 3.9.20, 3.10.15, 3.11.10, 3.12.5
3940
verify_generated_headers: bool = True,
4041
) -> None: ...
@@ -46,15 +47,17 @@ class _PolicyBase(Generic[_MessageT]):
4647
cte_type: str = ...,
4748
raise_on_defect: bool = ...,
4849
mangle_from_: bool = ...,
49-
message_factory: _MessageFactory[_MessageT] | None = ...,
50+
message_factory: _MessageFactory[_MessageT_co] | None = ...,
5051
# Added in Python 3.9.20, 3.10.15, 3.11.10, 3.12.5
5152
verify_generated_headers: bool = ...,
5253
) -> Self: ...
5354
def __add__(self, other: Policy) -> Self: ...
5455

55-
class Policy(_PolicyBase[_MessageT], metaclass=ABCMeta):
56-
def handle_defect(self, obj: _MessageT, defect: MessageDefect) -> None: ...
57-
def register_defect(self, obj: _MessageT, defect: MessageDefect) -> None: ...
56+
class Policy(_PolicyBase[_MessageT_co], metaclass=ABCMeta):
57+
# Every Message object has a `defects` attribute, so the following
58+
# methods will work for any Message object.
59+
def handle_defect(self, obj: Message[Any, Any], defect: MessageDefect) -> None: ...
60+
def register_defect(self, obj: Message[Any, Any], defect: MessageDefect) -> None: ...
5861
def header_max_count(self, name: str) -> int | None: ...
5962
@abstractmethod
6063
def header_source_parse(self, sourcelines: list[str]) -> tuple[str, str]: ...
@@ -67,7 +70,7 @@ class Policy(_PolicyBase[_MessageT], metaclass=ABCMeta):
6770
@abstractmethod
6871
def fold_binary(self, name: str, value: str) -> bytes: ...
6972

70-
class Compat32(Policy[_MessageT]):
73+
class Compat32(Policy[_MessageT_co]):
7174
def header_source_parse(self, sourcelines: list[str]) -> tuple[str, str]: ...
7275
def header_store_parse(self, name: str, value: str) -> tuple[str, str]: ...
7376
def header_fetch_parse(self, name: str, value: str) -> str | Header: ... # type: ignore[override]

stdlib/email/message.pyi

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ __all__ = ["Message", "EmailMessage"]
1212

1313
_T = TypeVar("_T")
1414
# Type returned by Policy.header_fetch_parse, often str or Header.
15-
_HeaderT = TypeVar("_HeaderT", default=str)
16-
_HeaderParamT = TypeVar("_HeaderParamT", default=str)
15+
_HeaderT_co = TypeVar("_HeaderT_co", covariant=True, default=str)
16+
_HeaderParamT_contra = TypeVar("_HeaderParamT_contra", contravariant=True, default=str)
1717
# Represents headers constructed by HeaderRegistry. Those are sub-classes
1818
# of BaseHeader and another header type.
19-
_HeaderRegistryT = TypeVar("_HeaderRegistryT", default=Any)
20-
_HeaderRegistryParamT = TypeVar("_HeaderRegistryParamT", default=Any)
19+
_HeaderRegistryT_co = TypeVar("_HeaderRegistryT_co", covariant=True, default=Any)
20+
_HeaderRegistryParamT_contra = TypeVar("_HeaderRegistryParamT_contra", contravariant=True, default=Any)
2121

2222
_PayloadType: TypeAlias = Message | str
2323
_EncodedPayloadType: TypeAlias = Message | bytes
@@ -30,7 +30,7 @@ class _SupportsEncodeToPayload(Protocol):
3030
class _SupportsDecodeToPayload(Protocol):
3131
def decode(self, encoding: str, errors: str, /) -> _PayloadType | _MultipartPayloadType: ...
3232

33-
class Message(Generic[_HeaderT, _HeaderParamT]):
33+
class Message(Generic[_HeaderT_co, _HeaderParamT_contra]):
3434
# The policy attributes and arguments in this class and its subclasses
3535
# would ideally use Policy[Self], but this is not possible.
3636
policy: Policy[Any] # undocumented
@@ -76,22 +76,22 @@ class Message(Generic[_HeaderT, _HeaderParamT]):
7676
# This is important for protocols using __getitem__, like SupportsKeysAndGetItem
7777
# Morally, the return type should be `AnyOf[_HeaderType, None]`,
7878
# so using "the Any trick" instead.
79-
def __getitem__(self, name: str) -> _HeaderT | MaybeNone: ...
80-
def __setitem__(self, name: str, val: _HeaderParamT) -> None: ...
79+
def __getitem__(self, name: str) -> _HeaderT_co | MaybeNone: ...
80+
def __setitem__(self, name: str, val: _HeaderParamT_contra) -> None: ...
8181
def __delitem__(self, name: str) -> None: ...
8282
def keys(self) -> list[str]: ...
83-
def values(self) -> list[_HeaderT]: ...
84-
def items(self) -> list[tuple[str, _HeaderT]]: ...
83+
def values(self) -> list[_HeaderT_co]: ...
84+
def items(self) -> list[tuple[str, _HeaderT_co]]: ...
8585
@overload
86-
def get(self, name: str, failobj: None = None) -> _HeaderT | None: ...
86+
def get(self, name: str, failobj: None = None) -> _HeaderT_co | None: ...
8787
@overload
88-
def get(self, name: str, failobj: _T) -> _HeaderT | _T: ...
88+
def get(self, name: str, failobj: _T) -> _HeaderT_co | _T: ...
8989
@overload
90-
def get_all(self, name: str, failobj: None = None) -> list[_HeaderT] | None: ...
90+
def get_all(self, name: str, failobj: None = None) -> list[_HeaderT_co] | None: ...
9191
@overload
92-
def get_all(self, name: str, failobj: _T) -> list[_HeaderT] | _T: ...
92+
def get_all(self, name: str, failobj: _T) -> list[_HeaderT_co] | _T: ...
9393
def add_header(self, _name: str, _value: str, **_params: _ParamsType) -> None: ...
94-
def replace_header(self, _name: str, _value: _HeaderParamT) -> None: ...
94+
def replace_header(self, _name: str, _value: _HeaderParamT_contra) -> None: ...
9595
def get_content_type(self) -> str: ...
9696
def get_content_maintype(self) -> str: ...
9797
def get_content_subtype(self) -> str: ...
@@ -144,18 +144,18 @@ class Message(Generic[_HeaderT, _HeaderParamT]):
144144
replace: bool = False,
145145
) -> None: ...
146146
# The following two methods are undocumented, but a source code comment states that they are public API
147-
def set_raw(self, name: str, value: _HeaderParamT) -> None: ...
148-
def raw_items(self) -> Iterator[tuple[str, _HeaderT]]: ...
147+
def set_raw(self, name: str, value: _HeaderParamT_contra) -> None: ...
148+
def raw_items(self) -> Iterator[tuple[str, _HeaderT_co]]: ...
149149

150-
class MIMEPart(Message[_HeaderRegistryT, _HeaderRegistryParamT]):
150+
class MIMEPart(Message[_HeaderRegistryT_co, _HeaderRegistryParamT_contra]):
151151
def __init__(self, policy: Policy[Any] | None = None) -> None: ...
152-
def get_body(self, preferencelist: Sequence[str] = ("related", "html", "plain")) -> MIMEPart[_HeaderRegistryT] | None: ...
152+
def get_body(self, preferencelist: Sequence[str] = ("related", "html", "plain")) -> MIMEPart[_HeaderRegistryT_co] | None: ...
153153
def attach(self, payload: Self) -> None: ... # type: ignore[override]
154154
# The attachments are created via type(self) in the attach method. It's theoretically
155155
# possible to sneak other attachment types into a MIMEPart instance, but could cause
156156
# cause unforseen consequences.
157157
def iter_attachments(self) -> Iterator[Self]: ...
158-
def iter_parts(self) -> Iterator[MIMEPart[_HeaderRegistryT]]: ...
158+
def iter_parts(self) -> Iterator[MIMEPart[_HeaderRegistryT_co]]: ...
159159
def get_content(self, *args: Any, content_manager: ContentManager | None = None, **kw: Any) -> Any: ...
160160
def set_content(self, *args: Any, content_manager: ContentManager | None = None, **kw: Any) -> None: ...
161161
def make_related(self, boundary: str | None = None) -> None: ...

stdlib/email/mime/text.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
from email._policybase import Policy
12
from email.mime.nonmultipart import MIMENonMultipart
2-
from email.policy import Policy
33

44
__all__ = ["MIMEText"]
55

0 commit comments

Comments
 (0)