Skip to content

Commit 92144d2

Browse files
committed
Various improvements for the mailbox stubs
* Use `_typeshed.SupportsItems` instead of custom `_HasItems` protocol. * Use a custom covariant protocol for mailbox messages. * Add `Mailbox._dump_message`. * Return a protocol from abstract method `Mailbox.get_file()`. * Return concrete type `BytesIO` instead of `typing.IO` from `Babyl.get_file()`. * Use our custom protocol instead of semi-protocol `typing.IO` in the remaining cases. Closes: #14935
1 parent 947ec49 commit 92144d2

File tree

2 files changed

+84
-40
lines changed

2 files changed

+84
-40
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import mailbox
2+
3+
4+
def mbox1() -> mailbox.Mailbox:
5+
return mailbox.mbox("")
6+
7+
def mbox2() -> mailbox.Mailbox[mailbox.mboxMessage]:
8+
return mailbox.mbox("")
9+
10+
def mbox3() -> mailbox.Mailbox[mailbox.Message]:
11+
return mailbox.mbox("")

stdlib/mailbox.pyi

Lines changed: 73 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import email.message
22
import io
33
import sys
4-
from _typeshed import StrPath, SupportsNoArgReadline, SupportsRead
4+
from _typeshed import StrPath, SupportsItems, SupportsNoArgReadline, SupportsRead, SupportsWrite, Unused
55
from abc import ABCMeta, abstractmethod
66
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
7-
from email._policybase import _MessageT
87
from types import GenericAlias, TracebackType
9-
from typing import IO, Any, AnyStr, Generic, Literal, Protocol, TypeVar, overload, type_check_only
8+
from typing import Any, AnyStr, Generic, Literal, Protocol, TypeVar, overload, type_check_only
109
from typing_extensions import Self, TypeAlias
1110

1211
__all__ = [
@@ -30,27 +29,50 @@ __all__ = [
3029
]
3130

3231
_T = TypeVar("_T")
32+
_AnyStr = TypeVar("_AnyStr", str, bytes, default=bytes)
3333

3434
@type_check_only
3535
class _SupportsReadAndReadline(SupportsRead[bytes], SupportsNoArgReadline[bytes], Protocol): ...
3636

37+
# As opposed to _MessageT_co in email._policybase, this type is bound to
38+
# mailbox.Message instead of email.message.Message.
39+
_MessageT_co = TypeVar("_MessageT_co", bound=Message, default=Message, covariant=True)
40+
3741
_MessageData: TypeAlias = email.message.Message | bytes | str | io.StringIO | _SupportsReadAndReadline
3842

3943
@type_check_only
4044
class _HasIteritems(Protocol):
4145
def iteritems(self) -> Iterator[tuple[str, _MessageData]]: ...
4246

43-
@type_check_only
44-
class _HasItems(Protocol):
45-
def items(self) -> Iterator[tuple[str, _MessageData]]: ...
46-
4747
linesep: bytes
4848

49-
class Mailbox(Generic[_MessageT]):
49+
# Common interface for get_file() return types.
50+
@type_check_only
51+
class _GetFileReturn(Protocol[_AnyStr]):
52+
def __iter__(self) -> Iterator[_AnyStr]: ...
53+
def __enter__(self) -> Self: ...
54+
def __exit__(
55+
self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, /
56+
) -> bool | None: ...
57+
def read(self, size: int | None = None, /) -> _AnyStr: ...
58+
def read1(self, size: int | None = None, /) -> _AnyStr: ...
59+
def readline(self, size: int | None = None, /) -> _AnyStr: ...
60+
def readlines(self, sizehint: int | None = None, /) -> list[_AnyStr]: ...
61+
def tell(self) -> int: ...
62+
def seek(self, offset: int, whence: int = 0, /) -> object: ...
63+
def close(self) -> object: ...
64+
def readable(self) -> bool: ...
65+
def writable(self) -> bool: ...
66+
def seekable(self) -> bool: ...
67+
def flush(self) -> object: ...
68+
@property
69+
def closed(self) -> bool: ...
70+
71+
class Mailbox(Generic[_MessageT_co]):
5072
_path: str # undocumented
51-
_factory: Callable[[IO[Any]], _MessageT] | None # undocumented
73+
_factory: Callable[[_GetFileReturn], _MessageT_co] | None # undocumented
5274
@overload
53-
def __init__(self, path: StrPath, factory: Callable[[IO[Any]], _MessageT], create: bool = True) -> None: ...
75+
def __init__(self, path: StrPath, factory: Callable[[_GetFileReturn], _MessageT_co], create: bool = True) -> None: ...
5476
@overload
5577
def __init__(self, path: StrPath, factory: None = None, create: bool = True) -> None: ...
5678
@abstractmethod
@@ -62,37 +84,38 @@ class Mailbox(Generic[_MessageT]):
6284
@abstractmethod
6385
def __setitem__(self, key: str, message: _MessageData) -> None: ...
6486
@overload
65-
def get(self, key: str, default: None = None) -> _MessageT | None: ...
87+
def get(self, key: str, default: None = None) -> _MessageT_co | None: ...
6688
@overload
67-
def get(self, key: str, default: _T) -> _MessageT | _T: ...
68-
def __getitem__(self, key: str) -> _MessageT: ...
89+
def get(self, key: str, default: _T) -> _MessageT_co | _T: ...
90+
def __getitem__(self, key: str) -> _MessageT_co: ...
6991
@abstractmethod
70-
def get_message(self, key: str) -> _MessageT: ...
92+
def get_message(self, key: str) -> _MessageT_co: ...
7193
def get_string(self, key: str) -> str: ...
7294
@abstractmethod
7395
def get_bytes(self, key: str) -> bytes: ...
74-
# As '_ProxyFile' doesn't implement the full IO spec, and BytesIO is incompatible with it, get_file return is Any here
7596
@abstractmethod
76-
def get_file(self, key: str) -> Any: ...
97+
def get_file(self, key: str) -> _GetFileReturn: ...
7798
@abstractmethod
7899
def iterkeys(self) -> Iterator[str]: ...
79100
def keys(self) -> list[str]: ...
80-
def itervalues(self) -> Iterator[_MessageT]: ...
81-
def __iter__(self) -> Iterator[_MessageT]: ...
82-
def values(self) -> list[_MessageT]: ...
83-
def iteritems(self) -> Iterator[tuple[str, _MessageT]]: ...
84-
def items(self) -> list[tuple[str, _MessageT]]: ...
101+
def itervalues(self) -> Iterator[_MessageT_co]: ...
102+
def __iter__(self) -> Iterator[_MessageT_co]: ...
103+
def values(self) -> list[_MessageT_co]: ...
104+
def iteritems(self) -> Iterator[tuple[str, _MessageT_co]]: ...
105+
def items(self) -> list[tuple[str, _MessageT_co]]: ...
85106
@abstractmethod
86107
def __contains__(self, key: str) -> bool: ...
87108
@abstractmethod
88109
def __len__(self) -> int: ...
89110
def clear(self) -> None: ...
90111
@overload
91-
def pop(self, key: str, default: None = None) -> _MessageT | None: ...
112+
def pop(self, key: str, default: None = None) -> _MessageT_co | None: ...
92113
@overload
93-
def pop(self, key: str, default: _T) -> _MessageT | _T: ...
94-
def popitem(self) -> tuple[str, _MessageT]: ...
95-
def update(self, arg: _HasIteritems | _HasItems | Iterable[tuple[str, _MessageData]] | None = None) -> None: ...
114+
def pop(self, key: str, default: _T) -> _MessageT_co | _T: ...
115+
def popitem(self) -> tuple[str, _MessageT_co]: ...
116+
def update(
117+
self, arg: _HasIteritems | SupportsItems[str, _MessageData] | Iterable[tuple[str, _MessageData]] | None = None
118+
) -> None: ...
96119
@abstractmethod
97120
def flush(self) -> None: ...
98121
@abstractmethod
@@ -101,16 +124,18 @@ class Mailbox(Generic[_MessageT]):
101124
def unlock(self) -> None: ...
102125
@abstractmethod
103126
def close(self) -> None: ...
127+
# Undocumented, called by subclasses to parse added messages.
128+
def _dump_message(self, message: _MessageData, target: SupportsWrite[bytes], mangle_from_: bool = False) -> None: ...
104129
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
105130

106131
class Maildir(Mailbox[MaildirMessage]):
107132
colon: str
108133
def __init__(
109-
self, dirname: StrPath, factory: Callable[[IO[Any]], MaildirMessage] | None = None, create: bool = True
134+
self, dirname: StrPath, factory: Callable[[_GetFileReturn], MaildirMessage] | None = None, create: bool = True
110135
) -> None: ...
111-
def add(self, message: _MessageData) -> str: ...
136+
def add(self, message: _MessageData | MaildirMessage) -> str: ...
112137
def remove(self, key: str) -> None: ...
113-
def __setitem__(self, key: str, message: _MessageData) -> None: ...
138+
def __setitem__(self, key: str, message: _MessageData | MaildirMessage) -> None: ...
114139
def get_message(self, key: str) -> MaildirMessage: ...
115140
def get_bytes(self, key: str) -> bytes: ...
116141
def get_file(self, key: str) -> _ProxyFile[bytes]: ...
@@ -136,7 +161,7 @@ class Maildir(Mailbox[MaildirMessage]):
136161
def clean(self) -> None: ...
137162
def next(self) -> str | None: ...
138163

139-
class _singlefileMailbox(Mailbox[_MessageT], metaclass=ABCMeta):
164+
class _singlefileMailbox(Mailbox[_MessageT_co], metaclass=ABCMeta):
140165
def add(self, message: _MessageData) -> str: ...
141166
def remove(self, key: str) -> None: ...
142167
def __setitem__(self, key: str, message: _MessageData) -> None: ...
@@ -148,20 +173,26 @@ class _singlefileMailbox(Mailbox[_MessageT], metaclass=ABCMeta):
148173
def flush(self) -> None: ...
149174
def close(self) -> None: ...
150175

151-
class _mboxMMDF(_singlefileMailbox[_MessageT]):
152-
def get_message(self, key: str) -> _MessageT: ...
176+
class _mboxMMDF(_singlefileMailbox[_MessageT_co]):
177+
def get_message(self, key: str) -> _MessageT_co: ...
153178
def get_file(self, key: str, from_: bool = False) -> _PartialFile[bytes]: ...
154179
def get_bytes(self, key: str, from_: bool = False) -> bytes: ...
155180
def get_string(self, key: str, from_: bool = False) -> str: ...
156181

157182
class mbox(_mboxMMDF[mboxMessage]):
158-
def __init__(self, path: StrPath, factory: Callable[[IO[Any]], mboxMessage] | None = None, create: bool = True) -> None: ...
183+
def __init__(
184+
self, path: StrPath, factory: Callable[[_GetFileReturn], mboxMessage] | None = None, create: bool = True
185+
) -> None: ...
159186

160187
class MMDF(_mboxMMDF[MMDFMessage]):
161-
def __init__(self, path: StrPath, factory: Callable[[IO[Any]], MMDFMessage] | None = None, create: bool = True) -> None: ...
188+
def __init__(
189+
self, path: StrPath, factory: Callable[[_GetFileReturn], MMDFMessage] | None = None, create: bool = True
190+
) -> None: ...
162191

163192
class MH(Mailbox[MHMessage]):
164-
def __init__(self, path: StrPath, factory: Callable[[IO[Any]], MHMessage] | None = None, create: bool = True) -> None: ...
193+
def __init__(
194+
self, path: StrPath, factory: Callable[[_GetFileReturn], MHMessage] | None = None, create: bool = True
195+
) -> None: ...
165196
def add(self, message: _MessageData) -> str: ...
166197
def remove(self, key: str) -> None: ...
167198
def __setitem__(self, key: str, message: _MessageData) -> None: ...
@@ -184,13 +215,15 @@ class MH(Mailbox[MHMessage]):
184215
def pack(self) -> None: ...
185216

186217
class Babyl(_singlefileMailbox[BabylMessage]):
187-
def __init__(self, path: StrPath, factory: Callable[[IO[Any]], BabylMessage] | None = None, create: bool = True) -> None: ...
218+
def __init__(
219+
self, path: StrPath, factory: Callable[[_GetFileReturn], BabylMessage] | None = None, create: bool = True
220+
) -> None: ...
188221
def get_message(self, key: str) -> BabylMessage: ...
189222
def get_bytes(self, key: str) -> bytes: ...
190-
def get_file(self, key: str) -> IO[bytes]: ...
223+
def get_file(self, key: str) -> io.BytesIO: ...
191224
def get_labels(self) -> list[str]: ...
192225

193-
class Message(email.message.Message):
226+
class Message(email.message.Message[str, str]):
194227
def __init__(self, message: _MessageData | None = None) -> None: ...
195228

196229
class MaildirMessage(Message):
@@ -233,7 +266,7 @@ class BabylMessage(Message):
233266
class MMDFMessage(_mboxMMDFMessage): ...
234267

235268
class _ProxyFile(Generic[AnyStr]):
236-
def __init__(self, f: IO[AnyStr], pos: int | None = None) -> None: ...
269+
def __init__(self, f: _GetFileReturn[AnyStr], pos: int | None = None) -> None: ...
237270
def read(self, size: int | None = None) -> AnyStr: ...
238271
def read1(self, size: int | None = None) -> AnyStr: ...
239272
def readline(self, size: int | None = None) -> AnyStr: ...
@@ -243,7 +276,7 @@ class _ProxyFile(Generic[AnyStr]):
243276
def seek(self, offset: int, whence: int = 0) -> None: ...
244277
def close(self) -> None: ...
245278
def __enter__(self) -> Self: ...
246-
def __exit__(self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ...
279+
def __exit__(self, *exc: Unused) -> None: ...
247280
def readable(self) -> bool: ...
248281
def writable(self) -> bool: ...
249282
def seekable(self) -> bool: ...
@@ -253,7 +286,7 @@ class _ProxyFile(Generic[AnyStr]):
253286
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
254287

255288
class _PartialFile(_ProxyFile[AnyStr]):
256-
def __init__(self, f: IO[AnyStr], start: int | None = None, stop: int | None = None) -> None: ...
289+
def __init__(self, f: _GetFileReturn[AnyStr], start: int | None = None, stop: int | None = None) -> None: ...
257290

258291
class Error(Exception): ...
259292
class NoSuchMailboxError(Error): ...

0 commit comments

Comments
 (0)