exchangelib.folders.roots API documentation

# Module `exchangelib.folders.roots`

Expand source code

    import logging
    from threading import Lock

    from .base import BaseFolder, MISSING_FOLDER_ERRORS
    from .collections import FolderCollection
    from .known_folders import MsgFolderRoot, NON_DELETABLE_FOLDERS, WELLKNOWN_FOLDERS_IN_ROOT, \
        WELLKNOWN_FOLDERS_IN_ARCHIVE_ROOT
    from .queryset import SingleFolderQuerySet, SHALLOW
    from ..errors import ErrorAccessDenied, ErrorFolderNotFound, ErrorInvalidOperation
    from ..fields import EffectiveRightsField
    from ..properties import EWSMeta
    from ..version import EXCHANGE_2007_SP1, EXCHANGE_2010_SP1

    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, None)

        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('Class %s must have a DISTINGUISHED_FOLDER_ID value' % cls)
            try:
                return cls.resolve(
                    account=account,
                    folder=cls(account=account, name=cls.DISTINGUISHED_FOLDER_ID, is_distinguished=True)
                )
            except MISSING_FOLDER_ERRORS:
                raise ErrorFolderNotFound('Could not find distinguished folder %s' % 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("'folder_cls' %s must have a DISTINGUISHED_FOLDER_ID value" % folder_cls)
            # 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('No usable default %s folders' % folder_cls)

        @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, 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:
                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 is 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 is 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 class to not match e.g. RecipientCache instead of 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. TOIS might not exist.
            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
                    pass

            # No candidates in TOIS. 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):
            # Get a single the folder of the same type 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 candidates:
                if len(candidates) > 1:
                    raise ValueError(
                        'Multiple possible default %s folders: %s' % (folder_cls, [f.name for f in candidates])
                    )
                if candidates[0].is_distinguished:
                    log.debug('Found cached distinguished %s folder', folder_cls)
                else:
                    log.debug('Found cached %s folder with localized name', folder_cls)
                return candidates[0]
            raise ErrorFolderNotFound('No usable default %s folders' % folder_cls)

    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, 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

  - RootOfHierarchy
  - BaseFolder
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement
  - SearchableMixIn

  ### Class variables

  `var DISTINGUISHED_FOLDER_ID`
  `var WELLKNOWN_FOLDERS`
  `var supported_from`

  ### Inherited members

  - `RootOfHierarchy`:
    - `ID_ELEMENT_CLS`
    - `add_field`
    - `deregister`
    - `folder_cls_from_container_class`
    - `folder_cls_from_folder_name`
    - `folder_sync_state`
    - `get_default_folder`
    - `get_distinguished`
    - `get_events`
    - `get_streaming_events`
    - `is_distinguished`
    - `item_sync_state`
    - `register`
    - `remove_field`
    - `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, 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

  - RootOfHierarchy
  - BaseFolder
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement
  - SearchableMixIn

  ### 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, 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`
    - `add_field`
    - `deregister`
    - `folder_cls_from_container_class`
    - `folder_cls_from_folder_name`
    - `folder_sync_state`
    - `get_default_folder`
    - `get_distinguished`
    - `get_events`
    - `get_streaming_events`
    - `is_distinguished`
    - `item_sync_state`
    - `register`
    - `remove_field`
    - `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 is 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 is 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 class to not match e.g. RecipientCache instead of 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. TOIS might not exist.
              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
                      pass

              # No candidates in TOIS. 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):
              # Get a single the folder of the same type 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 candidates:
                  if len(candidates) > 1:
                      raise ValueError(
                          'Multiple possible default %s folders: %s' % (folder_cls, [f.name for f in candidates])
                      )
                  if candidates[0].is_distinguished:
                      log.debug('Found cached distinguished %s folder', folder_cls)
                  else:
                      log.debug('Found cached %s folder with localized name', folder_cls)
                  return candidates[0]
              raise ErrorFolderNotFound('No usable default %s folders' % folder_cls)

  ### Ancestors

  - RootOfHierarchy
  - BaseFolder
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement
  - SearchableMixIn

  ### 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`
    - `add_field`
    - `deregister`
    - `folder_cls_from_container_class`
    - `folder_cls_from_folder_name`
    - `folder_sync_state`
    - `get_default_folder`
    - `get_distinguished`
    - `get_events`
    - `get_streaming_events`
    - `is_distinguished`
    - `item_sync_state`
    - `register`
    - `remove_field`
    - `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, None)

          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('Class %s must have a DISTINGUISHED_FOLDER_ID value' % cls)
              try:
                  return cls.resolve(
                      account=account,
                      folder=cls(account=account, name=cls.DISTINGUISHED_FOLDER_ID, is_distinguished=True)
                  )
              except MISSING_FOLDER_ERRORS:
                  raise ErrorFolderNotFound('Could not find distinguished folder %s' % 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("'folder_cls' %s must have a DISTINGUISHED_FOLDER_ID value" % folder_cls)
              # 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('No usable default %s folders' % folder_cls)

          @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, 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:
                  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

  - BaseFolder
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement
  - SearchableMixIn

  ### Subclasses

  - ArchiveRoot
  - PublicFoldersRoot
  - Root

  ### 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:
                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('Class %s must have a DISTINGUISHED_FOLDER_ID value' % cls)
            try:
                return cls.resolve(
                    account=account,
                    folder=cls(account=account, name=cls.DISTINGUISHED_FOLDER_ID, is_distinguished=True)
                )
            except MISSING_FOLDER_ERRORS:
                raise ErrorFolderNotFound('Could not find distinguished folder %s' % cls.DISTINGUISHED_FOLDER_ID)

  ### Instance variables

  `var account`
    Expand source code

        @property
        def account(self):
            return self._account

  `var effective_rights`
  `var parent`
    Expand source code

        @property
        def parent(self):
            return None

  `var root`
    Expand source code

        @property
        def root(self):
            return self

  ### 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("'folder_cls' %s must have a DISTINGUISHED_FOLDER_ID value" % folder_cls)
            # 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('No usable default %s folders' % folder_cls)

  `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, None)

  `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`
    - `add_field`
    - `deregister`
    - `folder_cls_from_container_class`
    - `folder_sync_state`
    - `get_events`
    - `get_streaming_events`
    - `is_distinguished`
    - `item_sync_state`
    - `register`
    - `remove_field`
    - `subscribe_to_pull`
    - `subscribe_to_push`
    - `subscribe_to_streaming`
    - `supported_fields`
    - `sync_hierarchy`
    - `sync_items`
    - `test_access`
    - `tree`
    - `unsubscribe`
    - `validate_field`