diff --git a/stdlib/email/message.pyi b/stdlib/email/message.pyi index 18852f4d3bb2..71c8da3a6a5a 100644 --- a/stdlib/email/message.pyi +++ b/stdlib/email/message.pyi @@ -3,17 +3,27 @@ from email import _ParamsType, _ParamType from email.charset import Charset from email.contentmanager import ContentManager from email.errors import MessageDefect +from email.header import Header from email.policy import Policy -from typing import Any, TypeVar, overload -from typing_extensions import Self, TypeAlias +from typing import Any, Protocol, TypeVar, overload +from typing_extensions import Literal, Self, TypeAlias __all__ = ["Message", "EmailMessage"] _T = TypeVar("_T") - -_PayloadType: TypeAlias = list[Message] | str | bytes | bytearray +_PayloadType: TypeAlias = Message | str +_EncodedPayloadType: TypeAlias = Message | bytes +_MultipartPayloadType: TypeAlias = list[_PayloadType] _CharsetType: TypeAlias = Charset | str | None +# Type returned by Policy.header_fetch_parse, AnyOf[str | Header] _HeaderType: TypeAlias = Any +_HeaderTypeParam: TypeAlias = str | Header + +class _SupportsEncodeToPayload(Protocol): + def encode(self, __encoding: str) -> _PayloadType | _MultipartPayloadType | _SupportsDecodeToPayload: ... + +class _SupportsDecodeToPayload(Protocol): + def decode(self, __encoding: str, __errors: str) -> _PayloadType | _MultipartPayloadType: ... class Message: policy: Policy # undocumented @@ -23,16 +33,43 @@ class Message: def is_multipart(self) -> bool: ... def set_unixfrom(self, unixfrom: str) -> None: ... def get_unixfrom(self) -> str | None: ... - def attach(self, payload: Message) -> None: ... - def get_payload(self, i: int | None = None, decode: bool = False) -> Any: ... # returns _PayloadType | None - def set_payload(self, payload: _PayloadType, charset: _CharsetType = None) -> None: ... + def attach(self, payload: _PayloadType) -> None: ... + # `i: int` without a multipart payload results in an error + # `| Any`: can be None for cleared or unset payload, but annoying to check + @overload # multipart + def get_payload(self, i: int, decode: Literal[True]) -> None: ... + @overload # multipart + def get_payload(self, i: int, decode: Literal[False] = False) -> _PayloadType | Any: ... + @overload # either + def get_payload(self, i: None = None, decode: Literal[False] = False) -> _PayloadType | _MultipartPayloadType | Any: ... + @overload # not multipart + def get_payload(self, i: None = None, *, decode: Literal[True]) -> _EncodedPayloadType | Any: ... + @overload # not multipart, IDEM but w/o kwarg + def get_payload(self, i: None, decode: Literal[True]) -> _EncodedPayloadType | Any: ... + # If `charset=None` and payload supports both `encode` AND `decode`, then an invalid payload could be passed, but this is unlikely + # Not[_SupportsEncodeToPayload] + @overload + def set_payload( + self, payload: _SupportsDecodeToPayload | _PayloadType | _MultipartPayloadType, charset: None = None + ) -> None: ... + @overload + def set_payload( + self, + payload: _SupportsEncodeToPayload | _SupportsDecodeToPayload | _PayloadType | _MultipartPayloadType, + charset: Charset | str, + ) -> None: ... def set_charset(self, charset: _CharsetType) -> None: ... def get_charset(self) -> _CharsetType: ... def __len__(self) -> int: ... def __contains__(self, name: str) -> bool: ... def __iter__(self) -> Iterator[str]: ... + # Same as `get` with `failobj=None`, but with the expectation that it won't return None in most scenarios + # This is important for protocols using __getitem__, like SupportsKeysAndGetItem + # Morally, the return type should be `AnyOf[_HeaderType, None]`, + # which we could spell as `_HeaderType | Any`, + # *but* `_HeaderType` itself is currently an alias to `Any`... def __getitem__(self, name: str) -> _HeaderType: ... - def __setitem__(self, name: str, val: _HeaderType) -> None: ... + def __setitem__(self, name: str, val: _HeaderTypeParam) -> None: ... def __delitem__(self, name: str) -> None: ... def keys(self) -> list[str]: ... def values(self) -> list[_HeaderType]: ... @@ -46,7 +83,7 @@ class Message: @overload def get_all(self, name: str, failobj: _T) -> list[_HeaderType] | _T: ... def add_header(self, _name: str, _value: str, **_params: _ParamsType) -> None: ... - def replace_header(self, _name: str, _value: _HeaderType) -> None: ... + def replace_header(self, _name: str, _value: _HeaderTypeParam) -> None: ... def get_content_type(self) -> str: ... def get_content_maintype(self) -> str: ... def get_content_subtype(self) -> str: ... @@ -100,7 +137,7 @@ class Message: ) -> None: ... def __init__(self, policy: Policy = ...) -> None: ... # The following two methods are undocumented, but a source code comment states that they are public API - def set_raw(self, name: str, value: _HeaderType) -> None: ... + def set_raw(self, name: str, value: _HeaderTypeParam) -> None: ... def raw_items(self) -> Iterator[tuple[str, _HeaderType]]: ... class MIMEPart(Message):