Skip to content

Make email.policy classes generic #10769

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions stdlib/email/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from _typeshed import Incomplete
from collections.abc import Callable
from email.message import Message
from email.policy import Policy
Expand All @@ -8,10 +9,16 @@ from typing_extensions import TypeAlias
_ParamType: TypeAlias = str | tuple[str | None, str | None, str] # noqa: Y047
_ParamsType: TypeAlias = str | None | tuple[str, str | None, str] # noqa: Y047

def message_from_string(s: str, _class: Callable[[], Message] = ..., *, policy: Policy = ...) -> Message: ...
def message_from_bytes(s: bytes | bytearray, _class: Callable[[], Message] = ..., *, policy: Policy = ...) -> Message: ...
def message_from_file(fp: IO[str], _class: Callable[[], Message] = ..., *, policy: Policy = ...) -> Message: ...
def message_from_binary_file(fp: IO[bytes], _class: Callable[[], Message] = ..., *, policy: Policy = ...) -> Message: ...
# TODO: These functions should be generic over the message with appropriate
# overloads. (#10762)
def message_from_string(s: str, _class: Callable[[], Message] = ..., *, policy: Policy[Incomplete] = ...) -> Message: ...
def message_from_bytes(
s: bytes | bytearray, _class: Callable[[], Message] = ..., *, policy: Policy[Incomplete] = ...
) -> Message: ...
def message_from_file(fp: IO[str], _class: Callable[[], Message] = ..., *, policy: Policy[Incomplete] = ...) -> Message: ...
def message_from_binary_file(
fp: IO[bytes], _class: Callable[[], Message] = ..., *, policy: Policy[Incomplete] = ...
) -> Message: ...

# Names in __all__ with no definition:
# base64mime
Expand Down
4 changes: 2 additions & 2 deletions stdlib/email/_header_value_parser.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class TokenList(list[TokenList | Terminal]):
def as_ew_allowed(self) -> bool: ...
@property
def comments(self) -> list[str]: ...
def fold(self, *, policy: Policy) -> str: ...
def fold(self, *, policy: Policy[Any]) -> str: ...
def pprint(self, indent: str = "") -> None: ...
def ppstr(self, indent: str = "") -> str: ...

Expand Down Expand Up @@ -297,7 +297,7 @@ class HeaderLabel(TokenList):
class MsgID(TokenList):
token_type: str
as_ew_allowed: bool
def fold(self, policy: Policy) -> str: ...
def fold(self, policy: Policy[Any]) -> str: ...

class MessageID(MsgID):
token_type: str
Expand Down
40 changes: 27 additions & 13 deletions stdlib/email/_policybase.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,46 @@
from email.errors import MessageDefect
from email.header import Header
from email.message import Message
from typing import Any
from typing import Any, Generic, TypeVar
from typing_extensions import Self

_MessageT = TypeVar("_MessageT", bound=Message)

class _PolicyBase:
def __add__(self, other: Any) -> Self: ...
def clone(self, **kw: Any) -> Self: ...

class Policy(_PolicyBase, metaclass=ABCMeta):
class Policy(_PolicyBase, Generic[_MessageT], metaclass=ABCMeta):
max_line_length: int | None
linesep: str
cte_type: str
raise_on_defect: bool
mangle_from_: bool
message_factory: Callable[[Policy], Message] | None
message_factory: Callable[[[_MessageT]], [_MessageT]] | None

Check failure on line 21 in stdlib/email/_policybase.pyi

View workflow job for this annotation

GitHub Actions / Test typeshed with pyright (Linux, 3.11)

List expression not allowed in type annotation   Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportInvalidTypeForm)

Check failure on line 21 in stdlib/email/_policybase.pyi

View workflow job for this annotation

GitHub Actions / Test typeshed with pyright (Linux, 3.11)

Expected type expression but received "list[TypeVar]" (reportGeneralTypeIssues)

Check failure on line 21 in stdlib/email/_policybase.pyi

View workflow job for this annotation

GitHub Actions / Test typeshed with pyright (Linux, 3.11)

List expression not allowed for this type argument
@overload

Check failure on line 22 in stdlib/email/_policybase.pyi

View workflow job for this annotation

GitHub Actions / Test typeshed with pyright (Linux, 3.11)

"overload" is not defined (reportUndefinedVariable)

Check failure on line 22 in stdlib/email/_policybase.pyi

View workflow job for this annotation

GitHub Actions / Test typeshed with pyright (Linux, 3.11)

Untyped function decorator obscures type of function; ignoring decorator (reportUntypedFunctionDecorator)
def __init__(

Check failure on line 23 in stdlib/email/_policybase.pyi

View workflow job for this annotation

GitHub Actions / Test typeshed with pyright (Linux, 3.11)

Method declaration "__init__" is obscured by a declaration of the same name (reportRedeclaration)
self: Policy[Message[str, str]],
*,
max_line_length: int | None = ...,
linesep: str = ...,
cte_type: str = ...,
raise_on_defect: bool = ...,
mangle_from_: bool = ...,
message_factory: None = None,
) -> None: ...
@overload

Check failure on line 33 in stdlib/email/_policybase.pyi

View workflow job for this annotation

GitHub Actions / Test typeshed with pyright (Linux, 3.11)

"overload" is not defined (reportUndefinedVariable)

Check failure on line 33 in stdlib/email/_policybase.pyi

View workflow job for this annotation

GitHub Actions / Test typeshed with pyright (Linux, 3.11)

Untyped function decorator obscures type of function; ignoring decorator (reportUntypedFunctionDecorator)
def __init__(
self,
*,
max_line_length: int | None = 78,
linesep: str = "\n",
cte_type: str = "8bit",
raise_on_defect: bool = False,
mangle_from_: bool = False,
message_factory: Callable[[Policy], Message] | None = None,
max_line_length: int | None = ...,
linesep: str = ...,
cte_type: str = ...,
raise_on_defect: bool = ...,
mangle_from_: bool = ...,
message_factory: Callable[[Policy[_MessageT]], _MessageT],
) -> None: ...
def handle_defect(self, obj: Message, defect: MessageDefect) -> None: ...
def register_defect(self, obj: Message, defect: MessageDefect) -> None: ...
def handle_defect(self, obj: _MessageT, defect: MessageDefect) -> None: ...
def register_defect(self, obj: _MessageT, defect: MessageDefect) -> None: ...
def header_max_count(self, name: str) -> int | None: ...
@abstractmethod
def header_source_parse(self, sourcelines: list[str]) -> tuple[str, str]: ...
Expand All @@ -41,11 +55,11 @@
@abstractmethod
def fold_binary(self, name: str, value: str) -> bytes: ...

class Compat32(Policy):
class Compat32(Policy[_MessageT]):
def header_source_parse(self, sourcelines: list[str]) -> tuple[str, str]: ...
def header_store_parse(self, name: str, value: str) -> tuple[str, str]: ...
def header_fetch_parse(self, name: str, value: str) -> str | Header: ... # type: ignore[override]
def fold(self, name: str, value: str) -> str: ...
def fold_binary(self, name: str, value: str) -> bytes: ...

compat32: Compat32
compat32: Compat32[Message]
10 changes: 5 additions & 5 deletions stdlib/email/feedparser.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ _MessageT = TypeVar("_MessageT", bound=Message)

class FeedParser(Generic[_MessageT]):
@overload
def __init__(self: FeedParser[Message], _factory: None = None, *, policy: Policy = ...) -> None: ...
def __init__(self: FeedParser[Message], _factory: None = None, *, policy: Policy[Message] = ...) -> None: ...
@overload
def __init__(self, _factory: Callable[[], _MessageT], *, policy: Policy = ...) -> None: ...
def __init__(self, _factory: Callable[[], _MessageT], *, policy: Policy[_MessageT] = ...) -> None: ...
def feed(self, data: str) -> None: ...
def close(self) -> _MessageT: ...

class BytesFeedParser(FeedParser[_MessageT]):
@overload
def __init__(self: BytesFeedParser[Message], _factory: None = None, *, policy: Policy = ...) -> None: ...
def __init__(self: BytesFeedParser[Message], _factory: None = None, *, policy: Policy[Message] = ...) -> None: ...
@overload
def __init__(self, _factory: Callable[[], _MessageT], *, policy: Policy = ...) -> None: ...
def feed(self, data: bytes | bytearray) -> None: ... # type: ignore[override]
def __init__(self, _factory: Callable[[], _MessageT], *, policy: Policy[_MessageT] = ...) -> None: ...
def feed(self, data: bytes | bytearray) -> None: ...
7 changes: 4 additions & 3 deletions stdlib/email/generator.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from _typeshed import SupportsWrite
from email.message import Message
from email.policy import Policy
from typing import Any
from typing_extensions import Self

__all__ = ["Generator", "DecodedGenerator", "BytesGenerator"]
Expand All @@ -14,7 +15,7 @@ class Generator:
mangle_from_: bool | None = None,
maxheaderlen: int | None = None,
*,
policy: Policy | None = None,
policy: Policy[Any] | None = None,
) -> None: ...
def flatten(self, msg: Message, unixfrom: bool = False, linesep: str | None = None) -> None: ...

Expand All @@ -25,7 +26,7 @@ class BytesGenerator(Generator):
mangle_from_: bool | None = None,
maxheaderlen: int | None = None,
*,
policy: Policy | None = None,
policy: Policy[Any] | None = None,
) -> None: ...

class DecodedGenerator(Generator):
Expand All @@ -36,5 +37,5 @@ class DecodedGenerator(Generator):
maxheaderlen: int | None = None,
fmt: str | None = None,
*,
policy: Policy | None = None,
policy: Policy[Any] | None = None,
) -> None: ...
2 changes: 1 addition & 1 deletion stdlib/email/headerregistry.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class BaseHeader(str):
def defects(self) -> tuple[MessageDefect, ...]: ...
def __new__(cls, name: str, value: Any) -> Self: ...
def init(self, name: str, *, parse_tree: TokenList, defects: Iterable[MessageDefect]) -> None: ...
def fold(self, *, policy: Policy) -> str: ...
def fold(self, *, policy: Policy[Any]) -> str: ...

class UnstructuredHeader:
max_count: ClassVar[Literal[1] | None]
Expand Down
12 changes: 6 additions & 6 deletions stdlib/email/message.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class _SupportsDecodeToPayload(Protocol):
def decode(self, encoding: str, errors: str, /) -> _PayloadType | _MultipartPayloadType: ...

class Message(Generic[_HeaderT, _HeaderParamT]):
policy: Policy # undocumented
policy: Policy[Any] # undocumented
preamble: str | None
epilogue: str | None
defects: list[MessageDefect]
Expand Down Expand Up @@ -125,8 +125,8 @@ class Message(Generic[_HeaderT, _HeaderParamT]):
def get_charsets(self, failobj: _T) -> list[str | _T]: ...
def walk(self) -> Generator[Self, None, None]: ...
def get_content_disposition(self) -> str | None: ...
def as_string(self, unixfrom: bool = False, maxheaderlen: int = 0, policy: Policy | None = None) -> str: ...
def as_bytes(self, unixfrom: bool = False, policy: Policy | None = None) -> bytes: ...
def as_string(self, unixfrom: bool = False, maxheaderlen: int = 0, policy: Policy[Any] | None = None) -> str: ...
def as_bytes(self, unixfrom: bool = False, policy: Policy[Any] | None = None) -> bytes: ...
def __bytes__(self) -> bytes: ...
def set_param(
self,
Expand All @@ -138,13 +138,13 @@ class Message(Generic[_HeaderT, _HeaderParamT]):
language: str = "",
replace: bool = False,
) -> None: ...
def __init__(self, policy: Policy = ...) -> None: ...
def __init__(self, policy: Policy[Any] = ...) -> 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: _HeaderParamT) -> None: ...
def raw_items(self) -> Iterator[tuple[str, _HeaderT]]: ...

class MIMEPart(Message[_HeaderRegistryT, _HeaderRegistryParamT]):
def __init__(self, policy: Policy | None = None) -> None: ...
def __init__(self, policy: Policy[Any] | None = None) -> None: ...
def get_body(self, preferencelist: Sequence[str] = ("related", "html", "plain")) -> MIMEPart[_HeaderRegistryT] | None: ...
def iter_attachments(self) -> Iterator[MIMEPart[_HeaderRegistryT]]: ...
def iter_parts(self) -> Iterator[MIMEPart[_HeaderRegistryT]]: ...
Expand All @@ -158,7 +158,7 @@ class MIMEPart(Message[_HeaderRegistryT, _HeaderRegistryParamT]):
def add_attachment(self, *args: Any, content_manager: ContentManager | None = ..., **kw: Any) -> None: ...
def clear(self) -> None: ...
def clear_content(self) -> None: ...
def as_string(self, unixfrom: bool = False, maxheaderlen: int | None = None, policy: Policy | None = None) -> str: ...
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): ...
3 changes: 2 additions & 1 deletion stdlib/email/mime/application.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ from collections.abc import Callable
from email import _ParamsType
from email.mime.nonmultipart import MIMENonMultipart
from email.policy import Policy
from typing import Any

__all__ = ["MIMEApplication"]

Expand All @@ -12,6 +13,6 @@ class MIMEApplication(MIMENonMultipart):
_subtype: str = "octet-stream",
_encoder: Callable[[MIMEApplication], object] = ...,
*,
policy: Policy | None = None,
policy: Policy[Any] | None = None,
**_params: _ParamsType,
) -> None: ...
3 changes: 2 additions & 1 deletion stdlib/email/mime/audio.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ from collections.abc import Callable
from email import _ParamsType
from email.mime.nonmultipart import MIMENonMultipart
from email.policy import Policy
from typing import Any

__all__ = ["MIMEAudio"]

Expand All @@ -12,6 +13,6 @@ class MIMEAudio(MIMENonMultipart):
_subtype: str | None = None,
_encoder: Callable[[MIMEAudio], object] = ...,
*,
policy: Policy | None = None,
policy: Policy[Any] | None = None,
**_params: _ParamsType,
) -> None: ...
3 changes: 2 additions & 1 deletion stdlib/email/mime/base.pyi
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import email.message
from email import _ParamsType
from email.policy import Policy
from typing import Any

__all__ = ["MIMEBase"]

class MIMEBase(email.message.Message):
def __init__(self, _maintype: str, _subtype: str, *, policy: Policy | None = None, **_params: _ParamsType) -> None: ...
def __init__(self, _maintype: str, _subtype: str, *, policy: Policy[Any] | None = None, **_params: _ParamsType) -> None: ...
3 changes: 2 additions & 1 deletion stdlib/email/mime/image.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ from collections.abc import Callable
from email import _ParamsType
from email.mime.nonmultipart import MIMENonMultipart
from email.policy import Policy
from typing import Any

__all__ = ["MIMEImage"]

Expand All @@ -12,6 +13,6 @@ class MIMEImage(MIMENonMultipart):
_subtype: str | None = None,
_encoder: Callable[[MIMEImage], object] = ...,
*,
policy: Policy | None = None,
policy: Policy[Any] | None = None,
**_params: _ParamsType,
) -> None: ...
3 changes: 2 additions & 1 deletion stdlib/email/mime/message.pyi
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from email.message import Message
from email.mime.nonmultipart import MIMENonMultipart
from email.policy import Policy
from typing import Any

__all__ = ["MIMEMessage"]

class MIMEMessage(MIMENonMultipart):
def __init__(self, _msg: Message, _subtype: str = "rfc822", *, policy: Policy | None = None) -> None: ...
def __init__(self, _msg: Message, _subtype: str = "rfc822", *, policy: Policy[Any] | None = None) -> None: ...
3 changes: 2 additions & 1 deletion stdlib/email/mime/multipart.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ from email import _ParamsType
from email.message import Message
from email.mime.base import MIMEBase
from email.policy import Policy
from typing import Any

__all__ = ["MIMEMultipart"]

Expand All @@ -13,6 +14,6 @@ class MIMEMultipart(MIMEBase):
boundary: str | None = None,
_subparts: Sequence[Message] | None = None,
*,
policy: Policy | None = None,
policy: Policy[Any] | None = None,
**_params: _ParamsType,
) -> None: ...
3 changes: 2 additions & 1 deletion stdlib/email/mime/text.pyi
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from email.mime.nonmultipart import MIMENonMultipart
from email.policy import Policy
from typing import Any

__all__ = ["MIMEText"]

class MIMEText(MIMENonMultipart):
def __init__(
self, _text: str, _subtype: str = "plain", _charset: str | None = None, *, policy: Policy | None = None
self, _text: str, _subtype: str = "plain", _charset: str | None = None, *, policy: Policy[Any] | None = None
) -> None: ...
10 changes: 6 additions & 4 deletions stdlib/email/parser.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ _MessageT = TypeVar("_MessageT", bound=Message, default=Message)

class Parser(Generic[_MessageT]):
@overload
def __init__(self: Parser[Message[str, str]], _class: None = None, *, policy: Policy = ...) -> None: ...
def __init__(self: Parser[Message[str, str]], _class: None = None, *, policy: Policy[Message[str, str]] = ...) -> None: ...
@overload
def __init__(self, _class: Callable[[], _MessageT], *, policy: Policy = ...) -> None: ...
def __init__(self, _class: Callable[[], _MessageT], *, policy: Policy[_MessageT] = ...) -> None: ...
def parse(self, fp: SupportsRead[str], headersonly: bool = False) -> _MessageT: ...
def parsestr(self, text: str, headersonly: bool = False) -> _MessageT: ...

Expand All @@ -25,9 +25,11 @@ class HeaderParser(Parser[_MessageT]):
class BytesParser(Generic[_MessageT]):
parser: Parser[_MessageT]
@overload
def __init__(self: BytesParser[Message[str, str]], _class: None = None, *, policy: Policy = ...) -> None: ...
def __init__(
self: BytesParser[Message[str, str]], _class: None = None, *, policy: Policy[Message[str, str]] = ...
) -> None: ...
@overload
def __init__(self, _class: Callable[[], _MessageT], *, policy: Policy = ...) -> None: ...
def __init__(self, _class: Callable[[], _MessageT], *, policy: Policy[_MessageT] = ...) -> None: ...
def parse(self, fp: _WrappedBuffer, headersonly: bool = False) -> _MessageT: ...
def parsebytes(self, text: bytes | bytearray, headersonly: bool = False) -> _MessageT: ...

Expand Down
Loading
Loading