Module exchangelib.folders.roots
Expand source code
import logging
from threading import Lock
from ..errors import ErrorAccessDenied, ErrorFolderNotFound, ErrorInvalidOperation
from ..fields import EffectiveRightsField
from ..properties import EWSMeta
from ..version import EXCHANGE_2007_SP1, EXCHANGE_2010_SP1
from .base import BaseFolder
from .collections import FolderCollection
from .known_folders import (
MISC_FOLDERS,
NON_DELETABLE_FOLDERS,
WELLKNOWN_FOLDERS_IN_ARCHIVE_ROOT,
WELLKNOWN_FOLDERS_IN_ROOT,
MsgFolderRoot,
)
from .queryset import MISSING_FOLDER_ERRORS, SHALLOW, SingleFolderQuerySet
log = logging.getLogger(__name__)
class RootOfHierarchy(BaseFolder, metaclass=EWSMeta):
"""Base class for folders that implement the root of a folder hierarchy."""
# A list of wellknown, or "distinguished", folders that are belong in this folder hierarchy. See
# https://docs.microsoft.com/en-us/dotnet/api/microsoft.exchange.webservices.data.wellknownfoldername
# and https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/distinguishedfolderid
# 'RootOfHierarchy' subclasses must not be in this list.
WELLKNOWN_FOLDERS = []
_subfolders_lock = Lock()
# This folder type also has 'folder:PermissionSet' on some server versions, but requesting it sometimes causes
# 'ErrorAccessDenied', as reported by some users. Ignore it entirely for root folders - it's usefulness is
# deemed minimal at best.
effective_rights = EffectiveRightsField(
field_uri="folder:EffectiveRights", is_read_only=True, supported_from=EXCHANGE_2007_SP1
)
__slots__ = "_account", "_subfolders"
# A special folder that acts as the top of a folder hierarchy. Finds and caches subfolders at arbitrary depth.
def __init__(self, **kwargs):
self._account = kwargs.pop("account", None) # A pointer back to the account holding the folder hierarchy
super().__init__(**kwargs)
self._subfolders = None # See self._folders_map()
@property
def account(self):
return self._account
@property
def root(self):
return self
@property
def parent(self):
return None
@classmethod
def register(cls, *args, **kwargs):
if cls is not RootOfHierarchy:
raise TypeError("For folder roots, custom fields must be registered on the RootOfHierarchy class")
return super().register(*args, **kwargs)
@classmethod
def deregister(cls, *args, **kwargs):
if cls is not RootOfHierarchy:
raise TypeError("For folder roots, custom fields must be registered on the RootOfHierarchy class")
return super().deregister(*args, **kwargs)
def get_folder(self, folder):
if not folder.id:
raise ValueError("'folder' must have an ID")
return self._folders_map.get(folder.id)
def add_folder(self, folder):
if not folder.id:
raise ValueError("'folder' must have an ID")
self._folders_map[folder.id] = folder
def update_folder(self, folder):
if not folder.id:
raise ValueError("'folder' must have an ID")
self._folders_map[folder.id] = folder
def remove_folder(self, folder):
if not folder.id:
raise ValueError("'folder' must have an ID")
try:
del self._folders_map[folder.id]
except KeyError:
pass
def clear_cache(self):
with self._subfolders_lock:
self._subfolders = None
def get_children(self, folder):
for f in self._folders_map.values():
if not f.parent:
continue
if f.parent.id == folder.id:
yield f
@classmethod
def get_distinguished(cls, account):
"""Get the distinguished folder for this folder class.
:param account:
"""
if not cls.DISTINGUISHED_FOLDER_ID:
raise ValueError(f"Class {cls} must have a DISTINGUISHED_FOLDER_ID value")
try:
return cls.resolve(
account=account, folder=cls(account=account, name=cls.DISTINGUISHED_FOLDER_ID, is_distinguished=True)
)
except MISSING_FOLDER_ERRORS:
raise ErrorFolderNotFound(f"Could not find distinguished folder {cls.DISTINGUISHED_FOLDER_ID}")
def get_default_folder(self, folder_cls):
"""Return the distinguished folder instance of type folder_cls belonging to this account. If no distinguished
folder was found, try as best we can to return the default folder of type 'folder_cls'
"""
if not folder_cls.DISTINGUISHED_FOLDER_ID:
raise ValueError(f"'folder_cls' {folder_cls} must have a DISTINGUISHED_FOLDER_ID value")
# Use cached distinguished folder instance, but only if cache has already been prepped. This is an optimization
# for accessing e.g. 'account.contacts' without fetching all folders of the account.
if self._subfolders is not None:
for f in self._folders_map.values():
# Require exact class, to not match subclasses, e.g. RecipientCache instead of Contacts
if f.__class__ == folder_cls and f.is_distinguished:
log.debug("Found cached distinguished %s folder", folder_cls)
return f
try:
log.debug("Requesting distinguished %s folder explicitly", folder_cls)
return folder_cls.get_distinguished(root=self)
except ErrorAccessDenied:
# Maybe we just don't have GetFolder access? Try FindItems instead
log.debug("Testing default %s folder with FindItem", folder_cls)
fld = folder_cls(root=self, name=folder_cls.DISTINGUISHED_FOLDER_ID, is_distinguished=True)
fld.test_access()
return self._folders_map.get(fld.id, fld) # Use cached instance if available
except MISSING_FOLDER_ERRORS:
# The Exchange server does not return a distinguished folder of this type
pass
raise ErrorFolderNotFound(f"No usable default {folder_cls} folders")
@property
def _folders_map(self):
if self._subfolders is not None:
return self._subfolders
with self._subfolders_lock:
# Map root, and all subfolders of root, at arbitrary depth by folder ID. First get distinguished folders,
# so we are sure to apply the correct Folder class, then fetch all subfolders of this root.
folders_map = {self.id: self}
distinguished_folders = [
cls(root=self, name=cls.DISTINGUISHED_FOLDER_ID, is_distinguished=True)
for cls in self.WELLKNOWN_FOLDERS
if cls.get_folder_allowed and cls.supports_version(self.account.version)
]
for f in FolderCollection(account=self.account, folders=distinguished_folders).resolve():
if isinstance(f, MISSING_FOLDER_ERRORS):
# This is just a distinguished folder the server does not have
continue
if isinstance(f, ErrorInvalidOperation):
# This is probably a distinguished folder the server does not have. We previously tested the exact
# error message (f.value), but some Exchange servers return localized error messages, so that's not
# possible to do reliably.
continue
if isinstance(f, ErrorAccessDenied):
# We may not have GetFolder access, either to this folder or at all
continue
if isinstance(f, Exception):
raise f
folders_map[f.id] = f
for f in (
SingleFolderQuerySet(account=self.account, folder=self).depth(self.DEFAULT_FOLDER_TRAVERSAL_DEPTH).all()
):
if isinstance(f, ErrorAccessDenied):
# We may not have FindFolder access, or GetFolder access, either to this folder or at all
continue
if isinstance(f, MISSING_FOLDER_ERRORS):
# We were unlucky. The folder disappeared between the FindFolder and the GetFolder calls
continue
if isinstance(f, Exception):
raise f
if f.id in folders_map:
# Already exists. Probably a distinguished folder
continue
folders_map[f.id] = f
self._subfolders = folders_map
return folders_map
@classmethod
def from_xml(cls, elem, account):
kwargs = cls._kwargs_from_elem(elem=elem, account=account)
cls._clear(elem)
return cls(account=account, **kwargs)
@classmethod
def folder_cls_from_folder_name(cls, folder_name, locale):
"""Return the folder class that matches a localized folder name.
:param folder_name:
:param locale: a string, e.g. 'da_DK'
"""
for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS + MISC_FOLDERS:
if folder_name.lower() in folder_cls.localized_names(locale):
return folder_cls
raise KeyError()
def __repr__(self):
# Let's not create an infinite loop when printing self.root
return self.__class__.__name__ + repr(
(
self.account,
"[self]",
self.name,
self.total_count,
self.unread_count,
self.child_folder_count,
self.folder_class,
self.id,
self.changekey,
)
)
class Root(RootOfHierarchy):
"""The root of the standard folder hierarchy."""
DISTINGUISHED_FOLDER_ID = "root"
WELLKNOWN_FOLDERS = WELLKNOWN_FOLDERS_IN_ROOT
@property
def tois(self):
# 'Top of Information Store' is a folder available in some Exchange accounts. It usually contains the
# distinguished folders belonging to the account (inbox, calendar, trash etc.).
return self.get_default_folder(MsgFolderRoot)
def get_default_folder(self, folder_cls):
try:
return super().get_default_folder(folder_cls)
except MISSING_FOLDER_ERRORS:
pass
# Try to pick a suitable default folder. we do this by:
# 1. Searching the full folder list for a folder with the distinguished folder name
# 2. Searching TOIS for a direct child folder of the same type that is marked as distinguished
# 3. Searching TOIS for a direct child folder of the same type that has a localized name
# 4. Searching root for a direct child folder of the same type that is marked as distinguished
# 5. Searching root for a direct child folder of the same type that has a localized name
log.debug("Searching default %s folder in full folder list", folder_cls)
for f in self._folders_map.values():
# Require exact type, to avoid matching with subclasses (e.g. RecipientCache and Contacts)
if f.__class__ == folder_cls and f.has_distinguished_name:
log.debug("Found cached %s folder with default distinguished name", folder_cls)
return f
# Try direct children of TOIS first, unless we're trying to get the TOIS folder
if folder_cls != MsgFolderRoot:
try:
return self._get_candidate(folder_cls=folder_cls, folder_coll=self.tois.children)
except MISSING_FOLDER_ERRORS:
# No candidates, or TOIS does not exist, or we don't have access to TOIS
pass
# Finally, try direct children of root
return self._get_candidate(folder_cls=folder_cls, folder_coll=self.children)
def _get_candidate(self, folder_cls, folder_coll):
# Look for a single useful folder of type folder_cls in folder_coll
same_type = [f for f in folder_coll if f.__class__ == folder_cls]
are_distinguished = [f for f in same_type if f.is_distinguished]
if are_distinguished:
candidates = are_distinguished
else:
candidates = [f for f in same_type if f.name.lower() in folder_cls.localized_names(self.account.locale)]
if not candidates:
raise ErrorFolderNotFound(f"No usable default {folder_cls} folders")
if len(candidates) > 1:
raise ValueError(f"Multiple possible default {folder_cls} folders: {[f.name for f in candidates]}")
candidate = candidates[0]
if candidate.is_distinguished:
log.debug("Found distinguished %s folder", folder_cls)
else:
log.debug("Found %s folder with localized name %s", folder_cls, candidate.name)
return candidate
class PublicFoldersRoot(RootOfHierarchy):
"""The root of the public folders hierarchy. Not available on all mailboxes."""
DISTINGUISHED_FOLDER_ID = "publicfoldersroot"
DEFAULT_FOLDER_TRAVERSAL_DEPTH = SHALLOW
supported_from = EXCHANGE_2007_SP1
def get_children(self, folder):
# EWS does not allow deep traversal of public folders, so self._folders_map will only populate the top-level
# subfolders. To traverse public folders at arbitrary depth, we need to get child folders on demand.
# Let's check if this folder already has any cached children. If so, assume we can just return those.
children = list(super().get_children(folder=folder))
if children:
# Return a generator like our parent does
yield from children
return
# Also return early if the server told us that there are no child folders.
if folder.child_folder_count == 0:
return
children_map = {}
try:
for f in (
SingleFolderQuerySet(account=self.account, folder=folder)
.depth(self.DEFAULT_FOLDER_TRAVERSAL_DEPTH)
.all()
):
if isinstance(f, MISSING_FOLDER_ERRORS):
# We were unlucky. The folder disappeared between the FindFolder and the GetFolder calls
continue
if isinstance(f, Exception):
raise f
children_map[f.id] = f
except ErrorAccessDenied:
# No access to this folder
pass
# Let's update the cache atomically, to avoid partial reads of the cache.
with self._subfolders_lock:
self._subfolders.update(children_map)
# Child folders have been cached now. Try super().get_children() again.
yield from super().get_children(folder=folder)
class ArchiveRoot(RootOfHierarchy):
"""The root of the archive folders hierarchy. Not available on all mailboxes."""
DISTINGUISHED_FOLDER_ID = "archiveroot"
supported_from = EXCHANGE_2010_SP1
WELLKNOWN_FOLDERS = WELLKNOWN_FOLDERS_IN_ARCHIVE_ROOT
Classes
class ArchiveRoot (**kwargs)
-
The root of the archive folders hierarchy. Not available on all mailboxes.
Expand source code
class ArchiveRoot(RootOfHierarchy): """The root of the archive folders hierarchy. Not available on all mailboxes.""" DISTINGUISHED_FOLDER_ID = "archiveroot" supported_from = EXCHANGE_2010_SP1 WELLKNOWN_FOLDERS = WELLKNOWN_FOLDERS_IN_ARCHIVE_ROOT
Ancestors
Class variables
var DISTINGUISHED_FOLDER_ID
var WELLKNOWN_FOLDERS
var supported_from
Inherited members
RootOfHierarchy
:ID_ELEMENT_CLS
account
add_field
all
deregister
exclude
filter
folder_cls_from_container_class
folder_cls_from_folder_name
folder_sync_state
get
get_default_folder
get_distinguished
get_events
get_streaming_events
is_distinguished
item_sync_state
none
parent
people
register
remove_field
root
subscribe_to_pull
subscribe_to_push
subscribe_to_streaming
supported_fields
sync_hierarchy
sync_items
test_access
tree
unsubscribe
validate_field
class PublicFoldersRoot (**kwargs)
-
The root of the public folders hierarchy. Not available on all mailboxes.
Expand source code
class PublicFoldersRoot(RootOfHierarchy): """The root of the public folders hierarchy. Not available on all mailboxes.""" DISTINGUISHED_FOLDER_ID = "publicfoldersroot" DEFAULT_FOLDER_TRAVERSAL_DEPTH = SHALLOW supported_from = EXCHANGE_2007_SP1 def get_children(self, folder): # EWS does not allow deep traversal of public folders, so self._folders_map will only populate the top-level # subfolders. To traverse public folders at arbitrary depth, we need to get child folders on demand. # Let's check if this folder already has any cached children. If so, assume we can just return those. children = list(super().get_children(folder=folder)) if children: # Return a generator like our parent does yield from children return # Also return early if the server told us that there are no child folders. if folder.child_folder_count == 0: return children_map = {} try: for f in ( SingleFolderQuerySet(account=self.account, folder=folder) .depth(self.DEFAULT_FOLDER_TRAVERSAL_DEPTH) .all() ): if isinstance(f, MISSING_FOLDER_ERRORS): # We were unlucky. The folder disappeared between the FindFolder and the GetFolder calls continue if isinstance(f, Exception): raise f children_map[f.id] = f except ErrorAccessDenied: # No access to this folder pass # Let's update the cache atomically, to avoid partial reads of the cache. with self._subfolders_lock: self._subfolders.update(children_map) # Child folders have been cached now. Try super().get_children() again. yield from super().get_children(folder=folder)
Ancestors
Class variables
var DEFAULT_FOLDER_TRAVERSAL_DEPTH
var DISTINGUISHED_FOLDER_ID
var supported_from
Methods
def get_children(self, folder)
-
Expand source code
def get_children(self, folder): # EWS does not allow deep traversal of public folders, so self._folders_map will only populate the top-level # subfolders. To traverse public folders at arbitrary depth, we need to get child folders on demand. # Let's check if this folder already has any cached children. If so, assume we can just return those. children = list(super().get_children(folder=folder)) if children: # Return a generator like our parent does yield from children return # Also return early if the server told us that there are no child folders. if folder.child_folder_count == 0: return children_map = {} try: for f in ( SingleFolderQuerySet(account=self.account, folder=folder) .depth(self.DEFAULT_FOLDER_TRAVERSAL_DEPTH) .all() ): if isinstance(f, MISSING_FOLDER_ERRORS): # We were unlucky. The folder disappeared between the FindFolder and the GetFolder calls continue if isinstance(f, Exception): raise f children_map[f.id] = f except ErrorAccessDenied: # No access to this folder pass # Let's update the cache atomically, to avoid partial reads of the cache. with self._subfolders_lock: self._subfolders.update(children_map) # Child folders have been cached now. Try super().get_children() again. yield from super().get_children(folder=folder)
Inherited members
RootOfHierarchy
:ID_ELEMENT_CLS
account
add_field
all
deregister
exclude
filter
folder_cls_from_container_class
folder_cls_from_folder_name
folder_sync_state
get
get_default_folder
get_distinguished
get_events
get_streaming_events
is_distinguished
item_sync_state
none
parent
people
register
remove_field
root
subscribe_to_pull
subscribe_to_push
subscribe_to_streaming
supported_fields
sync_hierarchy
sync_items
test_access
tree
unsubscribe
validate_field
class Root (**kwargs)
-
The root of the standard folder hierarchy.
Expand source code
class Root(RootOfHierarchy): """The root of the standard folder hierarchy.""" DISTINGUISHED_FOLDER_ID = "root" WELLKNOWN_FOLDERS = WELLKNOWN_FOLDERS_IN_ROOT @property def tois(self): # 'Top of Information Store' is a folder available in some Exchange accounts. It usually contains the # distinguished folders belonging to the account (inbox, calendar, trash etc.). return self.get_default_folder(MsgFolderRoot) def get_default_folder(self, folder_cls): try: return super().get_default_folder(folder_cls) except MISSING_FOLDER_ERRORS: pass # Try to pick a suitable default folder. we do this by: # 1. Searching the full folder list for a folder with the distinguished folder name # 2. Searching TOIS for a direct child folder of the same type that is marked as distinguished # 3. Searching TOIS for a direct child folder of the same type that has a localized name # 4. Searching root for a direct child folder of the same type that is marked as distinguished # 5. Searching root for a direct child folder of the same type that has a localized name log.debug("Searching default %s folder in full folder list", folder_cls) for f in self._folders_map.values(): # Require exact type, to avoid matching with subclasses (e.g. RecipientCache and Contacts) if f.__class__ == folder_cls and f.has_distinguished_name: log.debug("Found cached %s folder with default distinguished name", folder_cls) return f # Try direct children of TOIS first, unless we're trying to get the TOIS folder if folder_cls != MsgFolderRoot: try: return self._get_candidate(folder_cls=folder_cls, folder_coll=self.tois.children) except MISSING_FOLDER_ERRORS: # No candidates, or TOIS does not exist, or we don't have access to TOIS pass # Finally, try direct children of root return self._get_candidate(folder_cls=folder_cls, folder_coll=self.children) def _get_candidate(self, folder_cls, folder_coll): # Look for a single useful folder of type folder_cls in folder_coll same_type = [f for f in folder_coll if f.__class__ == folder_cls] are_distinguished = [f for f in same_type if f.is_distinguished] if are_distinguished: candidates = are_distinguished else: candidates = [f for f in same_type if f.name.lower() in folder_cls.localized_names(self.account.locale)] if not candidates: raise ErrorFolderNotFound(f"No usable default {folder_cls} folders") if len(candidates) > 1: raise ValueError(f"Multiple possible default {folder_cls} folders: {[f.name for f in candidates]}") candidate = candidates[0] if candidate.is_distinguished: log.debug("Found distinguished %s folder", folder_cls) else: log.debug("Found %s folder with localized name %s", folder_cls, candidate.name) return candidate
Ancestors
Class variables
var DISTINGUISHED_FOLDER_ID
var WELLKNOWN_FOLDERS
Instance variables
var tois
-
Expand source code
@property def tois(self): # 'Top of Information Store' is a folder available in some Exchange accounts. It usually contains the # distinguished folders belonging to the account (inbox, calendar, trash etc.). return self.get_default_folder(MsgFolderRoot)
Inherited members
RootOfHierarchy
:ID_ELEMENT_CLS
account
add_field
all
deregister
exclude
filter
folder_cls_from_container_class
folder_cls_from_folder_name
folder_sync_state
get
get_default_folder
get_distinguished
get_events
get_streaming_events
is_distinguished
item_sync_state
none
parent
people
register
remove_field
root
subscribe_to_pull
subscribe_to_push
subscribe_to_streaming
supported_fields
sync_hierarchy
sync_items
test_access
tree
unsubscribe
validate_field
class RootOfHierarchy (**kwargs)
-
Base class for folders that implement the root of a folder hierarchy.
Expand source code
class RootOfHierarchy(BaseFolder, metaclass=EWSMeta): """Base class for folders that implement the root of a folder hierarchy.""" # A list of wellknown, or "distinguished", folders that are belong in this folder hierarchy. See # https://docs.microsoft.com/en-us/dotnet/api/microsoft.exchange.webservices.data.wellknownfoldername # and https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/distinguishedfolderid # 'RootOfHierarchy' subclasses must not be in this list. WELLKNOWN_FOLDERS = [] _subfolders_lock = Lock() # This folder type also has 'folder:PermissionSet' on some server versions, but requesting it sometimes causes # 'ErrorAccessDenied', as reported by some users. Ignore it entirely for root folders - it's usefulness is # deemed minimal at best. effective_rights = EffectiveRightsField( field_uri="folder:EffectiveRights", is_read_only=True, supported_from=EXCHANGE_2007_SP1 ) __slots__ = "_account", "_subfolders" # A special folder that acts as the top of a folder hierarchy. Finds and caches subfolders at arbitrary depth. def __init__(self, **kwargs): self._account = kwargs.pop("account", None) # A pointer back to the account holding the folder hierarchy super().__init__(**kwargs) self._subfolders = None # See self._folders_map() @property def account(self): return self._account @property def root(self): return self @property def parent(self): return None @classmethod def register(cls, *args, **kwargs): if cls is not RootOfHierarchy: raise TypeError("For folder roots, custom fields must be registered on the RootOfHierarchy class") return super().register(*args, **kwargs) @classmethod def deregister(cls, *args, **kwargs): if cls is not RootOfHierarchy: raise TypeError("For folder roots, custom fields must be registered on the RootOfHierarchy class") return super().deregister(*args, **kwargs) def get_folder(self, folder): if not folder.id: raise ValueError("'folder' must have an ID") return self._folders_map.get(folder.id) def add_folder(self, folder): if not folder.id: raise ValueError("'folder' must have an ID") self._folders_map[folder.id] = folder def update_folder(self, folder): if not folder.id: raise ValueError("'folder' must have an ID") self._folders_map[folder.id] = folder def remove_folder(self, folder): if not folder.id: raise ValueError("'folder' must have an ID") try: del self._folders_map[folder.id] except KeyError: pass def clear_cache(self): with self._subfolders_lock: self._subfolders = None def get_children(self, folder): for f in self._folders_map.values(): if not f.parent: continue if f.parent.id == folder.id: yield f @classmethod def get_distinguished(cls, account): """Get the distinguished folder for this folder class. :param account: """ if not cls.DISTINGUISHED_FOLDER_ID: raise ValueError(f"Class {cls} must have a DISTINGUISHED_FOLDER_ID value") try: return cls.resolve( account=account, folder=cls(account=account, name=cls.DISTINGUISHED_FOLDER_ID, is_distinguished=True) ) except MISSING_FOLDER_ERRORS: raise ErrorFolderNotFound(f"Could not find distinguished folder {cls.DISTINGUISHED_FOLDER_ID}") def get_default_folder(self, folder_cls): """Return the distinguished folder instance of type folder_cls belonging to this account. If no distinguished folder was found, try as best we can to return the default folder of type 'folder_cls' """ if not folder_cls.DISTINGUISHED_FOLDER_ID: raise ValueError(f"'folder_cls' {folder_cls} must have a DISTINGUISHED_FOLDER_ID value") # Use cached distinguished folder instance, but only if cache has already been prepped. This is an optimization # for accessing e.g. 'account.contacts' without fetching all folders of the account. if self._subfolders is not None: for f in self._folders_map.values(): # Require exact class, to not match subclasses, e.g. RecipientCache instead of Contacts if f.__class__ == folder_cls and f.is_distinguished: log.debug("Found cached distinguished %s folder", folder_cls) return f try: log.debug("Requesting distinguished %s folder explicitly", folder_cls) return folder_cls.get_distinguished(root=self) except ErrorAccessDenied: # Maybe we just don't have GetFolder access? Try FindItems instead log.debug("Testing default %s folder with FindItem", folder_cls) fld = folder_cls(root=self, name=folder_cls.DISTINGUISHED_FOLDER_ID, is_distinguished=True) fld.test_access() return self._folders_map.get(fld.id, fld) # Use cached instance if available except MISSING_FOLDER_ERRORS: # The Exchange server does not return a distinguished folder of this type pass raise ErrorFolderNotFound(f"No usable default {folder_cls} folders") @property def _folders_map(self): if self._subfolders is not None: return self._subfolders with self._subfolders_lock: # Map root, and all subfolders of root, at arbitrary depth by folder ID. First get distinguished folders, # so we are sure to apply the correct Folder class, then fetch all subfolders of this root. folders_map = {self.id: self} distinguished_folders = [ cls(root=self, name=cls.DISTINGUISHED_FOLDER_ID, is_distinguished=True) for cls in self.WELLKNOWN_FOLDERS if cls.get_folder_allowed and cls.supports_version(self.account.version) ] for f in FolderCollection(account=self.account, folders=distinguished_folders).resolve(): if isinstance(f, MISSING_FOLDER_ERRORS): # This is just a distinguished folder the server does not have continue if isinstance(f, ErrorInvalidOperation): # This is probably a distinguished folder the server does not have. We previously tested the exact # error message (f.value), but some Exchange servers return localized error messages, so that's not # possible to do reliably. continue if isinstance(f, ErrorAccessDenied): # We may not have GetFolder access, either to this folder or at all continue if isinstance(f, Exception): raise f folders_map[f.id] = f for f in ( SingleFolderQuerySet(account=self.account, folder=self).depth(self.DEFAULT_FOLDER_TRAVERSAL_DEPTH).all() ): if isinstance(f, ErrorAccessDenied): # We may not have FindFolder access, or GetFolder access, either to this folder or at all continue if isinstance(f, MISSING_FOLDER_ERRORS): # We were unlucky. The folder disappeared between the FindFolder and the GetFolder calls continue if isinstance(f, Exception): raise f if f.id in folders_map: # Already exists. Probably a distinguished folder continue folders_map[f.id] = f self._subfolders = folders_map return folders_map @classmethod def from_xml(cls, elem, account): kwargs = cls._kwargs_from_elem(elem=elem, account=account) cls._clear(elem) return cls(account=account, **kwargs) @classmethod def folder_cls_from_folder_name(cls, folder_name, locale): """Return the folder class that matches a localized folder name. :param folder_name: :param locale: a string, e.g. 'da_DK' """ for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS + MISC_FOLDERS: if folder_name.lower() in folder_cls.localized_names(locale): return folder_cls raise KeyError() def __repr__(self): # Let's not create an infinite loop when printing self.root return self.__class__.__name__ + repr( ( self.account, "[self]", self.name, self.total_count, self.unread_count, self.child_folder_count, self.folder_class, self.id, self.changekey, ) )
Ancestors
Subclasses
Class variables
var FIELDS
var WELLKNOWN_FOLDERS
Static methods
def folder_cls_from_folder_name(folder_name, locale)
-
Return the folder class that matches a localized folder name.
:param folder_name: :param locale: a string, e.g. 'da_DK'
Expand source code
@classmethod def folder_cls_from_folder_name(cls, folder_name, locale): """Return the folder class that matches a localized folder name. :param folder_name: :param locale: a string, e.g. 'da_DK' """ for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS + MISC_FOLDERS: if folder_name.lower() in folder_cls.localized_names(locale): return folder_cls raise KeyError()
def from_xml(elem, account)
-
Expand source code
@classmethod def from_xml(cls, elem, account): kwargs = cls._kwargs_from_elem(elem=elem, account=account) cls._clear(elem) return cls(account=account, **kwargs)
def get_distinguished(account)
-
Get the distinguished folder for this folder class.
:param account:
Expand source code
@classmethod def get_distinguished(cls, account): """Get the distinguished folder for this folder class. :param account: """ if not cls.DISTINGUISHED_FOLDER_ID: raise ValueError(f"Class {cls} must have a DISTINGUISHED_FOLDER_ID value") try: return cls.resolve( account=account, folder=cls(account=account, name=cls.DISTINGUISHED_FOLDER_ID, is_distinguished=True) ) except MISSING_FOLDER_ERRORS: raise ErrorFolderNotFound(f"Could not find distinguished folder {cls.DISTINGUISHED_FOLDER_ID}")
Instance variables
var effective_rights
Methods
def add_folder(self, folder)
-
Expand source code
def add_folder(self, folder): if not folder.id: raise ValueError("'folder' must have an ID") self._folders_map[folder.id] = folder
def clear_cache(self)
-
Expand source code
def clear_cache(self): with self._subfolders_lock: self._subfolders = None
def get_children(self, folder)
-
Expand source code
def get_children(self, folder): for f in self._folders_map.values(): if not f.parent: continue if f.parent.id == folder.id: yield f
def get_default_folder(self, folder_cls)
-
Return the distinguished folder instance of type folder_cls belonging to this account. If no distinguished folder was found, try as best we can to return the default folder of type 'folder_cls'
Expand source code
def get_default_folder(self, folder_cls): """Return the distinguished folder instance of type folder_cls belonging to this account. If no distinguished folder was found, try as best we can to return the default folder of type 'folder_cls' """ if not folder_cls.DISTINGUISHED_FOLDER_ID: raise ValueError(f"'folder_cls' {folder_cls} must have a DISTINGUISHED_FOLDER_ID value") # Use cached distinguished folder instance, but only if cache has already been prepped. This is an optimization # for accessing e.g. 'account.contacts' without fetching all folders of the account. if self._subfolders is not None: for f in self._folders_map.values(): # Require exact class, to not match subclasses, e.g. RecipientCache instead of Contacts if f.__class__ == folder_cls and f.is_distinguished: log.debug("Found cached distinguished %s folder", folder_cls) return f try: log.debug("Requesting distinguished %s folder explicitly", folder_cls) return folder_cls.get_distinguished(root=self) except ErrorAccessDenied: # Maybe we just don't have GetFolder access? Try FindItems instead log.debug("Testing default %s folder with FindItem", folder_cls) fld = folder_cls(root=self, name=folder_cls.DISTINGUISHED_FOLDER_ID, is_distinguished=True) fld.test_access() return self._folders_map.get(fld.id, fld) # Use cached instance if available except MISSING_FOLDER_ERRORS: # The Exchange server does not return a distinguished folder of this type pass raise ErrorFolderNotFound(f"No usable default {folder_cls} folders")
def get_folder(self, folder)
-
Expand source code
def get_folder(self, folder): if not folder.id: raise ValueError("'folder' must have an ID") return self._folders_map.get(folder.id)
def remove_folder(self, folder)
-
Expand source code
def remove_folder(self, folder): if not folder.id: raise ValueError("'folder' must have an ID") try: del self._folders_map[folder.id] except KeyError: pass
def update_folder(self, folder)
-
Expand source code
def update_folder(self, folder): if not folder.id: raise ValueError("'folder' must have an ID") self._folders_map[folder.id] = folder
Inherited members
BaseFolder
:ID_ELEMENT_CLS
account
add_field
all
deregister
exclude
filter
folder_cls_from_container_class
folder_sync_state
get
get_events
get_streaming_events
is_distinguished
item_sync_state
none
parent
people
register
remove_field
root
subscribe_to_pull
subscribe_to_push
subscribe_to_streaming
supported_fields
sync_hierarchy
sync_items
test_access
tree
unsubscribe
validate_field