Source code for pymap.flags
"""Defines convenience classes for working with IMAP flags.
See Also:
`RFC 3501 2.3.2 <https://tools.ietf.org/html/rfc3501#section-2.3.2>`_
"""
from __future__ import annotations
import enum
from collections.abc import Iterable, Mapping, Set
from .parsing.specials.flag import Flag, Recent, Wildcard
__all__ = ['FlagOp', 'PermanentFlags', 'SessionFlags']
_recent_set = frozenset({Recent})
[docs]
class FlagOp(enum.Enum):
"""Types of operations when updating flags."""
#: All existing flags should be replaced with the flag set.
REPLACE = enum.auto()
#: The flag set should be added to the existing set.
ADD = enum.auto()
#: The flag set should be removed from the existing set.
DELETE = enum.auto()
def __bytes__(self) -> bytes:
return bytes(self.name, 'ascii')
[docs]
def apply(self, flag_set: Set[Flag], operand: Set[Flag]) \
-> frozenset[Flag]:
"""Apply the flag operation on the two sets, returning the result.
Args:
flag_set: The flag set being operated on.
operand: The flags to use as the operand.
"""
if self == FlagOp.ADD:
return frozenset(flag_set | operand)
elif self == FlagOp.DELETE:
return frozenset(flag_set - operand)
else: # op == FlagOp.REPLACE
return frozenset(operand)
[docs]
class PermanentFlags:
"""Keeps track of the defined permanent flags on a mailbox. Because the
permanent flags can include the special ``\\*`` wildcard flag, a simple
set intersect operation is not enough to filter out unknown permanent
flags.
Args:
defined: The defined session flags for the mailbox.
"""
__slots__ = ['_defined']
def __init__(self, defined: Iterable[Flag]) -> None:
super().__init__()
self._defined = frozenset(defined) - _recent_set
@property
def defined(self) -> frozenset[Flag]:
"""The defined permanent flags for the mailbox."""
return self._defined
[docs]
def intersect(self, other: Iterable[Flag]) -> frozenset[Flag]:
"""Returns the subset of flags in ``other`` that are also in
:attr:`.defined`. If the wildcard flag is defined, then all flags in
``other`` are returned.
The ``&`` operator is an alias of this method, making these two
calls equivalent::
perm_flags.intersect(other_flags)
perm_flags & other_flags
Args:
other: The operand flag set.
"""
if Wildcard in self._defined:
return frozenset(other)
else:
return self._defined & frozenset(other)
def __and__(self, other: Iterable[Flag]) -> frozenset[Flag]:
return self.intersect(other)
[docs]
class SessionFlags:
"""Used to track session flags on a message. Session flags are only valid
for the current IMAP client connection while it has the mailbox selected,
they do not persist. The ``\\Recent`` flag is a special-case session flag.
Args:
defined: The defined session flags for the mailbox.
"""
__slots__ = ['_defined', '_flags', '_recent']
def __init__(self, defined: Iterable[Flag]) -> None:
super().__init__()
self._defined = frozenset(defined) - _recent_set
self._flags: dict[int, frozenset[Flag]] = {}
self._recent: set[int] = set()
@property
def defined(self) -> frozenset[Flag]:
"""The defined session flags for the mailbox."""
return self._defined
[docs]
def intersect(self, other: Iterable[Flag]) -> frozenset[Flag]:
"""Returns the subset of flags in ``other`` that are also in
:attr:`.defined`. If the wildcard flag is defined, then all flags in
``other`` are returned.
The ``&`` operator is an alias of this method, making these two
calls equivalent::
session_flags.intersect(other_flags)
session_flags & other_flags
Args:
other: The operand flag set.
"""
if Wildcard in self._defined:
return frozenset(other)
else:
return self._defined & frozenset(other)
def __and__(self, other: Iterable[Flag]) -> frozenset[Flag]:
return self.intersect(other)
[docs]
def get(self, uid: int) -> frozenset[Flag]:
"""Return the session flags for the mailbox session.
Args:
uid: The message UID value.
"""
recent = _recent_set if uid in self._recent else frozenset()
flags = self._flags.get(uid)
return recent if flags is None else (flags | recent)
[docs]
def remove(self, uids: Iterable[int]) -> None:
"""Remove any session flags for the given message.
Args:
uids: The message UID values.
"""
for uid in uids:
self._recent.discard(uid)
self._flags.pop(uid, None)
[docs]
def update(self, uid: int, flag_set: Iterable[Flag],
op: FlagOp = FlagOp.REPLACE) -> frozenset[Flag]:
"""Update the flags for the session, returning the resulting flags.
Args:
uid: The message UID value.
flag_set: The set of flags for the update operation.
op: The type of update.
"""
orig_set = self._flags.get(uid, frozenset())
new_flags = op.apply(orig_set, self & flag_set)
if new_flags:
self._flags[uid] = new_flags
else:
self._flags.pop(uid, None)
return new_flags
[docs]
def add_recent(self, uid: int) -> None:
"""Adds the ``\\Recent`` flag to the flags for the session.
Args:
uid: The message UID value.
"""
self._recent.add(uid)
@property
def recent(self) -> int:
"""The number of messages with the ``\\Recent`` flag."""
return len(self._recent)
@property
def recent_uids(self) -> Set[int]:
"""The message UIDs with the ``\\Recent`` flag."""
return self._recent
@property
def flags(self) -> Mapping[int, frozenset[Flag]]:
"""The mapping of UID to its associated session flags, not including
``\\Recent``.
"""
return self._flags