8000 `email.message.Message`: Improve payload methods and Explain why `__g… · python/typeshed@c390c70 · GitHub
[go: up one dir, main page]

Skip to content

Commit c390c70

Browse files
AvasamAlexWaygood
andauthored
email.message.Message: Improve payload methods and Explain why __getitem__ isn't typed to return None (#11095)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parent 6c5dc24 commit c390c70

File tree

1 file changed

+47
-10
lines changed

1 file changed

+47
-10
lines changed

stdlib/email/message.pyi

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,27 @@ from email import _ParamsType, _ParamType
33
from email.charset import Charset
44
from email.contentmanager import ContentManager
55
from email.errors import MessageDefect
6+
from email.header import Header
67
from email.policy import Policy
7-
from typing import Any, TypeVar, overload
8-
from typing_extensions import Self, TypeAlias
8+
from typing import Any, Protocol, TypeVar, overload
9+
from typing_extensions import Literal, Self, TypeAlias
910

1011
__all__ = ["Message", "EmailMessage"]
1112

1213
_T = TypeVar("_T")
13-
14-
_PayloadType: TypeAlias = list[Message] | str | bytes | bytearray
14+
_PayloadType: TypeAlias = Message | str
15+
_EncodedPayloadType: TypeAlias = Message | bytes
16+
_MultipartPayloadType: TypeAlias = list[_PayloadType]
1517
_CharsetType: TypeAlias = Charset | str | None
18+
# Type returned by Policy.header_fetch_parse, AnyOf[str | Header]
1619
_HeaderType: TypeAlias = Any
20+
_HeaderTypeParam: TypeAlias = str | Header
21+
22+
class _SupportsEncodeToPayload(Protocol):
23+
def encode(self, __encoding: str) -> _PayloadType | _MultipartPayloadType | _SupportsDecodeToPayload: ...
24+
25+
class _SupportsDecodeToPayload(Protocol):
26+
def decode(self, __encoding: str, __errors: str) -> _PayloadType | _MultipartPayloadType: ...
1727

1828
class Message:
1929
policy: Policy # undocumented
@@ -23,16 +33,43 @@ class Message:
2333
def is_multipart(self) -> bool: ...
2434
def set_unixfrom(self, unixfrom: str) -> None: ...
2535
def get_unixfrom(self) -> str | None: ...
26-
def attach(self, payload: Message) -> None: ...
27-
def get_payload(self, i: int | None = None, decode: bool = False) -> Any: ... # returns _PayloadType | None
28-
def set_payload(self, payload: _PayloadType, charset: _CharsetType = None) -> None: ...
36+
def attach(self, payload: _PayloadType) -> None: ...
37+
# `i: int` without a multipart payload results in an error
38+
# `| Any`: can be None for cleared or unset payload, but annoying to check
39+
@overload # multipart
40+
def get_payload(self, i: int, decode: Literal[True]) -> None: ...
41+
@overload # multipart
42+
def get_payload(self, i: int, decode: Literal[False] = False) -> _PayloadType | Any: ...
43+
@overload # either
44+
def get_payload(self, i: None = None, decode: Literal[False] = False) -> _PayloadType | _MultipartPayloadType | Any: ...
45+
@overload # not multipart
46+
def get_payload(self, i: None = None, *, decode: Literal[True]) -> _EncodedPayloadType | Any: ...
47+
@overload # not multipart, IDEM but w/o kwarg
48+
def get_payload(self, i: None, decode: Literal[True]) -> _EncodedPayloadType | Any: ...
49+
# If `charset=None` and payload supports both `encode` AND `decode`, then an invalid payload could be passed, but this is unlikely
50+
# Not[_SupportsEncodeToPayload]
51+
@overload
52+
def set_payload(
53+
self, payload: _SupportsDecodeToPayload | _PayloadType | _MultipartPayloadType, charset: None = None
54+
) -> None: ...
55+
@overload
56+
def set_payload(
57+
self,
58+
payload: _SupportsEncodeToPayload | _SupportsDecodeToPayload | _PayloadType | _MultipartPayloadType,
59+
charset: Charset | str,
60+
) -> None: ...
2961
def set_charset(self, charset: _CharsetType) -> None: ...
3062
def get_charset(self) -> _CharsetType: ...
3163
def __len__(self) -> int: ...
3264
def __contains__(self, name: str) -> bool: ...
3365
def __iter__(self) -> Iterator[str]: ...
66+
# Same as `get` with `failobj=None`, but with the expectation that it won't return None in most scenarios
67+
# This is important for protocols using __getitem__, like SupportsKeysAndGetItem
68+
# Morally, the return type should be `AnyOf[_HeaderType, None]`,
69+
# which we could spell as `_HeaderType | Any`,
70+
# *but* `_HeaderType` itself is currently an alias to `Any`...
3471
def __getitem__(self, name: str) -> _HeaderType: ...
35-
def __setitem__(self, name: str, val: _HeaderType) -> None: ...
72+
def __setitem__(self, name: str, val: _HeaderTypeParam) -> None: ...
3673
def __delitem__(self, name: str) -> None: ...
3774
def keys(self) -> list[str]: ...
3875
def values(self) -> list[_HeaderType]: ...
@@ -46,7 +83,7 @@ class Message:
4683
@overload
4784
def get_all(self, name: str, failobj: _T) -> list[_HeaderType] | _T: ...
4885
def add_header(self, _name: str, _value: str, **_params: _ParamsType) -> None: ...
49-
def replace_header(self, _name: str, _value: _HeaderType) -> None: ...
86+
def replace_header(self, _name: str, _value: _HeaderTypeParam) -> None: ...
5087
def get_content_type(self) -> str: ...
5188
def get_content_maintype(self) -> str: ...
5289
def get_content_subtype(self) -> str: ...
@@ -100,7 +137,7 @@ class Message:
100137
) -> None: ...
101138
def __init__(self, policy: Policy = ...) -> None: ...
102139
# The following two methods are undocumented, but a source code comment states that they are public API
103-
def set_raw(self, name: str, value: _HeaderType) -> None: ...
140+
def set_raw(self, name: str, value: _HeaderTypeParam) -> None: ...
104141
def raw_items(self) -> Iterator[tuple[str, _HeaderType]]: ...
105142

106143
class MIMEPart(Message):

0 commit comments

Comments
 (0)
0