Source code for pymap.mime.parsed


from __future__ import annotations

from collections.abc import Iterable, Iterator, Mapping, Sequence
from email.headerregistry import HeaderRegistry, BaseHeader, \
    UnstructuredHeader, DateHeader, AddressHeader, SingleAddressHeader, \
    ContentDispositionHeader, ContentTransferEncodingHeader, ContentTypeHeader
from email.policy import SMTP
from typing import cast, TypeAlias, Any, ClassVar

__all__ = ['ParsedHeaders']

_Headers: TypeAlias = Mapping[bytes, Sequence[Sequence[bytes]]]


[docs] class ParsedHeaders(Mapping[bytes, Sequence[BaseHeader]]): """The mapping of message headers, parsed on-demand using a :class:`~email.headerregistry.HeaderRegistry`. Also provides typed properties for standard headers used in IMAP processing. """ _registry: ClassVar[HeaderRegistry] = HeaderRegistry() __slots__ = ['_headers', '_parsed'] def __init__(self, headers: _Headers) -> None: super().__init__() self._headers = headers self._parsed: dict[bytes, Sequence[BaseHeader]] = {} def __getitem__(self, name: bytes) -> Sequence[BaseHeader]: name_lower = name.lower() parsed = self._parsed.get(name_lower) if parsed is not None: return parsed values = self._headers[name_lower] self._parsed[name_lower] = parsed = list(self._parse(values)) return parsed def __len__(self) -> int: return len(self._headers) def __contains__(self, name: Any) -> bool: if not isinstance(name, bytes): raise TypeError(name) return name.lower() in self._headers def __iter__(self) -> Iterator[bytes]: return iter(self._headers.keys()) @classmethod def _parse(cls, values: Sequence[Sequence[bytes]]) \ -> Iterable[BaseHeader]: for value in values: lines = [line.decode('ascii', 'surrogateescape') for line in value] # TODO: Once typeshed merges this fix: # https://github.com/python/typeshed/pull/4365 # assign to hdr_name, hdr_value = ... instead. hdr_tuple = SMTP.header_source_parse(lines) yield cls._registry(hdr_tuple[0], hdr_tuple[1]) def __repr__(self) -> str: return repr(dict(self)) @property def content_type(self) -> ContentTypeHeader | None: """The ``Content-Type`` header.""" try: return cast(ContentTypeHeader, self[b'content-type'][0]) except (KeyError, IndexError): return None @property def date(self) -> DateHeader | None: """The ``Date`` header.""" try: return cast(DateHeader, self[b'date'][0]) except (KeyError, IndexError): return None @property def subject(self) -> UnstructuredHeader | None: """The ``Subject`` header.""" try: return cast(UnstructuredHeader, self[b'subject'][0]) except (KeyError, IndexError): return None @property def from_(self) -> Sequence[AddressHeader] | None: """The ``From`` header.""" try: return cast(Sequence[AddressHeader], self[b'from']) except KeyError: return None @property def sender(self) -> Sequence[SingleAddressHeader] | None: """The ``Sender`` header.""" try: return cast(Sequence[SingleAddressHeader], self[b'sender']) except KeyError: return None @property def reply_to(self) -> Sequence[AddressHeader] | None: """The ``Reply-To`` header.""" try: return cast(Sequence[AddressHeader], self[b'reply-to']) except KeyError: return None @property def to(self) -> Sequence[AddressHeader] | None: """The ``To`` header.""" try: return cast(Sequence[AddressHeader], self[b'to']) except KeyError: return None @property def cc(self) -> Sequence[AddressHeader] | None: """The ``Cc`` header.""" try: return cast(Sequence[AddressHeader], self[b'cc']) except KeyError: return None @property def bcc(self) -> Sequence[AddressHeader] | None: """The ``Bcc`` header.""" try: return cast(Sequence[AddressHeader], self[b'bcc']) except KeyError: return None @property def in_reply_to(self) -> UnstructuredHeader | None: """The ``In-Reply-To`` header.""" try: return cast(UnstructuredHeader, self[b'in-reply-to'][0]) except (KeyError, IndexError): return None @property def references(self) -> UnstructuredHeader | None: """The ``References`` header.""" try: return cast(UnstructuredHeader, self[b'references'][0]) except (KeyError, IndexError): return None @property def message_id(self) -> UnstructuredHeader | None: """The ``Message-Id`` header.""" try: return cast(UnstructuredHeader, self[b'message-id'][0]) except (KeyError, IndexError): return None @property def content_disposition(self) -> ContentDispositionHeader | None: """The ``Content-Disposition`` header.""" try: return cast(ContentDispositionHeader, self[b'content-disposition'][0]) except (KeyError, IndexError): return None @property def content_language(self) -> UnstructuredHeader | None: """The ``Content-Language`` header.""" try: return cast(UnstructuredHeader, self[b'content-language'][0]) except (KeyError, IndexError): return None @property def content_location(self) -> UnstructuredHeader | None: """The ``Content-Location`` header.""" try: return cast(UnstructuredHeader, self[b'content-location'][0]) except (KeyError, IndexError): return None @property def content_id(self) -> UnstructuredHeader | None: """The ``Content-Id`` header.""" try: return cast(UnstructuredHeader, self[b'content-id'][0]) except (KeyError, IndexError): return None @property def content_description(self) -> UnstructuredHeader | None: """The ``Content-Description`` header.""" try: return cast(UnstructuredHeader, self[b'content-description'][0]) except (KeyError, IndexError): return None @property def content_transfer_encoding(self) \ -> ContentTransferEncodingHeader | None: """The ``Content-Transfer-Encoding`` header.""" try: return cast(ContentTransferEncodingHeader, self[b'content-transfer-encoding'][0]) except (KeyError, IndexError): return None