File: response_types.py

package info (click to toggle)
python-imapclient 3.0.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 668 kB
  • sloc: python: 5,355; sh: 14; makefile: 11
file content (148 lines) | stat: -rw-r--r-- 5,112 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# Copyright (c) 2014, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses

import dataclasses
import datetime
from email.utils import formataddr
from typing import Any, List, Optional, Tuple, TYPE_CHECKING, Union

from .typing_imapclient import _Atom
from .util import to_unicode


@dataclasses.dataclass
class Envelope:
    r"""Represents envelope structures of messages. Returned when parsing
    ENVELOPE responses.

    :ivar date: A datetime instance that represents the "Date" header.
    :ivar subject: A string that contains the "Subject" header.
    :ivar from\_: A tuple of Address objects that represent one or more
      addresses from the "From" header, or None if header does not exist.
    :ivar sender: As for from\_ but represents the "Sender" header.
    :ivar reply_to: As for from\_ but represents the "Reply-To" header.
    :ivar to: As for from\_ but represents the "To" header.
    :ivar cc: As for from\_ but represents the "Cc" header.
    :ivar bcc: As for from\_ but represents the "Bcc" recipients.
    :ivar in_reply_to: A string that contains the "In-Reply-To" header.
    :ivar message_id: A string that contains the "Message-Id" header.

    A particular issue to watch out for is IMAP's handling of "group
    syntax" in address fields. This is often encountered as a
    recipient header of the form::

        undisclosed-recipients:;

    but can also be expressed per this more general example::

        A group: a@example.com, B <b@example.org>;

    This example would yield the following Address tuples::

      Address(name=None, route=None, mailbox=u'A group', host=None)
      Address(name=None, route=None, mailbox=u'a', host=u'example.com')
      Address(name=u'B', route=None, mailbox=u'b', host=u'example.org')
      Address(name=None, route=None, mailbox=None, host=None)

    The first Address, where ``host`` is ``None``, indicates the start
    of the group. The ``mailbox`` field contains the group name. The
    final Address, where both ``mailbox`` and ``host`` are ``None``,
    indicates the end of the group.

    See :rfc:`3501#section-7.4.2` and :rfc:`2822` for further details.

    """
    date: Optional[datetime.datetime]
    subject: bytes
    from_: Optional[Tuple["Address", ...]]
    sender: Optional[Tuple["Address", ...]]
    reply_to: Optional[Tuple["Address", ...]]
    to: Optional[Tuple["Address", ...]]
    cc: Optional[Tuple["Address", ...]]
    bcc: Optional[Tuple["Address", ...]]
    in_reply_to: bytes
    message_id: bytes


@dataclasses.dataclass
class Address:
    """Represents electronic mail addresses. Used to store addresses in
    :py:class:`Envelope`.

    :ivar name: The address "personal name".
    :ivar route: SMTP source route (rarely used).
    :ivar mailbox: Mailbox name (what comes just before the @ sign).
    :ivar host: The host/domain name.

    As an example, an address header that looks like::

        Mary Smith <mary@foo.com>

    would be represented as::

        Address(name=u'Mary Smith', route=None, mailbox=u'mary', host=u'foo.com')

    See :rfc:`2822` for more detail.

    See also :py:class:`Envelope` for information about handling of
    "group syntax".
    """

    name: bytes
    route: bytes
    mailbox: bytes
    host: bytes

    def __str__(self) -> str:
        if self.mailbox and self.host:
            address = to_unicode(self.mailbox) + "@" + to_unicode(self.host)
        else:
            address = to_unicode(self.mailbox or self.host)

        return formataddr((to_unicode(self.name), address))


class SearchIds(List[int]):
    """
    Contains a list of message ids as returned by IMAPClient.search().

    The *modseq* attribute will contain the MODSEQ value returned by
    the server (only if the SEARCH command sent involved the MODSEQ
    criteria). See :rfc:`4551` for more details.
    """

    def __init__(self, *args: Any):
        super().__init__(*args)
        self.modseq: Optional[int] = None


_BodyDataType = Tuple[Union[bytes, int, "BodyData"], "_BodyDataType"]


class BodyData(_BodyDataType):
    """
    Returned when parsing BODY and BODYSTRUCTURE responses.
    """

    @classmethod
    def create(cls, response: Tuple[_Atom, ...]) -> "BodyData":
        # In case of multipart messages we will see at least 2 tuples
        # at the start. Nest these in to a list so that the returned
        # response tuple always has a consistent number of elements
        # regardless of whether the message is multipart or not.
        if isinstance(response[0], tuple):
            # Multipart, find where the message part tuples stop
            parts = []
            for i, part in enumerate(response):
                if isinstance(part, bytes):
                    break
                if TYPE_CHECKING:
                    assert isinstance(part, tuple)
                parts.append(part)
            return cls(([cls.create(part) for part in parts],) + response[i:])
        return cls(response)

    @property
    def is_multipart(self) -> bool:
        return isinstance(self[0], list)