Module exchangelib.folders
Sub-modules
- exchangelib.folders.base
- exchangelib.folders.collections
- exchangelib.folders.known_folders
- exchangelib.folders.queryset
- exchangelib.folders.roots
Classes
- class AdminAuditLogs (**kwargs)
- 
Expand source codeclass AdminAuditLogs(WellknownFolder): DISTINGUISHED_FOLDER_ID = "adminauditlogs" supported_from = EXCHANGE_2013 get_folder_allowed = FalseBase class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var get_folder_allowed
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 AllCategorizedItems (**kwargs)
- 
Expand source codeclass AllCategorizedItems(WellknownFolder): DISTINGUISHED_FOLDER_ID = "allcategorizeditems" CONTAINER_CLASS = "IPF.Note" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 AllContacts (**kwargs)
- 
Expand source codeclass AllContacts(WellknownFolder): DISTINGUISHED_FOLDER_ID = "allcontacts" CONTAINER_CLASS = "IPF.Note" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 AllItems (**kwargs)
- 
Expand source codeclass AllItems(WellknownFolder): DISTINGUISHED_FOLDER_ID = "allitems" CONTAINER_CLASS = "IPF" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 AllPersonMetadata (**kwargs)
- 
Expand source codeclass AllPersonMetadata(WellknownFolder): DISTINGUISHED_FOLDER_ID = "allpersonmetadata" CONTAINER_CLASS = "IPF.Note" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 AllTodoTasks (**kwargs)
- 
Expand source codeclass AllTodoTasks(NonDeletableFolder): CONTAINER_CLASS = "IPF.Task" supported_item_models = TASK_ITEM_CLASSESA mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var supported_item_models
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ApplicationData (**kwargs)
- 
Expand source codeclass ApplicationData(NonDeletableFolder): CONTAINER_CLASS = "IPM.ApplicationData"A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ArchiveDeletedItems (**kwargs)
- 
Expand source codeclass ArchiveDeletedItems(WellknownFolder): DISTINGUISHED_FOLDER_ID = "archivedeleteditems" supported_from = EXCHANGE_2010_SP1Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ArchiveInbox (**kwargs)
- 
Expand source codeclass ArchiveInbox(WellknownFolder): DISTINGUISHED_FOLDER_ID = "archiveinbox" supported_from = EXCHANGE_2013_SP1Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ArchiveMsgFolderRoot (**kwargs)
- 
Expand source codeclass ArchiveMsgFolderRoot(WellknownFolder): DISTINGUISHED_FOLDER_ID = "archivemsgfolderroot" supported_from = EXCHANGE_2010_SP1Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ArchiveRecoverableItemsDeletions (**kwargs)
- 
Expand source codeclass ArchiveRecoverableItemsDeletions(WellknownFolder): DISTINGUISHED_FOLDER_ID = "archiverecoverableitemsdeletions" supported_from = EXCHANGE_2010_SP1Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ArchiveRecoverableItemsPurges (**kwargs)
- 
Expand source codeclass ArchiveRecoverableItemsPurges(WellknownFolder): DISTINGUISHED_FOLDER_ID = "archiverecoverableitemspurges" supported_from = EXCHANGE_2010_SP1Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ArchiveRecoverableItemsRoot (**kwargs)
- 
Expand source codeclass ArchiveRecoverableItemsRoot(WellknownFolder): DISTINGUISHED_FOLDER_ID = "archiverecoverableitemsroot" supported_from = EXCHANGE_2010_SP1Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ArchiveRecoverableItemsVersions (**kwargs)
- 
Expand source codeclass ArchiveRecoverableItemsVersions(WellknownFolder): DISTINGUISHED_FOLDER_ID = "archiverecoverableitemsversions" supported_from = EXCHANGE_2010_SP1Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ArchiveRoot (**kwargs)
- 
Expand source codeclass 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_ROOTThe root of the archive folders hierarchy. Not available on all mailboxes. Ancestors- RootOfHierarchy
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 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
- get
- get_default_folder
- get_distinguished
- get_events
- get_streaming_events
- 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 Audits (**kwargs)
- 
Expand source codeclass Audits(NonDeletableFolder): get_folder_allowed = FalseA mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var get_folder_allowed
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 BaseFolder (**kwargs)
- 
Expand source codeclass BaseFolder(RegisterMixIn, SearchableMixIn, SupportedVersionClassMixIn, metaclass=EWSMeta): """Base class for all classes that implement a folder.""" ELEMENT_NAME = "Folder" NAMESPACE = TNS # See https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/distinguishedfolderid DISTINGUISHED_FOLDER_ID = None # Default item type for this folder. See # https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxosfld/68a85898-84fe-43c4-b166-4711c13cdd61 CONTAINER_CLASS = None supported_item_models = ITEM_CLASSES # The Item types that this folder can contain. Default is all # Whether this folder type is allowed with the GetFolder service get_folder_allowed = True DEFAULT_FOLDER_TRAVERSAL_DEPTH = DEEP_FOLDERS DEFAULT_ITEM_TRAVERSAL_DEPTH = SHALLOW_ITEMS LOCALIZED_NAMES = {} # A map of (str)locale: (tuple)localized_folder_names ITEM_MODEL_MAP = {cls.response_tag(): cls for cls in ITEM_CLASSES} ID_ELEMENT_CLS = FolderId _id = IdElementField(field_uri="folder:FolderId", value_cls=ID_ELEMENT_CLS) _distinguished_id = IdElementField( field_uri="folder:DistinguishedFolderId", value_cls=DistinguishedFolderId, supported_from=EXCHANGE_2016 ) parent_folder_id = EWSElementField(field_uri="folder:ParentFolderId", value_cls=ParentFolderId, is_read_only=True) folder_class = CharField(field_uri="folder:FolderClass", is_required_after_save=True) name = CharField(field_uri="folder:DisplayName") total_count = IntegerField(field_uri="folder:TotalCount", is_read_only=True) child_folder_count = IntegerField(field_uri="folder:ChildFolderCount", is_read_only=True) unread_count = IntegerField(field_uri="folder:UnreadCount", is_read_only=True) __slots__ = "item_sync_state", "folder_sync_state" # Used to register extended properties INSERT_AFTER_FIELD = "child_folder_count" def __init__(self, **kwargs): self.item_sync_state = kwargs.pop("item_sync_state", None) self.folder_sync_state = kwargs.pop("folder_sync_state", None) super().__init__(**kwargs) if self._distinguished_id and not self._distinguished_id.mailbox and self.account: # Ensure that distinguished IDs have a mailbox, but don't override a custom mailbox (e.g. shared folders) self._distinguished_id.mailbox = Mailbox(email_address=self.account.primary_smtp_address) @property @abc.abstractmethod def account(self): """Return the account this folder belongs to""" @property @abc.abstractmethod def root(self): """Return the root folder this folder belongs to""" @property @abc.abstractmethod def parent(self): """Return the parent folder of this folder""" @property def is_distinguished(self): return self._distinguished_id or (self.DISTINGUISHED_FOLDER_ID and not self._id) @property def is_deletable(self): return not self.is_distinguished def clean(self, version=None): super().clean(version=version) # Set a default folder class for new folders. A folder class cannot be changed after saving. if self.id is None and self.folder_class is None: self.folder_class = self.CONTAINER_CLASS @property def children(self): # It's dangerous to return a generator here because we may then call methods on a child that result in the # cache being updated while it's iterated. return FolderCollection(account=self.account, folders=self.root.get_children(self)) @property def parts(self): parts = [self] f = self.parent while f: parts.insert(0, f) f = f.parent return parts @property def absolute(self): return "".join(f"/{p.name}" for p in self.parts) def _walk(self): for c in self.children: yield c yield from c.walk() def walk(self): return FolderCollection(account=self.account, folders=self._walk()) def _glob(self, pattern): split_pattern = pattern.split("/", maxsplit=1) head, tail = (split_pattern[0], None) if len(split_pattern) == 1 else split_pattern if head == "": # We got an absolute path. Restart globbing at root yield from self.root.glob(tail or "*") elif head == "..": # Relative path with reference to parent. Restart globbing at parent if not self.parent: raise ValueError("Already at top") yield from self.parent.glob(tail or "*") elif head == "**": # Match anything here or in any sub-folder at arbitrary depth for c in self.walk(): # fnmatch() may be case-sensitive depending on operating system: # force a case-insensitive match since case appears not to # matter for folders in Exchange if fnmatch(c.name.lower(), (tail or "*").lower()): yield c else: # Regular pattern for c in self.children: # See note above on fnmatch() case-sensitivity if not fnmatch(c.name.lower(), head.lower()): continue if tail is None: yield c continue yield from c.glob(tail) def glob(self, pattern): return FolderCollection(account=self.account, folders=self._glob(pattern)) def tree(self): """Return a string representation of the folder structure of this folder. Example: root ├── inbox │ └── todos └── archive ├── Last Job ├── exchangelib issues └── Mom """ tree = f"{self.name}\n" children = list(self.children) for i, c in enumerate(sorted(children, key=attrgetter("name")), start=1): nodes = c.tree().split("\n") for j, node in enumerate(nodes, start=1): if i != len(children) and j == 1: # Not the last child, but the first node, which is the name of the child tree += f"├── {node}\n" elif i != len(children) and j > 1: # Not the last child, and not name of child tree += f"│ {node}\n" elif i == len(children) and j == 1: # Not the last child, but the first node, which is the name of the child tree += f"└── {node}\n" else: # Last child and not name of child tree += f" {node}\n" return tree.strip() @classmethod def _get_distinguished(cls, folder): if not cls.DISTINGUISHED_FOLDER_ID: raise ValueError(f"Class {cls} must have a DISTINGUISHED_FOLDER_ID value") try: return cls.resolve(account=folder.account, folder=folder) except MISSING_FOLDER_ERRORS as e: raise ErrorFolderNotFound(f"Could not find distinguished folder {cls.DISTINGUISHED_FOLDER_ID!r} ({e})") @property def has_distinguished_name(self): return self.name and self.DISTINGUISHED_FOLDER_ID and self.name.lower() == self.DISTINGUISHED_FOLDER_ID.lower() @classmethod def localized_names(cls, locale): # Return localized names for a specific locale. If no locale-specific names exist, return the default names, # if any. return tuple(s.lower() for s in cls.LOCALIZED_NAMES.get(locale, cls.LOCALIZED_NAMES.get(None, [cls.__name__]))) @staticmethod def folder_cls_from_container_class(container_class): """Return a reasonable folder class given a container class, e.g. 'IPF.Note'. Don't iterate WELLKNOWN_FOLDERS because many folder classes have the same CONTAINER_CLASS. :param container_class: :return: """ from .known_folders import ( ApplicationData, Calendar, CompanyContacts, Contacts, ConversationSettings, CrawlerData, DlpPolicyEvaluation, EventCheckPoints, FreeBusyCache, GALContacts, Messages, OrganizationalContacts, PeopleCentricConversationBuddies, RecipientCache, RecoveryPoints, Reminders, RSSFeeds, Signal, SwssItems, Tasks, ) for folder_cls in ( ApplicationData, Calendar, CompanyContacts, Contacts, ConversationSettings, CrawlerData, DlpPolicyEvaluation, EventCheckPoints, FreeBusyCache, GALContacts, Messages, OrganizationalContacts, PeopleCentricConversationBuddies, RSSFeeds, RecipientCache, RecoveryPoints, Reminders, Signal, SwssItems, Tasks, ): if folder_cls.CONTAINER_CLASS == container_class: return folder_cls raise KeyError() @classmethod def item_model_from_tag(cls, tag): try: return cls.ITEM_MODEL_MAP[tag] except KeyError: raise ValueError(f"Item type {tag} was unexpected in a {cls.__name__} folder") @classmethod def allowed_item_fields(cls, version): # Return non-ID fields of all item classes allowed in this folder type fields = set() for item_model in cls.supported_item_models: fields.update(set(item_model.supported_fields(version=version))) return fields def validate_item_field(self, field, version): FolderCollection(account=self.account, folders=[self]).validate_item_field(field=field, version=version) def normalize_fields(self, fields): # Takes a list of fieldnames, Field or FieldPath objects pointing to item fields. Turns them into FieldPath # objects and adds internal timezone fields if necessary. Assume fields are already validated. fields = list(fields) has_start, has_end = False, False for i, field_path in enumerate(fields): # Allow both Field and FieldPath instances and string field paths as input if isinstance(field_path, str): field_path = FieldPath.from_string(field_path=field_path, folder=self) fields[i] = field_path elif isinstance(field_path, Field): field_path = FieldPath(field=field_path) fields[i] = field_path if field_path.field.name == "start": has_start = True elif field_path.field.name == "end": has_end = True # For CalendarItem items, we want to inject internal timezone fields. See also CalendarItem.clean() if CalendarItem in self.supported_item_models: meeting_tz_field, start_tz_field, end_tz_field = CalendarItem.timezone_fields() if self.account.version.build < EXCHANGE_2010: if has_start or has_end: fields.append(FieldPath(field=meeting_tz_field)) else: if has_start: fields.append(FieldPath(field=start_tz_field)) if has_end: fields.append(FieldPath(field=end_tz_field)) return fields @classmethod def get_item_field_by_fieldname(cls, fieldname): for item_model in cls.supported_item_models: with suppress(InvalidField): return item_model.get_field_by_fieldname(fieldname) raise InvalidField(f"{fieldname!r} is not a valid field name on {cls.supported_item_models}") def get(self, *args, **kwargs): return FolderCollection(account=self.account, folders=[self]).get(*args, **kwargs) def all(self): return FolderCollection(account=self.account, folders=[self]).all() def none(self): return FolderCollection(account=self.account, folders=[self]).none() def filter(self, *args, **kwargs): return FolderCollection(account=self.account, folders=[self]).filter(*args, **kwargs) def exclude(self, *args, **kwargs): return FolderCollection(account=self.account, folders=[self]).exclude(*args, **kwargs) def people(self): # No point in using a FolderCollection because FindPeople only supports one folder return FolderCollection(account=self.account, folders=[self]).people() def bulk_create(self, items, *args, **kwargs): return self.account.bulk_create(folder=self, items=items, *args, **kwargs) def save(self, update_fields=None): from ..services import CreateFolder, UpdateFolder if self.id is None: # New folder if update_fields: raise ValueError("'update_fields' is only valid for updates") res = CreateFolder(account=self.account).get(parent_folder=self.parent, folders=[self]) self._id = self.ID_ELEMENT_CLS(res.id, res.changekey) self.root.add_folder(self) # Add this folder to the cache return self # Update folder if not update_fields: # The fields to update was not specified explicitly. Update all fields where update is possible update_fields = [] for f in self.supported_fields(version=self.account.version): if f.is_read_only: # These cannot be changed continue if (f.is_required or f.is_required_after_save) and ( getattr(self, f.name) is None or (f.is_list and not getattr(self, f.name)) ): # These are required and cannot be deleted continue update_fields.append(f.name) res = UpdateFolder(account=self.account).get(folders=[(self, update_fields)]) folder_id, changekey = res.id, res.changekey if self.id != folder_id: raise ValueError("ID mismatch") # Don't check changekey value. It may not change on no-op updates self.changekey = changekey self.root.update_folder(self) # Update the folder in the cache return self def move(self, to_folder): from ..services import MoveFolder res = MoveFolder(account=self.account).get(folders=[self], to_folder=to_folder) folder_id, changekey = res.id, res.changekey if self.id != folder_id: raise ValueError("ID mismatch") # Don't check changekey value. It may not change on no-op moves self.changekey = changekey self.parent_folder_id = ParentFolderId(id=to_folder.id, changekey=to_folder.changekey) self.root.update_folder(self) # Update the folder in the cache def delete(self, delete_type=HARD_DELETE): from ..services import DeleteFolder DeleteFolder(account=self.account).get(folders=[self], delete_type=delete_type) self.root.remove_folder(self) # Remove the updated folder from the cache self._id = None def empty(self, delete_type=HARD_DELETE, delete_sub_folders=False): from ..services import EmptyFolder EmptyFolder(account=self.account).get( folders=[self], delete_type=delete_type, delete_sub_folders=delete_sub_folders ) if delete_sub_folders: # We don't know exactly what was deleted, so invalidate the entire folder cache to be safe self.root.clear_cache() def wipe(self, page_size=None, chunk_size=None, _seen=None, _level=0): # Recursively deletes all items in this folder, and all sub-folders and their content. Attempts to protect # distinguished folders from being deleted. Use with caution! from .known_folders import Audits _seen = _seen or set() if self.id in _seen: raise RecursionError(f"We already tried to wipe {self}") if _level > 16: raise RecursionError(f"Max recursion level reached: {_level}") _seen.add(self.id) if isinstance(self, Audits): # Shortcircuit because this folder can have many items that are all non-deletable log.warning("Cannot wipe audits folder %s", self) return if self.is_distinguished and "recoverableitems" in self.DISTINGUISHED_FOLDER_ID: log.warning("Cannot wipe recoverable items folder %s", self) return log.warning("Wiping %s", self) has_non_deletable_subfolders = any(not f.is_deletable for f in self.children) try: if has_non_deletable_subfolders: self.empty() else: self.empty(delete_sub_folders=True) except ErrorRecoverableItemsAccessDenied: log.warning("Access denied to %s. Skipping", self) return except DELETE_FOLDER_ERRORS: try: if has_non_deletable_subfolders: raise # We already tried this self.empty() except DELETE_FOLDER_ERRORS: log.warning("Not allowed to empty %s. Trying to delete items instead", self) kwargs = {} if page_size is not None: kwargs["page_size"] = page_size if chunk_size is not None: kwargs["chunk_size"] = chunk_size try: self.all().delete(**kwargs) except DELETE_FOLDER_ERRORS: log.warning("Not allowed to delete items in %s", self) _level += 1 for f in self.children: f.wipe(page_size=page_size, chunk_size=chunk_size, _seen=_seen, _level=_level) # Remove non-distinguished children that are empty and have no sub-folders if f.is_deletable and not f.children: log.warning("Deleting folder %s", f) try: f.delete() except ErrorDeleteDistinguishedFolder: log.warning("Tried to delete a distinguished folder (%s)", f) def test_access(self): """Does a simple FindItem to test (read) access to the folder. Maybe the account doesn't exist, maybe the service user doesn't have access to the calendar. This will throw the most common errors. """ self.all().exists() return True @classmethod def _kwargs_from_elem(cls, elem, account): # Check for 'DisplayName' element before collecting kwargs because that clears the elements has_name_elem = elem.find(cls.get_field_by_fieldname("name").response_tag()) is not None kwargs = {f.name: f.from_xml(elem=elem, account=account) for f in cls.FIELDS} if has_name_elem and not kwargs["name"]: # When we request the 'DisplayName' property, some folders may still be returned with an empty value. # Assign a default name to these folders. kwargs["name"] = cls.DISTINGUISHED_FOLDER_ID return kwargs def to_id(self): # Use self._distinguished_id as-is if we have it. This could be a DistinguishedFolderId with a mailbox pointing # to a shared mailbox. if self._distinguished_id: return self._distinguished_id if self._id: return self._id if not self.DISTINGUISHED_FOLDER_ID: raise ValueError(f"{self} must be a distinguished folder or have an ID") self._distinguished_id = DistinguishedFolderId( id=self.DISTINGUISHED_FOLDER_ID, mailbox=Mailbox(email_address=self.account.primary_smtp_address), ) return self._distinguished_id @classmethod def resolve(cls, account, folder): # Resolve a single folder folders = list(FolderCollection(account=account, folders=[folder]).resolve()) if not folders: raise ErrorFolderNotFound(f"Could not find folder {folder!r}") if len(folders) != 1: raise ValueError(f"Expected result length 1, but got {folders}") f = folders[0] if isinstance(f, Exception): raise f if f.__class__ != cls: raise ValueError(f"Expected folder {f!r} to be a {cls} instance") return f @require_id def refresh(self): fresh_folder = self.resolve(account=self.account, folder=self) if self.id != fresh_folder.id: raise ValueError("ID mismatch") # Apparently, the changekey may get updated for f in self.FIELDS: setattr(self, f.name, getattr(fresh_folder, f.name)) return self @require_id def get_user_configuration(self, name, properties=None): from ..services import GetUserConfiguration from ..services.get_user_configuration import ALL if properties is None: properties = ALL return GetUserConfiguration(account=self.account).get( user_configuration_name=UserConfigurationNameMNS(name=name, folder=self), properties=properties, ) @require_id def create_user_configuration(self, name, dictionary=None, xml_data=None, binary_data=None): from ..services import CreateUserConfiguration user_configuration = UserConfiguration( user_configuration_name=UserConfigurationName(name=name, folder=self), dictionary=dictionary, xml_data=xml_data, binary_data=binary_data, ) return CreateUserConfiguration(account=self.account).get(user_configuration=user_configuration) @require_id def update_user_configuration(self, name, dictionary=None, xml_data=None, binary_data=None): from ..services import UpdateUserConfiguration user_configuration = UserConfiguration( user_configuration_name=UserConfigurationName(name=name, folder=self), dictionary=dictionary, xml_data=xml_data, binary_data=binary_data, ) return UpdateUserConfiguration(account=self.account).get(user_configuration=user_configuration) @require_id def delete_user_configuration(self, name): from ..services import DeleteUserConfiguration return DeleteUserConfiguration(account=self.account).get( user_configuration_name=UserConfigurationNameMNS(name=name, folder=self) ) @require_id def subscribe_to_pull(self, event_types=None, watermark=None, timeout=60): """Create a pull subscription. :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPull.EVENT_TYPES :param watermark: An event bookmark as returned by some sync services :param timeout: Timeout of the subscription, in minutes. Timeout is reset when the server receives a GetEvents request for this subscription. :return: The subscription ID and a watermark """ from ..services import SubscribeToPull if event_types is None: event_types = SubscribeToPull.EVENT_TYPES return FolderCollection(account=self.account, folders=[self]).subscribe_to_pull( event_types=event_types, watermark=watermark, timeout=timeout, ) @require_id def subscribe_to_push(self, callback_url, event_types=None, watermark=None, status_frequency=1): """Create a push subscription. :param callback_url: A client-defined URL that the server will call :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPush.EVENT_TYPES :param watermark: An event bookmark as returned by some sync services :param status_frequency: The frequency, in minutes, that the callback URL will be called with. :return: The subscription ID and a watermark """ from ..services import SubscribeToPush if event_types is None: event_types = SubscribeToPush.EVENT_TYPES return FolderCollection(account=self.account, folders=[self]).subscribe_to_push( event_types=event_types, watermark=watermark, status_frequency=status_frequency, callback_url=callback_url, ) @require_id def subscribe_to_streaming(self, event_types=None): """Create a streaming subscription. :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPush.EVENT_TYPES :return: The subscription ID """ from ..services import SubscribeToStreaming if event_types is None: event_types = SubscribeToStreaming.EVENT_TYPES return FolderCollection(account=self.account, folders=[self]).subscribe_to_streaming(event_types=event_types) @require_id def pull_subscription(self, **kwargs): return PullSubscription(target=self, **kwargs) @require_id def push_subscription(self, **kwargs): return PushSubscription(target=self, **kwargs) @require_id def streaming_subscription(self, **kwargs): return StreamingSubscription(target=self, **kwargs) def unsubscribe(self, subscription_id): """Unsubscribe. Only applies to pull and streaming notifications. :param subscription_id: A subscription ID as acquired by .subscribe_to_[pull|streaming]() :return: True This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. """ from ..services import Unsubscribe return Unsubscribe(account=self.account).get(subscription_id=subscription_id) def sync_items(self, sync_state=None, only_fields=None, ignore=None, max_changes_returned=None, sync_scope=None): """Return all item changes to a folder, as a generator. If sync_state is specified, get all item changes after this sync state. After fully consuming the generator, self.item_sync_state will hold the new sync state. :param sync_state: The state of the sync. Returned by a successful call to the SyncFolderItems service. :param only_fields: A list of string or FieldPath items specifying the fields to fetch. Default to all fields :param ignore: A list of Item IDs to ignore in the sync :param max_changes_returned: The max number of change :param sync_scope: Specify whether to return just items, or items and folder associated information. Possible values are specified in SyncFolderItems.SYNC_SCOPES :return: A generator of (change_type, item) tuples """ if not sync_state: sync_state = self.item_sync_state try: yield from FolderCollection(account=self.account, folders=[self]).sync_items( sync_state=sync_state, only_fields=only_fields, ignore=ignore, max_changes_returned=max_changes_returned, sync_scope=sync_scope, ) except SyncCompleted as e: # Set the new sync state on the folder instance self.item_sync_state = e.sync_state def sync_hierarchy(self, sync_state=None, only_fields=None): """Return all folder changes to a folder hierarchy, as a generator. If sync_state is specified, get all folder changes after this sync state. After fully consuming the generator, self.folder_sync_state will hold the new sync state. :param sync_state: The state of the sync. Returned by a successful call to the SyncFolderItems service. :param only_fields: A list of string or FieldPath items specifying the fields to fetch. Default to all fields :return: """ if not sync_state: sync_state = self.folder_sync_state try: yield from FolderCollection(account=self.account, folders=[self]).sync_hierarchy( sync_state=sync_state, only_fields=only_fields, ) except SyncCompleted as e: # Set the new sync state on the folder instance self.folder_sync_state = e.sync_state def get_events(self, subscription_id, watermark): """Get events since the given watermark. Non-blocking. :param subscription_id: A subscription ID as acquired by .subscribe_to_[pull|push]() :param watermark: Either the watermark from the subscription, or as returned by the last .get_events() call. :return: A Notification object containing a list of events This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. """ from ..services import GetEvents svc = GetEvents(account=self.account) while True: notification = svc.get(subscription_id=subscription_id, watermark=watermark) yield notification if not notification.more_events: break def get_streaming_events(self, subscription_id_or_ids, connection_timeout=1, max_notifications_returned=None): """Get events since the subscription was created, in streaming mode. This method will block as many minutes as specified by 'connection_timeout'. :param subscription_id_or_ids: A subscription ID, or list of IDs, as acquired by .subscribe_to_streaming() :param connection_timeout: Timeout of the connection, in minutes. The connection is closed after this timeout is reached. :param max_notifications_returned: If specified, will exit after receiving this number of notifications :return: A generator of Notification objects, each containing a list of events This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. """ from ..services import GetStreamingEvents svc = GetStreamingEvents(account=self.account) subscription_ids = ( subscription_id_or_ids if is_iterable(subscription_id_or_ids, generators_allowed=True) else [subscription_id_or_ids] ) for i, notification in enumerate( svc.call(subscription_ids=subscription_ids, connection_timeout=connection_timeout), start=1 ): yield notification if max_notifications_returned and i >= max_notifications_returned: svc.stop_streaming() break def __floordiv__(self, other): """Support the some_folder // 'child_folder' // 'child_of_child_folder' navigation syntax. Works like as __truediv__ but does not touch the folder cache. This is useful if the folder hierarchy contains a huge number of folders, and you don't want to fetch them all :param other: :return: """ if other == "..": raise ValueError("Cannot get parent without a folder cache") if other == ".": return self # Assume an exact match on the folder name in a shallow search will only return at most one folder try: return SingleFolderQuerySet(account=self.account, folder=self).depth(SHALLOW_FOLDERS).get(name=other) except DoesNotExist: raise ErrorFolderNotFound(f"No subfolder with name {other!r}") def __truediv__(self, other): """Support the some_folder / 'child_folder' / 'child_of_child_folder' navigation syntax.""" if other == "..": if not self.parent: raise ValueError("Already at top") return self.parent if other == ".": return self for c in self.children: # Folders are case-insensitive server-side. Let's do that here as well. if c.name.lower() == other.lower(): return c raise ErrorFolderNotFound(f"No subfolder with name {other!r}") def __repr__(self): return self.__class__.__name__ + repr( ( self.root, self.name, self.total_count, self.unread_count, self.child_folder_count, self.folder_class, self.id, self.changekey, ) ) def __str__(self): return f"{self.__class__.__name__} ({self.name})"Base class for all classes that implement a folder. AncestorsSubclassesClass variables- var CONTAINER_CLASS
- var DEFAULT_FOLDER_TRAVERSAL_DEPTH
- var DEFAULT_ITEM_TRAVERSAL_DEPTH
- var DISTINGUISHED_FOLDER_ID
- var ELEMENT_NAME
- var FIELDS
- var ID_ELEMENT_CLS
- var INSERT_AFTER_FIELD
- var ITEM_MODEL_MAP
- var LOCALIZED_NAMES
- var NAMESPACE
- var get_folder_allowed
- var supported_item_models
 Static methods- def allowed_item_fields(version)
- def folder_cls_from_container_class(container_class)
- 
Expand source code@staticmethod def folder_cls_from_container_class(container_class): """Return a reasonable folder class given a container class, e.g. 'IPF.Note'. Don't iterate WELLKNOWN_FOLDERS because many folder classes have the same CONTAINER_CLASS. :param container_class: :return: """ from .known_folders import ( ApplicationData, Calendar, CompanyContacts, Contacts, ConversationSettings, CrawlerData, DlpPolicyEvaluation, EventCheckPoints, FreeBusyCache, GALContacts, Messages, OrganizationalContacts, PeopleCentricConversationBuddies, RecipientCache, RecoveryPoints, Reminders, RSSFeeds, Signal, SwssItems, Tasks, ) for folder_cls in ( ApplicationData, Calendar, CompanyContacts, Contacts, ConversationSettings, CrawlerData, DlpPolicyEvaluation, EventCheckPoints, FreeBusyCache, GALContacts, Messages, OrganizationalContacts, PeopleCentricConversationBuddies, RSSFeeds, RecipientCache, RecoveryPoints, Reminders, Signal, SwssItems, Tasks, ): if folder_cls.CONTAINER_CLASS == container_class: return folder_cls raise KeyError()Return a reasonable folder class given a container class, e.g. 'IPF.Note'. Don't iterate WELLKNOWN_FOLDERS because many folder classes have the same CONTAINER_CLASS. :param container_class: :return: 
- def get_item_field_by_fieldname(fieldname)
- def item_model_from_tag(tag)
- def localized_names(locale)
- def resolve(account, folder)
 Instance variables- prop absolute
- 
Expand source code@property def absolute(self): return "".join(f"/{p.name}" for p in self.parts)
- prop account
- 
Expand source code@property @abc.abstractmethod def account(self): """Return the account this folder belongs to"""Return the account this folder belongs to 
- var child_folder_count
- prop children
- 
Expand source code@property def children(self): # It's dangerous to return a generator here because we may then call methods on a child that result in the # cache being updated while it's iterated. return FolderCollection(account=self.account, folders=self.root.get_children(self))
- var folder_class
- var folder_sync_state
- 
Expand source codeclass BaseFolder(RegisterMixIn, SearchableMixIn, SupportedVersionClassMixIn, metaclass=EWSMeta): """Base class for all classes that implement a folder.""" ELEMENT_NAME = "Folder" NAMESPACE = TNS # See https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/distinguishedfolderid DISTINGUISHED_FOLDER_ID = None # Default item type for this folder. See # https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxosfld/68a85898-84fe-43c4-b166-4711c13cdd61 CONTAINER_CLASS = None supported_item_models = ITEM_CLASSES # The Item types that this folder can contain. Default is all # Whether this folder type is allowed with the GetFolder service get_folder_allowed = True DEFAULT_FOLDER_TRAVERSAL_DEPTH = DEEP_FOLDERS DEFAULT_ITEM_TRAVERSAL_DEPTH = SHALLOW_ITEMS LOCALIZED_NAMES = {} # A map of (str)locale: (tuple)localized_folder_names ITEM_MODEL_MAP = {cls.response_tag(): cls for cls in ITEM_CLASSES} ID_ELEMENT_CLS = FolderId _id = IdElementField(field_uri="folder:FolderId", value_cls=ID_ELEMENT_CLS) _distinguished_id = IdElementField( field_uri="folder:DistinguishedFolderId", value_cls=DistinguishedFolderId, supported_from=EXCHANGE_2016 ) parent_folder_id = EWSElementField(field_uri="folder:ParentFolderId", value_cls=ParentFolderId, is_read_only=True) folder_class = CharField(field_uri="folder:FolderClass", is_required_after_save=True) name = CharField(field_uri="folder:DisplayName") total_count = IntegerField(field_uri="folder:TotalCount", is_read_only=True) child_folder_count = IntegerField(field_uri="folder:ChildFolderCount", is_read_only=True) unread_count = IntegerField(field_uri="folder:UnreadCount", is_read_only=True) __slots__ = "item_sync_state", "folder_sync_state" # Used to register extended properties INSERT_AFTER_FIELD = "child_folder_count" def __init__(self, **kwargs): self.item_sync_state = kwargs.pop("item_sync_state", None) self.folder_sync_state = kwargs.pop("folder_sync_state", None) super().__init__(**kwargs) if self._distinguished_id and not self._distinguished_id.mailbox and self.account: # Ensure that distinguished IDs have a mailbox, but don't override a custom mailbox (e.g. shared folders) self._distinguished_id.mailbox = Mailbox(email_address=self.account.primary_smtp_address) @property @abc.abstractmethod def account(self): """Return the account this folder belongs to""" @property @abc.abstractmethod def root(self): """Return the root folder this folder belongs to""" @property @abc.abstractmethod def parent(self): """Return the parent folder of this folder""" @property def is_distinguished(self): return self._distinguished_id or (self.DISTINGUISHED_FOLDER_ID and not self._id) @property def is_deletable(self): return not self.is_distinguished def clean(self, version=None): super().clean(version=version) # Set a default folder class for new folders. A folder class cannot be changed after saving. if self.id is None and self.folder_class is None: self.folder_class = self.CONTAINER_CLASS @property def children(self): # It's dangerous to return a generator here because we may then call methods on a child that result in the # cache being updated while it's iterated. return FolderCollection(account=self.account, folders=self.root.get_children(self)) @property def parts(self): parts = [self] f = self.parent while f: parts.insert(0, f) f = f.parent return parts @property def absolute(self): return "".join(f"/{p.name}" for p in self.parts) def _walk(self): for c in self.children: yield c yield from c.walk() def walk(self): return FolderCollection(account=self.account, folders=self._walk()) def _glob(self, pattern): split_pattern = pattern.split("/", maxsplit=1) head, tail = (split_pattern[0], None) if len(split_pattern) == 1 else split_pattern if head == "": # We got an absolute path. Restart globbing at root yield from self.root.glob(tail or "*") elif head == "..": # Relative path with reference to parent. Restart globbing at parent if not self.parent: raise ValueError("Already at top") yield from self.parent.glob(tail or "*") elif head == "**": # Match anything here or in any sub-folder at arbitrary depth for c in self.walk(): # fnmatch() may be case-sensitive depending on operating system: # force a case-insensitive match since case appears not to # matter for folders in Exchange if fnmatch(c.name.lower(), (tail or "*").lower()): yield c else: # Regular pattern for c in self.children: # See note above on fnmatch() case-sensitivity if not fnmatch(c.name.lower(), head.lower()): continue if tail is None: yield c continue yield from c.glob(tail) def glob(self, pattern): return FolderCollection(account=self.account, folders=self._glob(pattern)) def tree(self): """Return a string representation of the folder structure of this folder. Example: root ├── inbox │ └── todos └── archive ├── Last Job ├── exchangelib issues └── Mom """ tree = f"{self.name}\n" children = list(self.children) for i, c in enumerate(sorted(children, key=attrgetter("name")), start=1): nodes = c.tree().split("\n") for j, node in enumerate(nodes, start=1): if i != len(children) and j == 1: # Not the last child, but the first node, which is the name of the child tree += f"├── {node}\n" elif i != len(children) and j > 1: # Not the last child, and not name of child tree += f"│ {node}\n" elif i == len(children) and j == 1: # Not the last child, but the first node, which is the name of the child tree += f"└── {node}\n" else: # Last child and not name of child tree += f" {node}\n" return tree.strip() @classmethod def _get_distinguished(cls, folder): if not cls.DISTINGUISHED_FOLDER_ID: raise ValueError(f"Class {cls} must have a DISTINGUISHED_FOLDER_ID value") try: return cls.resolve(account=folder.account, folder=folder) except MISSING_FOLDER_ERRORS as e: raise ErrorFolderNotFound(f"Could not find distinguished folder {cls.DISTINGUISHED_FOLDER_ID!r} ({e})") @property def has_distinguished_name(self): return self.name and self.DISTINGUISHED_FOLDER_ID and self.name.lower() == self.DISTINGUISHED_FOLDER_ID.lower() @classmethod def localized_names(cls, locale): # Return localized names for a specific locale. If no locale-specific names exist, return the default names, # if any. return tuple(s.lower() for s in cls.LOCALIZED_NAMES.get(locale, cls.LOCALIZED_NAMES.get(None, [cls.__name__]))) @staticmethod def folder_cls_from_container_class(container_class): """Return a reasonable folder class given a container class, e.g. 'IPF.Note'. Don't iterate WELLKNOWN_FOLDERS because many folder classes have the same CONTAINER_CLASS. :param container_class: :return: """ from .known_folders import ( ApplicationData, Calendar, CompanyContacts, Contacts, ConversationSettings, CrawlerData, DlpPolicyEvaluation, EventCheckPoints, FreeBusyCache, GALContacts, Messages, OrganizationalContacts, PeopleCentricConversationBuddies, RecipientCache, RecoveryPoints, Reminders, RSSFeeds, Signal, SwssItems, Tasks, ) for folder_cls in ( ApplicationData, Calendar, CompanyContacts, Contacts, ConversationSettings, CrawlerData, DlpPolicyEvaluation, EventCheckPoints, FreeBusyCache, GALContacts, Messages, OrganizationalContacts, PeopleCentricConversationBuddies, RSSFeeds, RecipientCache, RecoveryPoints, Reminders, Signal, SwssItems, Tasks, ): if folder_cls.CONTAINER_CLASS == container_class: return folder_cls raise KeyError() @classmethod def item_model_from_tag(cls, tag): try: return cls.ITEM_MODEL_MAP[tag] except KeyError: raise ValueError(f"Item type {tag} was unexpected in a {cls.__name__} folder") @classmethod def allowed_item_fields(cls, version): # Return non-ID fields of all item classes allowed in this folder type fields = set() for item_model in cls.supported_item_models: fields.update(set(item_model.supported_fields(version=version))) return fields def validate_item_field(self, field, version): FolderCollection(account=self.account, folders=[self]).validate_item_field(field=field, version=version) def normalize_fields(self, fields): # Takes a list of fieldnames, Field or FieldPath objects pointing to item fields. Turns them into FieldPath # objects and adds internal timezone fields if necessary. Assume fields are already validated. fields = list(fields) has_start, has_end = False, False for i, field_path in enumerate(fields): # Allow both Field and FieldPath instances and string field paths as input if isinstance(field_path, str): field_path = FieldPath.from_string(field_path=field_path, folder=self) fields[i] = field_path elif isinstance(field_path, Field): field_path = FieldPath(field=field_path) fields[i] = field_path if field_path.field.name == "start": has_start = True elif field_path.field.name == "end": has_end = True # For CalendarItem items, we want to inject internal timezone fields. See also CalendarItem.clean() if CalendarItem in self.supported_item_models: meeting_tz_field, start_tz_field, end_tz_field = CalendarItem.timezone_fields() if self.account.version.build < EXCHANGE_2010: if has_start or has_end: fields.append(FieldPath(field=meeting_tz_field)) else: if has_start: fields.append(FieldPath(field=start_tz_field)) if has_end: fields.append(FieldPath(field=end_tz_field)) return fields @classmethod def get_item_field_by_fieldname(cls, fieldname): for item_model in cls.supported_item_models: with suppress(InvalidField): return item_model.get_field_by_fieldname(fieldname) raise InvalidField(f"{fieldname!r} is not a valid field name on {cls.supported_item_models}") def get(self, *args, **kwargs): return FolderCollection(account=self.account, folders=[self]).get(*args, **kwargs) def all(self): return FolderCollection(account=self.account, folders=[self]).all() def none(self): return FolderCollection(account=self.account, folders=[self]).none() def filter(self, *args, **kwargs): return FolderCollection(account=self.account, folders=[self]).filter(*args, **kwargs) def exclude(self, *args, **kwargs): return FolderCollection(account=self.account, folders=[self]).exclude(*args, **kwargs) def people(self): # No point in using a FolderCollection because FindPeople only supports one folder return FolderCollection(account=self.account, folders=[self]).people() def bulk_create(self, items, *args, **kwargs): return self.account.bulk_create(folder=self, items=items, *args, **kwargs) def save(self, update_fields=None): from ..services import CreateFolder, UpdateFolder if self.id is None: # New folder if update_fields: raise ValueError("'update_fields' is only valid for updates") res = CreateFolder(account=self.account).get(parent_folder=self.parent, folders=[self]) self._id = self.ID_ELEMENT_CLS(res.id, res.changekey) self.root.add_folder(self) # Add this folder to the cache return self # Update folder if not update_fields: # The fields to update was not specified explicitly. Update all fields where update is possible update_fields = [] for f in self.supported_fields(version=self.account.version): if f.is_read_only: # These cannot be changed continue if (f.is_required or f.is_required_after_save) and ( getattr(self, f.name) is None or (f.is_list and not getattr(self, f.name)) ): # These are required and cannot be deleted continue update_fields.append(f.name) res = UpdateFolder(account=self.account).get(folders=[(self, update_fields)]) folder_id, changekey = res.id, res.changekey if self.id != folder_id: raise ValueError("ID mismatch") # Don't check changekey value. It may not change on no-op updates self.changekey = changekey self.root.update_folder(self) # Update the folder in the cache return self def move(self, to_folder): from ..services import MoveFolder res = MoveFolder(account=self.account).get(folders=[self], to_folder=to_folder) folder_id, changekey = res.id, res.changekey if self.id != folder_id: raise ValueError("ID mismatch") # Don't check changekey value. It may not change on no-op moves self.changekey = changekey self.parent_folder_id = ParentFolderId(id=to_folder.id, changekey=to_folder.changekey) self.root.update_folder(self) # Update the folder in the cache def delete(self, delete_type=HARD_DELETE): from ..services import DeleteFolder DeleteFolder(account=self.account).get(folders=[self], delete_type=delete_type) self.root.remove_folder(self) # Remove the updated folder from the cache self._id = None def empty(self, delete_type=HARD_DELETE, delete_sub_folders=False): from ..services import EmptyFolder EmptyFolder(account=self.account).get( folders=[self], delete_type=delete_type, delete_sub_folders=delete_sub_folders ) if delete_sub_folders: # We don't know exactly what was deleted, so invalidate the entire folder cache to be safe self.root.clear_cache() def wipe(self, page_size=None, chunk_size=None, _seen=None, _level=0): # Recursively deletes all items in this folder, and all sub-folders and their content. Attempts to protect # distinguished folders from being deleted. Use with caution! from .known_folders import Audits _seen = _seen or set() if self.id in _seen: raise RecursionError(f"We already tried to wipe {self}") if _level > 16: raise RecursionError(f"Max recursion level reached: {_level}") _seen.add(self.id) if isinstance(self, Audits): # Shortcircuit because this folder can have many items that are all non-deletable log.warning("Cannot wipe audits folder %s", self) return if self.is_distinguished and "recoverableitems" in self.DISTINGUISHED_FOLDER_ID: log.warning("Cannot wipe recoverable items folder %s", self) return log.warning("Wiping %s", self) has_non_deletable_subfolders = any(not f.is_deletable for f in self.children) try: if has_non_deletable_subfolders: self.empty() else: self.empty(delete_sub_folders=True) except ErrorRecoverableItemsAccessDenied: log.warning("Access denied to %s. Skipping", self) return except DELETE_FOLDER_ERRORS: try: if has_non_deletable_subfolders: raise # We already tried this self.empty() except DELETE_FOLDER_ERRORS: log.warning("Not allowed to empty %s. Trying to delete items instead", self) kwargs = {} if page_size is not None: kwargs["page_size"] = page_size if chunk_size is not None: kwargs["chunk_size"] = chunk_size try: self.all().delete(**kwargs) except DELETE_FOLDER_ERRORS: log.warning("Not allowed to delete items in %s", self) _level += 1 for f in self.children: f.wipe(page_size=page_size, chunk_size=chunk_size, _seen=_seen, _level=_level) # Remove non-distinguished children that are empty and have no sub-folders if f.is_deletable and not f.children: log.warning("Deleting folder %s", f) try: f.delete() except ErrorDeleteDistinguishedFolder: log.warning("Tried to delete a distinguished folder (%s)", f) def test_access(self): """Does a simple FindItem to test (read) access to the folder. Maybe the account doesn't exist, maybe the service user doesn't have access to the calendar. This will throw the most common errors. """ self.all().exists() return True @classmethod def _kwargs_from_elem(cls, elem, account): # Check for 'DisplayName' element before collecting kwargs because that clears the elements has_name_elem = elem.find(cls.get_field_by_fieldname("name").response_tag()) is not None kwargs = {f.name: f.from_xml(elem=elem, account=account) for f in cls.FIELDS} if has_name_elem and not kwargs["name"]: # When we request the 'DisplayName' property, some folders may still be returned with an empty value. # Assign a default name to these folders. kwargs["name"] = cls.DISTINGUISHED_FOLDER_ID return kwargs def to_id(self): # Use self._distinguished_id as-is if we have it. This could be a DistinguishedFolderId with a mailbox pointing # to a shared mailbox. if self._distinguished_id: return self._distinguished_id if self._id: return self._id if not self.DISTINGUISHED_FOLDER_ID: raise ValueError(f"{self} must be a distinguished folder or have an ID") self._distinguished_id = DistinguishedFolderId( id=self.DISTINGUISHED_FOLDER_ID, mailbox=Mailbox(email_address=self.account.primary_smtp_address), ) return self._distinguished_id @classmethod def resolve(cls, account, folder): # Resolve a single folder folders = list(FolderCollection(account=account, folders=[folder]).resolve()) if not folders: raise ErrorFolderNotFound(f"Could not find folder {folder!r}") if len(folders) != 1: raise ValueError(f"Expected result length 1, but got {folders}") f = folders[0] if isinstance(f, Exception): raise f if f.__class__ != cls: raise ValueError(f"Expected folder {f!r} to be a {cls} instance") return f @require_id def refresh(self): fresh_folder = self.resolve(account=self.account, folder=self) if self.id != fresh_folder.id: raise ValueError("ID mismatch") # Apparently, the changekey may get updated for f in self.FIELDS: setattr(self, f.name, getattr(fresh_folder, f.name)) return self @require_id def get_user_configuration(self, name, properties=None): from ..services import GetUserConfiguration from ..services.get_user_configuration import ALL if properties is None: properties = ALL return GetUserConfiguration(account=self.account).get( user_configuration_name=UserConfigurationNameMNS(name=name, folder=self), properties=properties, ) @require_id def create_user_configuration(self, name, dictionary=None, xml_data=None, binary_data=None): from ..services import CreateUserConfiguration user_configuration = UserConfiguration( user_configuration_name=UserConfigurationName(name=name, folder=self), dictionary=dictionary, xml_data=xml_data, binary_data=binary_data, ) return CreateUserConfiguration(account=self.account).get(user_configuration=user_configuration) @require_id def update_user_configuration(self, name, dictionary=None, xml_data=None, binary_data=None): from ..services import UpdateUserConfiguration user_configuration = UserConfiguration( user_configuration_name=UserConfigurationName(name=name, folder=self), dictionary=dictionary, xml_data=xml_data, binary_data=binary_data, ) return UpdateUserConfiguration(account=self.account).get(user_configuration=user_configuration) @require_id def delete_user_configuration(self, name): from ..services import DeleteUserConfiguration return DeleteUserConfiguration(account=self.account).get( user_configuration_name=UserConfigurationNameMNS(name=name, folder=self) ) @require_id def subscribe_to_pull(self, event_types=None, watermark=None, timeout=60): """Create a pull subscription. :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPull.EVENT_TYPES :param watermark: An event bookmark as returned by some sync services :param timeout: Timeout of the subscription, in minutes. Timeout is reset when the server receives a GetEvents request for this subscription. :return: The subscription ID and a watermark """ from ..services import SubscribeToPull if event_types is None: event_types = SubscribeToPull.EVENT_TYPES return FolderCollection(account=self.account, folders=[self]).subscribe_to_pull( event_types=event_types, watermark=watermark, timeout=timeout, ) @require_id def subscribe_to_push(self, callback_url, event_types=None, watermark=None, status_frequency=1): """Create a push subscription. :param callback_url: A client-defined URL that the server will call :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPush.EVENT_TYPES :param watermark: An event bookmark as returned by some sync services :param status_frequency: The frequency, in minutes, that the callback URL will be called with. :return: The subscription ID and a watermark """ from ..services import SubscribeToPush if event_types is None: event_types = SubscribeToPush.EVENT_TYPES return FolderCollection(account=self.account, folders=[self]).subscribe_to_push( event_types=event_types, watermark=watermark, status_frequency=status_frequency, callback_url=callback_url, ) @require_id def subscribe_to_streaming(self, event_types=None): """Create a streaming subscription. :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPush.EVENT_TYPES :return: The subscription ID """ from ..services import SubscribeToStreaming if event_types is None: event_types = SubscribeToStreaming.EVENT_TYPES return FolderCollection(account=self.account, folders=[self]).subscribe_to_streaming(event_types=event_types) @require_id def pull_subscription(self, **kwargs): return PullSubscription(target=self, **kwargs) @require_id def push_subscription(self, **kwargs): return PushSubscription(target=self, **kwargs) @require_id def streaming_subscription(self, **kwargs): return StreamingSubscription(target=self, **kwargs) def unsubscribe(self, subscription_id): """Unsubscribe. Only applies to pull and streaming notifications. :param subscription_id: A subscription ID as acquired by .subscribe_to_[pull|streaming]() :return: True This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. """ from ..services import Unsubscribe return Unsubscribe(account=self.account).get(subscription_id=subscription_id) def sync_items(self, sync_state=None, only_fields=None, ignore=None, max_changes_returned=None, sync_scope=None): """Return all item changes to a folder, as a generator. If sync_state is specified, get all item changes after this sync state. After fully consuming the generator, self.item_sync_state will hold the new sync state. :param sync_state: The state of the sync. Returned by a successful call to the SyncFolderItems service. :param only_fields: A list of string or FieldPath items specifying the fields to fetch. Default to all fields :param ignore: A list of Item IDs to ignore in the sync :param max_changes_returned: The max number of change :param sync_scope: Specify whether to return just items, or items and folder associated information. Possible values are specified in SyncFolderItems.SYNC_SCOPES :return: A generator of (change_type, item) tuples """ if not sync_state: sync_state = self.item_sync_state try: yield from FolderCollection(account=self.account, folders=[self]).sync_items( sync_state=sync_state, only_fields=only_fields, ignore=ignore, max_changes_returned=max_changes_returned, sync_scope=sync_scope, ) except SyncCompleted as e: # Set the new sync state on the folder instance self.item_sync_state = e.sync_state def sync_hierarchy(self, sync_state=None, only_fields=None): """Return all folder changes to a folder hierarchy, as a generator. If sync_state is specified, get all folder changes after this sync state. After fully consuming the generator, self.folder_sync_state will hold the new sync state. :param sync_state: The state of the sync. Returned by a successful call to the SyncFolderItems service. :param only_fields: A list of string or FieldPath items specifying the fields to fetch. Default to all fields :return: """ if not sync_state: sync_state = self.folder_sync_state try: yield from FolderCollection(account=self.account, folders=[self]).sync_hierarchy( sync_state=sync_state, only_fields=only_fields, ) except SyncCompleted as e: # Set the new sync state on the folder instance self.folder_sync_state = e.sync_state def get_events(self, subscription_id, watermark): """Get events since the given watermark. Non-blocking. :param subscription_id: A subscription ID as acquired by .subscribe_to_[pull|push]() :param watermark: Either the watermark from the subscription, or as returned by the last .get_events() call. :return: A Notification object containing a list of events This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. """ from ..services import GetEvents svc = GetEvents(account=self.account) while True: notification = svc.get(subscription_id=subscription_id, watermark=watermark) yield notification if not notification.more_events: break def get_streaming_events(self, subscription_id_or_ids, connection_timeout=1, max_notifications_returned=None): """Get events since the subscription was created, in streaming mode. This method will block as many minutes as specified by 'connection_timeout'. :param subscription_id_or_ids: A subscription ID, or list of IDs, as acquired by .subscribe_to_streaming() :param connection_timeout: Timeout of the connection, in minutes. The connection is closed after this timeout is reached. :param max_notifications_returned: If specified, will exit after receiving this number of notifications :return: A generator of Notification objects, each containing a list of events This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. """ from ..services import GetStreamingEvents svc = GetStreamingEvents(account=self.account) subscription_ids = ( subscription_id_or_ids if is_iterable(subscription_id_or_ids, generators_allowed=True) else [subscription_id_or_ids] ) for i, notification in enumerate( svc.call(subscription_ids=subscription_ids, connection_timeout=connection_timeout), start=1 ): yield notification if max_notifications_returned and i >= max_notifications_returned: svc.stop_streaming() break def __floordiv__(self, other): """Support the some_folder // 'child_folder' // 'child_of_child_folder' navigation syntax. Works like as __truediv__ but does not touch the folder cache. This is useful if the folder hierarchy contains a huge number of folders, and you don't want to fetch them all :param other: :return: """ if other == "..": raise ValueError("Cannot get parent without a folder cache") if other == ".": return self # Assume an exact match on the folder name in a shallow search will only return at most one folder try: return SingleFolderQuerySet(account=self.account, folder=self).depth(SHALLOW_FOLDERS).get(name=other) except DoesNotExist: raise ErrorFolderNotFound(f"No subfolder with name {other!r}") def __truediv__(self, other): """Support the some_folder / 'child_folder' / 'child_of_child_folder' navigation syntax.""" if other == "..": if not self.parent: raise ValueError("Already at top") return self.parent if other == ".": return self for c in self.children: # Folders are case-insensitive server-side. Let's do that here as well. if c.name.lower() == other.lower(): return c raise ErrorFolderNotFound(f"No subfolder with name {other!r}") def __repr__(self): return self.__class__.__name__ + repr( ( self.root, self.name, self.total_count, self.unread_count, self.child_folder_count, self.folder_class, self.id, self.changekey, ) ) def __str__(self): return f"{self.__class__.__name__} ({self.name})"
- prop has_distinguished_name
- 
Expand source code@property def has_distinguished_name(self): return self.name and self.DISTINGUISHED_FOLDER_ID and self.name.lower() == self.DISTINGUISHED_FOLDER_ID.lower()
- prop is_deletable
- 
Expand source code@property def is_deletable(self): return not self.is_distinguished
- prop is_distinguished
- 
Expand source code@property def is_distinguished(self): return self._distinguished_id or (self.DISTINGUISHED_FOLDER_ID and not self._id)
- var item_sync_state
- 
Expand source codeclass BaseFolder(RegisterMixIn, SearchableMixIn, SupportedVersionClassMixIn, metaclass=EWSMeta): """Base class for all classes that implement a folder.""" ELEMENT_NAME = "Folder" NAMESPACE = TNS # See https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/distinguishedfolderid DISTINGUISHED_FOLDER_ID = None # Default item type for this folder. See # https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxosfld/68a85898-84fe-43c4-b166-4711c13cdd61 CONTAINER_CLASS = None supported_item_models = ITEM_CLASSES # The Item types that this folder can contain. Default is all # Whether this folder type is allowed with the GetFolder service get_folder_allowed = True DEFAULT_FOLDER_TRAVERSAL_DEPTH = DEEP_FOLDERS DEFAULT_ITEM_TRAVERSAL_DEPTH = SHALLOW_ITEMS LOCALIZED_NAMES = {} # A map of (str)locale: (tuple)localized_folder_names ITEM_MODEL_MAP = {cls.response_tag(): cls for cls in ITEM_CLASSES} ID_ELEMENT_CLS = FolderId _id = IdElementField(field_uri="folder:FolderId", value_cls=ID_ELEMENT_CLS) _distinguished_id = IdElementField( field_uri="folder:DistinguishedFolderId", value_cls=DistinguishedFolderId, supported_from=EXCHANGE_2016 ) parent_folder_id = EWSElementField(field_uri="folder:ParentFolderId", value_cls=ParentFolderId, is_read_only=True) folder_class = CharField(field_uri="folder:FolderClass", is_required_after_save=True) name = CharField(field_uri="folder:DisplayName") total_count = IntegerField(field_uri="folder:TotalCount", is_read_only=True) child_folder_count = IntegerField(field_uri="folder:ChildFolderCount", is_read_only=True) unread_count = IntegerField(field_uri="folder:UnreadCount", is_read_only=True) __slots__ = "item_sync_state", "folder_sync_state" # Used to register extended properties INSERT_AFTER_FIELD = "child_folder_count" def __init__(self, **kwargs): self.item_sync_state = kwargs.pop("item_sync_state", None) self.folder_sync_state = kwargs.pop("folder_sync_state", None) super().__init__(**kwargs) if self._distinguished_id and not self._distinguished_id.mailbox and self.account: # Ensure that distinguished IDs have a mailbox, but don't override a custom mailbox (e.g. shared folders) self._distinguished_id.mailbox = Mailbox(email_address=self.account.primary_smtp_address) @property @abc.abstractmethod def account(self): """Return the account this folder belongs to""" @property @abc.abstractmethod def root(self): """Return the root folder this folder belongs to""" @property @abc.abstractmethod def parent(self): """Return the parent folder of this folder""" @property def is_distinguished(self): return self._distinguished_id or (self.DISTINGUISHED_FOLDER_ID and not self._id) @property def is_deletable(self): return not self.is_distinguished def clean(self, version=None): super().clean(version=version) # Set a default folder class for new folders. A folder class cannot be changed after saving. if self.id is None and self.folder_class is None: self.folder_class = self.CONTAINER_CLASS @property def children(self): # It's dangerous to return a generator here because we may then call methods on a child that result in the # cache being updated while it's iterated. return FolderCollection(account=self.account, folders=self.root.get_children(self)) @property def parts(self): parts = [self] f = self.parent while f: parts.insert(0, f) f = f.parent return parts @property def absolute(self): return "".join(f"/{p.name}" for p in self.parts) def _walk(self): for c in self.children: yield c yield from c.walk() def walk(self): return FolderCollection(account=self.account, folders=self._walk()) def _glob(self, pattern): split_pattern = pattern.split("/", maxsplit=1) head, tail = (split_pattern[0], None) if len(split_pattern) == 1 else split_pattern if head == "": # We got an absolute path. Restart globbing at root yield from self.root.glob(tail or "*") elif head == "..": # Relative path with reference to parent. Restart globbing at parent if not self.parent: raise ValueError("Already at top") yield from self.parent.glob(tail or "*") elif head == "**": # Match anything here or in any sub-folder at arbitrary depth for c in self.walk(): # fnmatch() may be case-sensitive depending on operating system: # force a case-insensitive match since case appears not to # matter for folders in Exchange if fnmatch(c.name.lower(), (tail or "*").lower()): yield c else: # Regular pattern for c in self.children: # See note above on fnmatch() case-sensitivity if not fnmatch(c.name.lower(), head.lower()): continue if tail is None: yield c continue yield from c.glob(tail) def glob(self, pattern): return FolderCollection(account=self.account, folders=self._glob(pattern)) def tree(self): """Return a string representation of the folder structure of this folder. Example: root ├── inbox │ └── todos └── archive ├── Last Job ├── exchangelib issues └── Mom """ tree = f"{self.name}\n" children = list(self.children) for i, c in enumerate(sorted(children, key=attrgetter("name")), start=1): nodes = c.tree().split("\n") for j, node in enumerate(nodes, start=1): if i != len(children) and j == 1: # Not the last child, but the first node, which is the name of the child tree += f"├── {node}\n" elif i != len(children) and j > 1: # Not the last child, and not name of child tree += f"│ {node}\n" elif i == len(children) and j == 1: # Not the last child, but the first node, which is the name of the child tree += f"└── {node}\n" else: # Last child and not name of child tree += f" {node}\n" return tree.strip() @classmethod def _get_distinguished(cls, folder): if not cls.DISTINGUISHED_FOLDER_ID: raise ValueError(f"Class {cls} must have a DISTINGUISHED_FOLDER_ID value") try: return cls.resolve(account=folder.account, folder=folder) except MISSING_FOLDER_ERRORS as e: raise ErrorFolderNotFound(f"Could not find distinguished folder {cls.DISTINGUISHED_FOLDER_ID!r} ({e})") @property def has_distinguished_name(self): return self.name and self.DISTINGUISHED_FOLDER_ID and self.name.lower() == self.DISTINGUISHED_FOLDER_ID.lower() @classmethod def localized_names(cls, locale): # Return localized names for a specific locale. If no locale-specific names exist, return the default names, # if any. return tuple(s.lower() for s in cls.LOCALIZED_NAMES.get(locale, cls.LOCALIZED_NAMES.get(None, [cls.__name__]))) @staticmethod def folder_cls_from_container_class(container_class): """Return a reasonable folder class given a container class, e.g. 'IPF.Note'. Don't iterate WELLKNOWN_FOLDERS because many folder classes have the same CONTAINER_CLASS. :param container_class: :return: """ from .known_folders import ( ApplicationData, Calendar, CompanyContacts, Contacts, ConversationSettings, CrawlerData, DlpPolicyEvaluation, EventCheckPoints, FreeBusyCache, GALContacts, Messages, OrganizationalContacts, PeopleCentricConversationBuddies, RecipientCache, RecoveryPoints, Reminders, RSSFeeds, Signal, SwssItems, Tasks, ) for folder_cls in ( ApplicationData, Calendar, CompanyContacts, Contacts, ConversationSettings, CrawlerData, DlpPolicyEvaluation, EventCheckPoints, FreeBusyCache, GALContacts, Messages, OrganizationalContacts, PeopleCentricConversationBuddies, RSSFeeds, RecipientCache, RecoveryPoints, Reminders, Signal, SwssItems, Tasks, ): if folder_cls.CONTAINER_CLASS == container_class: return folder_cls raise KeyError() @classmethod def item_model_from_tag(cls, tag): try: return cls.ITEM_MODEL_MAP[tag] except KeyError: raise ValueError(f"Item type {tag} was unexpected in a {cls.__name__} folder") @classmethod def allowed_item_fields(cls, version): # Return non-ID fields of all item classes allowed in this folder type fields = set() for item_model in cls.supported_item_models: fields.update(set(item_model.supported_fields(version=version))) return fields def validate_item_field(self, field, version): FolderCollection(account=self.account, folders=[self]).validate_item_field(field=field, version=version) def normalize_fields(self, fields): # Takes a list of fieldnames, Field or FieldPath objects pointing to item fields. Turns them into FieldPath # objects and adds internal timezone fields if necessary. Assume fields are already validated. fields = list(fields) has_start, has_end = False, False for i, field_path in enumerate(fields): # Allow both Field and FieldPath instances and string field paths as input if isinstance(field_path, str): field_path = FieldPath.from_string(field_path=field_path, folder=self) fields[i] = field_path elif isinstance(field_path, Field): field_path = FieldPath(field=field_path) fields[i] = field_path if field_path.field.name == "start": has_start = True elif field_path.field.name == "end": has_end = True # For CalendarItem items, we want to inject internal timezone fields. See also CalendarItem.clean() if CalendarItem in self.supported_item_models: meeting_tz_field, start_tz_field, end_tz_field = CalendarItem.timezone_fields() if self.account.version.build < EXCHANGE_2010: if has_start or has_end: fields.append(FieldPath(field=meeting_tz_field)) else: if has_start: fields.append(FieldPath(field=start_tz_field)) if has_end: fields.append(FieldPath(field=end_tz_field)) return fields @classmethod def get_item_field_by_fieldname(cls, fieldname): for item_model in cls.supported_item_models: with suppress(InvalidField): return item_model.get_field_by_fieldname(fieldname) raise InvalidField(f"{fieldname!r} is not a valid field name on {cls.supported_item_models}") def get(self, *args, **kwargs): return FolderCollection(account=self.account, folders=[self]).get(*args, **kwargs) def all(self): return FolderCollection(account=self.account, folders=[self]).all() def none(self): return FolderCollection(account=self.account, folders=[self]).none() def filter(self, *args, **kwargs): return FolderCollection(account=self.account, folders=[self]).filter(*args, **kwargs) def exclude(self, *args, **kwargs): return FolderCollection(account=self.account, folders=[self]).exclude(*args, **kwargs) def people(self): # No point in using a FolderCollection because FindPeople only supports one folder return FolderCollection(account=self.account, folders=[self]).people() def bulk_create(self, items, *args, **kwargs): return self.account.bulk_create(folder=self, items=items, *args, **kwargs) def save(self, update_fields=None): from ..services import CreateFolder, UpdateFolder if self.id is None: # New folder if update_fields: raise ValueError("'update_fields' is only valid for updates") res = CreateFolder(account=self.account).get(parent_folder=self.parent, folders=[self]) self._id = self.ID_ELEMENT_CLS(res.id, res.changekey) self.root.add_folder(self) # Add this folder to the cache return self # Update folder if not update_fields: # The fields to update was not specified explicitly. Update all fields where update is possible update_fields = [] for f in self.supported_fields(version=self.account.version): if f.is_read_only: # These cannot be changed continue if (f.is_required or f.is_required_after_save) and ( getattr(self, f.name) is None or (f.is_list and not getattr(self, f.name)) ): # These are required and cannot be deleted continue update_fields.append(f.name) res = UpdateFolder(account=self.account).get(folders=[(self, update_fields)]) folder_id, changekey = res.id, res.changekey if self.id != folder_id: raise ValueError("ID mismatch") # Don't check changekey value. It may not change on no-op updates self.changekey = changekey self.root.update_folder(self) # Update the folder in the cache return self def move(self, to_folder): from ..services import MoveFolder res = MoveFolder(account=self.account).get(folders=[self], to_folder=to_folder) folder_id, changekey = res.id, res.changekey if self.id != folder_id: raise ValueError("ID mismatch") # Don't check changekey value. It may not change on no-op moves self.changekey = changekey self.parent_folder_id = ParentFolderId(id=to_folder.id, changekey=to_folder.changekey) self.root.update_folder(self) # Update the folder in the cache def delete(self, delete_type=HARD_DELETE): from ..services import DeleteFolder DeleteFolder(account=self.account).get(folders=[self], delete_type=delete_type) self.root.remove_folder(self) # Remove the updated folder from the cache self._id = None def empty(self, delete_type=HARD_DELETE, delete_sub_folders=False): from ..services import EmptyFolder EmptyFolder(account=self.account).get( folders=[self], delete_type=delete_type, delete_sub_folders=delete_sub_folders ) if delete_sub_folders: # We don't know exactly what was deleted, so invalidate the entire folder cache to be safe self.root.clear_cache() def wipe(self, page_size=None, chunk_size=None, _seen=None, _level=0): # Recursively deletes all items in this folder, and all sub-folders and their content. Attempts to protect # distinguished folders from being deleted. Use with caution! from .known_folders import Audits _seen = _seen or set() if self.id in _seen: raise RecursionError(f"We already tried to wipe {self}") if _level > 16: raise RecursionError(f"Max recursion level reached: {_level}") _seen.add(self.id) if isinstance(self, Audits): # Shortcircuit because this folder can have many items that are all non-deletable log.warning("Cannot wipe audits folder %s", self) return if self.is_distinguished and "recoverableitems" in self.DISTINGUISHED_FOLDER_ID: log.warning("Cannot wipe recoverable items folder %s", self) return log.warning("Wiping %s", self) has_non_deletable_subfolders = any(not f.is_deletable for f in self.children) try: if has_non_deletable_subfolders: self.empty() else: self.empty(delete_sub_folders=True) except ErrorRecoverableItemsAccessDenied: log.warning("Access denied to %s. Skipping", self) return except DELETE_FOLDER_ERRORS: try: if has_non_deletable_subfolders: raise # We already tried this self.empty() except DELETE_FOLDER_ERRORS: log.warning("Not allowed to empty %s. Trying to delete items instead", self) kwargs = {} if page_size is not None: kwargs["page_size"] = page_size if chunk_size is not None: kwargs["chunk_size"] = chunk_size try: self.all().delete(**kwargs) except DELETE_FOLDER_ERRORS: log.warning("Not allowed to delete items in %s", self) _level += 1 for f in self.children: f.wipe(page_size=page_size, chunk_size=chunk_size, _seen=_seen, _level=_level) # Remove non-distinguished children that are empty and have no sub-folders if f.is_deletable and not f.children: log.warning("Deleting folder %s", f) try: f.delete() except ErrorDeleteDistinguishedFolder: log.warning("Tried to delete a distinguished folder (%s)", f) def test_access(self): """Does a simple FindItem to test (read) access to the folder. Maybe the account doesn't exist, maybe the service user doesn't have access to the calendar. This will throw the most common errors. """ self.all().exists() return True @classmethod def _kwargs_from_elem(cls, elem, account): # Check for 'DisplayName' element before collecting kwargs because that clears the elements has_name_elem = elem.find(cls.get_field_by_fieldname("name").response_tag()) is not None kwargs = {f.name: f.from_xml(elem=elem, account=account) for f in cls.FIELDS} if has_name_elem and not kwargs["name"]: # When we request the 'DisplayName' property, some folders may still be returned with an empty value. # Assign a default name to these folders. kwargs["name"] = cls.DISTINGUISHED_FOLDER_ID return kwargs def to_id(self): # Use self._distinguished_id as-is if we have it. This could be a DistinguishedFolderId with a mailbox pointing # to a shared mailbox. if self._distinguished_id: return self._distinguished_id if self._id: return self._id if not self.DISTINGUISHED_FOLDER_ID: raise ValueError(f"{self} must be a distinguished folder or have an ID") self._distinguished_id = DistinguishedFolderId( id=self.DISTINGUISHED_FOLDER_ID, mailbox=Mailbox(email_address=self.account.primary_smtp_address), ) return self._distinguished_id @classmethod def resolve(cls, account, folder): # Resolve a single folder folders = list(FolderCollection(account=account, folders=[folder]).resolve()) if not folders: raise ErrorFolderNotFound(f"Could not find folder {folder!r}") if len(folders) != 1: raise ValueError(f"Expected result length 1, but got {folders}") f = folders[0] if isinstance(f, Exception): raise f if f.__class__ != cls: raise ValueError(f"Expected folder {f!r} to be a {cls} instance") return f @require_id def refresh(self): fresh_folder = self.resolve(account=self.account, folder=self) if self.id != fresh_folder.id: raise ValueError("ID mismatch") # Apparently, the changekey may get updated for f in self.FIELDS: setattr(self, f.name, getattr(fresh_folder, f.name)) return self @require_id def get_user_configuration(self, name, properties=None): from ..services import GetUserConfiguration from ..services.get_user_configuration import ALL if properties is None: properties = ALL return GetUserConfiguration(account=self.account).get( user_configuration_name=UserConfigurationNameMNS(name=name, folder=self), properties=properties, ) @require_id def create_user_configuration(self, name, dictionary=None, xml_data=None, binary_data=None): from ..services import CreateUserConfiguration user_configuration = UserConfiguration( user_configuration_name=UserConfigurationName(name=name, folder=self), dictionary=dictionary, xml_data=xml_data, binary_data=binary_data, ) return CreateUserConfiguration(account=self.account).get(user_configuration=user_configuration) @require_id def update_user_configuration(self, name, dictionary=None, xml_data=None, binary_data=None): from ..services import UpdateUserConfiguration user_configuration = UserConfiguration( user_configuration_name=UserConfigurationName(name=name, folder=self), dictionary=dictionary, xml_data=xml_data, binary_data=binary_data, ) return UpdateUserConfiguration(account=self.account).get(user_configuration=user_configuration) @require_id def delete_user_configuration(self, name): from ..services import DeleteUserConfiguration return DeleteUserConfiguration(account=self.account).get( user_configuration_name=UserConfigurationNameMNS(name=name, folder=self) ) @require_id def subscribe_to_pull(self, event_types=None, watermark=None, timeout=60): """Create a pull subscription. :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPull.EVENT_TYPES :param watermark: An event bookmark as returned by some sync services :param timeout: Timeout of the subscription, in minutes. Timeout is reset when the server receives a GetEvents request for this subscription. :return: The subscription ID and a watermark """ from ..services import SubscribeToPull if event_types is None: event_types = SubscribeToPull.EVENT_TYPES return FolderCollection(account=self.account, folders=[self]).subscribe_to_pull( event_types=event_types, watermark=watermark, timeout=timeout, ) @require_id def subscribe_to_push(self, callback_url, event_types=None, watermark=None, status_frequency=1): """Create a push subscription. :param callback_url: A client-defined URL that the server will call :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPush.EVENT_TYPES :param watermark: An event bookmark as returned by some sync services :param status_frequency: The frequency, in minutes, that the callback URL will be called with. :return: The subscription ID and a watermark """ from ..services import SubscribeToPush if event_types is None: event_types = SubscribeToPush.EVENT_TYPES return FolderCollection(account=self.account, folders=[self]).subscribe_to_push( event_types=event_types, watermark=watermark, status_frequency=status_frequency, callback_url=callback_url, ) @require_id def subscribe_to_streaming(self, event_types=None): """Create a streaming subscription. :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPush.EVENT_TYPES :return: The subscription ID """ from ..services import SubscribeToStreaming if event_types is None: event_types = SubscribeToStreaming.EVENT_TYPES return FolderCollection(account=self.account, folders=[self]).subscribe_to_streaming(event_types=event_types) @require_id def pull_subscription(self, **kwargs): return PullSubscription(target=self, **kwargs) @require_id def push_subscription(self, **kwargs): return PushSubscription(target=self, **kwargs) @require_id def streaming_subscription(self, **kwargs): return StreamingSubscription(target=self, **kwargs) def unsubscribe(self, subscription_id): """Unsubscribe. Only applies to pull and streaming notifications. :param subscription_id: A subscription ID as acquired by .subscribe_to_[pull|streaming]() :return: True This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. """ from ..services import Unsubscribe return Unsubscribe(account=self.account).get(subscription_id=subscription_id) def sync_items(self, sync_state=None, only_fields=None, ignore=None, max_changes_returned=None, sync_scope=None): """Return all item changes to a folder, as a generator. If sync_state is specified, get all item changes after this sync state. After fully consuming the generator, self.item_sync_state will hold the new sync state. :param sync_state: The state of the sync. Returned by a successful call to the SyncFolderItems service. :param only_fields: A list of string or FieldPath items specifying the fields to fetch. Default to all fields :param ignore: A list of Item IDs to ignore in the sync :param max_changes_returned: The max number of change :param sync_scope: Specify whether to return just items, or items and folder associated information. Possible values are specified in SyncFolderItems.SYNC_SCOPES :return: A generator of (change_type, item) tuples """ if not sync_state: sync_state = self.item_sync_state try: yield from FolderCollection(account=self.account, folders=[self]).sync_items( sync_state=sync_state, only_fields=only_fields, ignore=ignore, max_changes_returned=max_changes_returned, sync_scope=sync_scope, ) except SyncCompleted as e: # Set the new sync state on the folder instance self.item_sync_state = e.sync_state def sync_hierarchy(self, sync_state=None, only_fields=None): """Return all folder changes to a folder hierarchy, as a generator. If sync_state is specified, get all folder changes after this sync state. After fully consuming the generator, self.folder_sync_state will hold the new sync state. :param sync_state: The state of the sync. Returned by a successful call to the SyncFolderItems service. :param only_fields: A list of string or FieldPath items specifying the fields to fetch. Default to all fields :return: """ if not sync_state: sync_state = self.folder_sync_state try: yield from FolderCollection(account=self.account, folders=[self]).sync_hierarchy( sync_state=sync_state, only_fields=only_fields, ) except SyncCompleted as e: # Set the new sync state on the folder instance self.folder_sync_state = e.sync_state def get_events(self, subscription_id, watermark): """Get events since the given watermark. Non-blocking. :param subscription_id: A subscription ID as acquired by .subscribe_to_[pull|push]() :param watermark: Either the watermark from the subscription, or as returned by the last .get_events() call. :return: A Notification object containing a list of events This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. """ from ..services import GetEvents svc = GetEvents(account=self.account) while True: notification = svc.get(subscription_id=subscription_id, watermark=watermark) yield notification if not notification.more_events: break def get_streaming_events(self, subscription_id_or_ids, connection_timeout=1, max_notifications_returned=None): """Get events since the subscription was created, in streaming mode. This method will block as many minutes as specified by 'connection_timeout'. :param subscription_id_or_ids: A subscription ID, or list of IDs, as acquired by .subscribe_to_streaming() :param connection_timeout: Timeout of the connection, in minutes. The connection is closed after this timeout is reached. :param max_notifications_returned: If specified, will exit after receiving this number of notifications :return: A generator of Notification objects, each containing a list of events This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. """ from ..services import GetStreamingEvents svc = GetStreamingEvents(account=self.account) subscription_ids = ( subscription_id_or_ids if is_iterable(subscription_id_or_ids, generators_allowed=True) else [subscription_id_or_ids] ) for i, notification in enumerate( svc.call(subscription_ids=subscription_ids, connection_timeout=connection_timeout), start=1 ): yield notification if max_notifications_returned and i >= max_notifications_returned: svc.stop_streaming() break def __floordiv__(self, other): """Support the some_folder // 'child_folder' // 'child_of_child_folder' navigation syntax. Works like as __truediv__ but does not touch the folder cache. This is useful if the folder hierarchy contains a huge number of folders, and you don't want to fetch them all :param other: :return: """ if other == "..": raise ValueError("Cannot get parent without a folder cache") if other == ".": return self # Assume an exact match on the folder name in a shallow search will only return at most one folder try: return SingleFolderQuerySet(account=self.account, folder=self).depth(SHALLOW_FOLDERS).get(name=other) except DoesNotExist: raise ErrorFolderNotFound(f"No subfolder with name {other!r}") def __truediv__(self, other): """Support the some_folder / 'child_folder' / 'child_of_child_folder' navigation syntax.""" if other == "..": if not self.parent: raise ValueError("Already at top") return self.parent if other == ".": return self for c in self.children: # Folders are case-insensitive server-side. Let's do that here as well. if c.name.lower() == other.lower(): return c raise ErrorFolderNotFound(f"No subfolder with name {other!r}") def __repr__(self): return self.__class__.__name__ + repr( ( self.root, self.name, self.total_count, self.unread_count, self.child_folder_count, self.folder_class, self.id, self.changekey, ) ) def __str__(self): return f"{self.__class__.__name__} ({self.name})"
- var name
- prop parent
- 
Expand source code@property @abc.abstractmethod def parent(self): """Return the parent folder of this folder"""Return the parent folder of this folder 
- var parent_folder_id
- prop parts
- 
Expand source code@property def parts(self): parts = [self] f = self.parent while f: parts.insert(0, f) f = f.parent return parts
- prop root
- 
Expand source code@property @abc.abstractmethod def root(self): """Return the root folder this folder belongs to"""Return the root folder this folder belongs to 
- var total_count
- var unread_count
 Methods- def bulk_create(self, items, *args, **kwargs)
- 
Expand source codedef bulk_create(self, items, *args, **kwargs): return self.account.bulk_create(folder=self, items=items, *args, **kwargs)
- def clean(self, version=None)
- 
Expand source codedef clean(self, version=None): super().clean(version=version) # Set a default folder class for new folders. A folder class cannot be changed after saving. if self.id is None and self.folder_class is None: self.folder_class = self.CONTAINER_CLASS
- def create_user_configuration(self, name, dictionary=None, xml_data=None, binary_data=None)
- 
Expand source code@require_id def create_user_configuration(self, name, dictionary=None, xml_data=None, binary_data=None): from ..services import CreateUserConfiguration user_configuration = UserConfiguration( user_configuration_name=UserConfigurationName(name=name, folder=self), dictionary=dictionary, xml_data=xml_data, binary_data=binary_data, ) return CreateUserConfiguration(account=self.account).get(user_configuration=user_configuration)
- def delete(self, delete_type='HardDelete')
- 
Expand source codedef delete(self, delete_type=HARD_DELETE): from ..services import DeleteFolder DeleteFolder(account=self.account).get(folders=[self], delete_type=delete_type) self.root.remove_folder(self) # Remove the updated folder from the cache self._id = None
- def delete_user_configuration(self, name)
- 
Expand source code@require_id def delete_user_configuration(self, name): from ..services import DeleteUserConfiguration return DeleteUserConfiguration(account=self.account).get( user_configuration_name=UserConfigurationNameMNS(name=name, folder=self) )
- def empty(self, delete_type='HardDelete', delete_sub_folders=False)
- 
Expand source codedef empty(self, delete_type=HARD_DELETE, delete_sub_folders=False): from ..services import EmptyFolder EmptyFolder(account=self.account).get( folders=[self], delete_type=delete_type, delete_sub_folders=delete_sub_folders ) if delete_sub_folders: # We don't know exactly what was deleted, so invalidate the entire folder cache to be safe self.root.clear_cache()
- def get_events(self, subscription_id, watermark)
- 
Expand source codedef get_events(self, subscription_id, watermark): """Get events since the given watermark. Non-blocking. :param subscription_id: A subscription ID as acquired by .subscribe_to_[pull|push]() :param watermark: Either the watermark from the subscription, or as returned by the last .get_events() call. :return: A Notification object containing a list of events This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. """ from ..services import GetEvents svc = GetEvents(account=self.account) while True: notification = svc.get(subscription_id=subscription_id, watermark=watermark) yield notification if not notification.more_events: breakGet events since the given watermark. Non-blocking. :param subscription_id: A subscription ID as acquired by .subscribe_to_pull|push :param watermark: Either the watermark from the subscription, or as returned by the last .get_events() call. :return: A Notification object containing a list of events This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. 
- def get_streaming_events(self,
 subscription_id_or_ids,
 connection_timeout=1,
 max_notifications_returned=None)
- 
Expand source codedef get_streaming_events(self, subscription_id_or_ids, connection_timeout=1, max_notifications_returned=None): """Get events since the subscription was created, in streaming mode. This method will block as many minutes as specified by 'connection_timeout'. :param subscription_id_or_ids: A subscription ID, or list of IDs, as acquired by .subscribe_to_streaming() :param connection_timeout: Timeout of the connection, in minutes. The connection is closed after this timeout is reached. :param max_notifications_returned: If specified, will exit after receiving this number of notifications :return: A generator of Notification objects, each containing a list of events This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. """ from ..services import GetStreamingEvents svc = GetStreamingEvents(account=self.account) subscription_ids = ( subscription_id_or_ids if is_iterable(subscription_id_or_ids, generators_allowed=True) else [subscription_id_or_ids] ) for i, notification in enumerate( svc.call(subscription_ids=subscription_ids, connection_timeout=connection_timeout), start=1 ): yield notification if max_notifications_returned and i >= max_notifications_returned: svc.stop_streaming() breakGet events since the subscription was created, in streaming mode. This method will block as many minutes as specified by 'connection_timeout'. :param subscription_id_or_ids: A subscription ID, or list of IDs, as acquired by .subscribe_to_streaming() :param connection_timeout: Timeout of the connection, in minutes. The connection is closed after this timeout is reached. :param max_notifications_returned: If specified, will exit after receiving this number of notifications :return: A generator of Notification objects, each containing a list of events This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. 
- def get_user_configuration(self, name, properties=None)
- 
Expand source code@require_id def get_user_configuration(self, name, properties=None): from ..services import GetUserConfiguration from ..services.get_user_configuration import ALL if properties is None: properties = ALL return GetUserConfiguration(account=self.account).get( user_configuration_name=UserConfigurationNameMNS(name=name, folder=self), properties=properties, )
- def glob(self, pattern)
- 
Expand source codedef glob(self, pattern): return FolderCollection(account=self.account, folders=self._glob(pattern))
- def move(self, to_folder)
- 
Expand source codedef move(self, to_folder): from ..services import MoveFolder res = MoveFolder(account=self.account).get(folders=[self], to_folder=to_folder) folder_id, changekey = res.id, res.changekey if self.id != folder_id: raise ValueError("ID mismatch") # Don't check changekey value. It may not change on no-op moves self.changekey = changekey self.parent_folder_id = ParentFolderId(id=to_folder.id, changekey=to_folder.changekey) self.root.update_folder(self) # Update the folder in the cache
- def normalize_fields(self, fields)
- 
Expand source codedef normalize_fields(self, fields): # Takes a list of fieldnames, Field or FieldPath objects pointing to item fields. Turns them into FieldPath # objects and adds internal timezone fields if necessary. Assume fields are already validated. fields = list(fields) has_start, has_end = False, False for i, field_path in enumerate(fields): # Allow both Field and FieldPath instances and string field paths as input if isinstance(field_path, str): field_path = FieldPath.from_string(field_path=field_path, folder=self) fields[i] = field_path elif isinstance(field_path, Field): field_path = FieldPath(field=field_path) fields[i] = field_path if field_path.field.name == "start": has_start = True elif field_path.field.name == "end": has_end = True # For CalendarItem items, we want to inject internal timezone fields. See also CalendarItem.clean() if CalendarItem in self.supported_item_models: meeting_tz_field, start_tz_field, end_tz_field = CalendarItem.timezone_fields() if self.account.version.build < EXCHANGE_2010: if has_start or has_end: fields.append(FieldPath(field=meeting_tz_field)) else: if has_start: fields.append(FieldPath(field=start_tz_field)) if has_end: fields.append(FieldPath(field=end_tz_field)) return fields
- def pull_subscription(self, **kwargs)
- 
Expand source code@require_id def pull_subscription(self, **kwargs): return PullSubscription(target=self, **kwargs)
- def push_subscription(self, **kwargs)
- 
Expand source code@require_id def push_subscription(self, **kwargs): return PushSubscription(target=self, **kwargs)
- def refresh(self)
- 
Expand source code@require_id def refresh(self): fresh_folder = self.resolve(account=self.account, folder=self) if self.id != fresh_folder.id: raise ValueError("ID mismatch") # Apparently, the changekey may get updated for f in self.FIELDS: setattr(self, f.name, getattr(fresh_folder, f.name)) return self
- def save(self, update_fields=None)
- 
Expand source codedef save(self, update_fields=None): from ..services import CreateFolder, UpdateFolder if self.id is None: # New folder if update_fields: raise ValueError("'update_fields' is only valid for updates") res = CreateFolder(account=self.account).get(parent_folder=self.parent, folders=[self]) self._id = self.ID_ELEMENT_CLS(res.id, res.changekey) self.root.add_folder(self) # Add this folder to the cache return self # Update folder if not update_fields: # The fields to update was not specified explicitly. Update all fields where update is possible update_fields = [] for f in self.supported_fields(version=self.account.version): if f.is_read_only: # These cannot be changed continue if (f.is_required or f.is_required_after_save) and ( getattr(self, f.name) is None or (f.is_list and not getattr(self, f.name)) ): # These are required and cannot be deleted continue update_fields.append(f.name) res = UpdateFolder(account=self.account).get(folders=[(self, update_fields)]) folder_id, changekey = res.id, res.changekey if self.id != folder_id: raise ValueError("ID mismatch") # Don't check changekey value. It may not change on no-op updates self.changekey = changekey self.root.update_folder(self) # Update the folder in the cache return self
- def streaming_subscription(self, **kwargs)
- 
Expand source code@require_id def streaming_subscription(self, **kwargs): return StreamingSubscription(target=self, **kwargs)
- def subscribe_to_pull(self, event_types=None, watermark=None, timeout=60)
- 
Expand source code@require_id def subscribe_to_pull(self, event_types=None, watermark=None, timeout=60): """Create a pull subscription. :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPull.EVENT_TYPES :param watermark: An event bookmark as returned by some sync services :param timeout: Timeout of the subscription, in minutes. Timeout is reset when the server receives a GetEvents request for this subscription. :return: The subscription ID and a watermark """ from ..services import SubscribeToPull if event_types is None: event_types = SubscribeToPull.EVENT_TYPES return FolderCollection(account=self.account, folders=[self]).subscribe_to_pull( event_types=event_types, watermark=watermark, timeout=timeout, )Create a pull subscription. :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPull.EVENT_TYPES :param watermark: An event bookmark as returned by some sync services :param timeout: Timeout of the subscription, in minutes. Timeout is reset when the server receives a GetEvents request for this subscription. :return: The subscription ID and a watermark 
- def subscribe_to_push(self, callback_url, event_types=None, watermark=None, status_frequency=1)
- 
Expand source code@require_id def subscribe_to_push(self, callback_url, event_types=None, watermark=None, status_frequency=1): """Create a push subscription. :param callback_url: A client-defined URL that the server will call :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPush.EVENT_TYPES :param watermark: An event bookmark as returned by some sync services :param status_frequency: The frequency, in minutes, that the callback URL will be called with. :return: The subscription ID and a watermark """ from ..services import SubscribeToPush if event_types is None: event_types = SubscribeToPush.EVENT_TYPES return FolderCollection(account=self.account, folders=[self]).subscribe_to_push( event_types=event_types, watermark=watermark, status_frequency=status_frequency, callback_url=callback_url, )Create a push subscription. :param callback_url: A client-defined URL that the server will call :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPush.EVENT_TYPES :param watermark: An event bookmark as returned by some sync services :param status_frequency: The frequency, in minutes, that the callback URL will be called with. :return: The subscription ID and a watermark 
- def subscribe_to_streaming(self, event_types=None)
- 
Expand source code@require_id def subscribe_to_streaming(self, event_types=None): """Create a streaming subscription. :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPush.EVENT_TYPES :return: The subscription ID """ from ..services import SubscribeToStreaming if event_types is None: event_types = SubscribeToStreaming.EVENT_TYPES return FolderCollection(account=self.account, folders=[self]).subscribe_to_streaming(event_types=event_types)Create a streaming subscription. :param event_types: List of event types to subscribe to. Possible values defined in SubscribeToPush.EVENT_TYPES :return: The subscription ID 
- def sync_hierarchy(self, sync_state=None, only_fields=None)
- 
Expand source codedef sync_hierarchy(self, sync_state=None, only_fields=None): """Return all folder changes to a folder hierarchy, as a generator. If sync_state is specified, get all folder changes after this sync state. After fully consuming the generator, self.folder_sync_state will hold the new sync state. :param sync_state: The state of the sync. Returned by a successful call to the SyncFolderItems service. :param only_fields: A list of string or FieldPath items specifying the fields to fetch. Default to all fields :return: """ if not sync_state: sync_state = self.folder_sync_state try: yield from FolderCollection(account=self.account, folders=[self]).sync_hierarchy( sync_state=sync_state, only_fields=only_fields, ) except SyncCompleted as e: # Set the new sync state on the folder instance self.folder_sync_state = e.sync_stateReturn all folder changes to a folder hierarchy, as a generator. If sync_state is specified, get all folder changes after this sync state. After fully consuming the generator, self.folder_sync_state will hold the new sync state. :param sync_state: The state of the sync. Returned by a successful call to the SyncFolderItems service. :param only_fields: A list of string or FieldPath items specifying the fields to fetch. Default to all fields :return: 
- def sync_items(self,
 sync_state=None,
 only_fields=None,
 ignore=None,
 max_changes_returned=None,
 sync_scope=None)
- 
Expand source codedef sync_items(self, sync_state=None, only_fields=None, ignore=None, max_changes_returned=None, sync_scope=None): """Return all item changes to a folder, as a generator. If sync_state is specified, get all item changes after this sync state. After fully consuming the generator, self.item_sync_state will hold the new sync state. :param sync_state: The state of the sync. Returned by a successful call to the SyncFolderItems service. :param only_fields: A list of string or FieldPath items specifying the fields to fetch. Default to all fields :param ignore: A list of Item IDs to ignore in the sync :param max_changes_returned: The max number of change :param sync_scope: Specify whether to return just items, or items and folder associated information. Possible values are specified in SyncFolderItems.SYNC_SCOPES :return: A generator of (change_type, item) tuples """ if not sync_state: sync_state = self.item_sync_state try: yield from FolderCollection(account=self.account, folders=[self]).sync_items( sync_state=sync_state, only_fields=only_fields, ignore=ignore, max_changes_returned=max_changes_returned, sync_scope=sync_scope, ) except SyncCompleted as e: # Set the new sync state on the folder instance self.item_sync_state = e.sync_stateReturn all item changes to a folder, as a generator. If sync_state is specified, get all item changes after this sync state. After fully consuming the generator, self.item_sync_state will hold the new sync state. :param sync_state: The state of the sync. Returned by a successful call to the SyncFolderItems service. :param only_fields: A list of string or FieldPath items specifying the fields to fetch. Default to all fields :param ignore: A list of Item IDs to ignore in the sync :param max_changes_returned: The max number of change :param sync_scope: Specify whether to return just items, or items and folder associated information. Possible values are specified in SyncFolderItems.SYNC_SCOPES :return: A generator of (change_type, item) tuples 
- def test_access(self)
- 
Expand source codedef test_access(self): """Does a simple FindItem to test (read) access to the folder. Maybe the account doesn't exist, maybe the service user doesn't have access to the calendar. This will throw the most common errors. """ self.all().exists() return TrueDoes a simple FindItem to test (read) access to the folder. Maybe the account doesn't exist, maybe the service user doesn't have access to the calendar. This will throw the most common errors. 
- def to_id(self)
- 
Expand source codedef to_id(self): # Use self._distinguished_id as-is if we have it. This could be a DistinguishedFolderId with a mailbox pointing # to a shared mailbox. if self._distinguished_id: return self._distinguished_id if self._id: return self._id if not self.DISTINGUISHED_FOLDER_ID: raise ValueError(f"{self} must be a distinguished folder or have an ID") self._distinguished_id = DistinguishedFolderId( id=self.DISTINGUISHED_FOLDER_ID, mailbox=Mailbox(email_address=self.account.primary_smtp_address), ) return self._distinguished_id
- def tree(self)
- 
Expand source codedef tree(self): """Return a string representation of the folder structure of this folder. Example: root ├── inbox │ └── todos └── archive ├── Last Job ├── exchangelib issues └── Mom """ tree = f"{self.name}\n" children = list(self.children) for i, c in enumerate(sorted(children, key=attrgetter("name")), start=1): nodes = c.tree().split("\n") for j, node in enumerate(nodes, start=1): if i != len(children) and j == 1: # Not the last child, but the first node, which is the name of the child tree += f"├── {node}\n" elif i != len(children) and j > 1: # Not the last child, and not name of child tree += f"│ {node}\n" elif i == len(children) and j == 1: # Not the last child, but the first node, which is the name of the child tree += f"└── {node}\n" else: # Last child and not name of child tree += f" {node}\n" return tree.strip()Return a string representation of the folder structure of this folder. Example: root ├── inbox │ └── todos └── archive ├── Last Job ├── exchangelib issues └── Mom 
- def unsubscribe(self, subscription_id)
- 
Expand source codedef unsubscribe(self, subscription_id): """Unsubscribe. Only applies to pull and streaming notifications. :param subscription_id: A subscription ID as acquired by .subscribe_to_[pull|streaming]() :return: True This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. """ from ..services import Unsubscribe return Unsubscribe(account=self.account).get(subscription_id=subscription_id)Unsubscribe. Only applies to pull and streaming notifications. :param subscription_id: A subscription ID as acquired by .subscribe_to_pull|streaming :return: True This method doesn't need the current folder instance, but it makes sense to keep the method along the other sync methods. 
- def update_user_configuration(self, name, dictionary=None, xml_data=None, binary_data=None)
- 
Expand source code@require_id def update_user_configuration(self, name, dictionary=None, xml_data=None, binary_data=None): from ..services import UpdateUserConfiguration user_configuration = UserConfiguration( user_configuration_name=UserConfigurationName(name=name, folder=self), dictionary=dictionary, xml_data=xml_data, binary_data=binary_data, ) return UpdateUserConfiguration(account=self.account).get(user_configuration=user_configuration)
- def validate_item_field(self, field, version)
- 
Expand source codedef validate_item_field(self, field, version): FolderCollection(account=self.account, folders=[self]).validate_item_field(field=field, version=version)
- def walk(self)
- 
Expand source codedef walk(self): return FolderCollection(account=self.account, folders=self._walk())
- def wipe(self, page_size=None, chunk_size=None)
- 
Expand source codedef wipe(self, page_size=None, chunk_size=None, _seen=None, _level=0): # Recursively deletes all items in this folder, and all sub-folders and their content. Attempts to protect # distinguished folders from being deleted. Use with caution! from .known_folders import Audits _seen = _seen or set() if self.id in _seen: raise RecursionError(f"We already tried to wipe {self}") if _level > 16: raise RecursionError(f"Max recursion level reached: {_level}") _seen.add(self.id) if isinstance(self, Audits): # Shortcircuit because this folder can have many items that are all non-deletable log.warning("Cannot wipe audits folder %s", self) return if self.is_distinguished and "recoverableitems" in self.DISTINGUISHED_FOLDER_ID: log.warning("Cannot wipe recoverable items folder %s", self) return log.warning("Wiping %s", self) has_non_deletable_subfolders = any(not f.is_deletable for f in self.children) try: if has_non_deletable_subfolders: self.empty() else: self.empty(delete_sub_folders=True) except ErrorRecoverableItemsAccessDenied: log.warning("Access denied to %s. Skipping", self) return except DELETE_FOLDER_ERRORS: try: if has_non_deletable_subfolders: raise # We already tried this self.empty() except DELETE_FOLDER_ERRORS: log.warning("Not allowed to empty %s. Trying to delete items instead", self) kwargs = {} if page_size is not None: kwargs["page_size"] = page_size if chunk_size is not None: kwargs["chunk_size"] = chunk_size try: self.all().delete(**kwargs) except DELETE_FOLDER_ERRORS: log.warning("Not allowed to delete items in %s", self) _level += 1 for f in self.children: f.wipe(page_size=page_size, chunk_size=chunk_size, _seen=_seen, _level=_level) # Remove non-distinguished children that are empty and have no sub-folders if f.is_deletable and not f.children: log.warning("Deleting folder %s", f) try: f.delete() except ErrorDeleteDistinguishedFolder: log.warning("Tried to delete a distinguished folder (%s)", f)
 Inherited members
- class Birthdays (**kwargs)
- 
Expand source codeclass Birthdays(Folder): CONTAINER_CLASS = "IPF.Appointment.Birthday" LOCALIZED_NAMES = { "da_DK": ("Fødselsdage",), }Ancestors- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var LOCALIZED_NAMES
 Inherited members- Folder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Calendar (**kwargs)
- 
Expand source codeclass Calendar(WellknownFolder): """An interface for the Exchange calendar.""" DISTINGUISHED_FOLDER_ID = "calendar" CONTAINER_CLASS = "IPF.Appointment" supported_item_models = (CalendarItem,) LOCALIZED_NAMES = { "da_DK": ("Kalender",), "de_DE": ("Kalender",), "en_US": ("Calendar",), "es_ES": ("Calendario",), "fr_CA": ("Calendrier",), "nl_NL": ("Agenda",), "ru_RU": ("Календарь",), "sv_SE": ("Kalender",), "zh_CN": ("日历",), } def view(self, *args, **kwargs): return FolderCollection(account=self.account, folders=[self]).view(*args, **kwargs)An interface for the Exchange calendar. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
- var supported_item_models
 Methods- def view(self, *args, **kwargs)
- 
Expand source codedef view(self, *args, **kwargs): return FolderCollection(account=self.account, folders=[self]).view(*args, **kwargs)
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 CalendarLogging (**kwargs)
- 
Expand source codeclass CalendarLogging(NonDeletableFolder): LOCALIZED_NAMES = { None: ("Calendar Logging",), }A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var LOCALIZED_NAMES
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 CalendarSearchCache (**kwargs)
- 
Expand source codeclass CalendarSearchCache(NonDeletableFolder): CONTAINER_CLASS = "IPF.Appointment"A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 CommonViews (**kwargs)
- 
Expand source codeclass CommonViews(NonDeletableFolder): DEFAULT_ITEM_TRAVERSAL_DEPTH = ASSOCIATED LOCALIZED_NAMES = { None: ("Common Views",), }A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DEFAULT_ITEM_TRAVERSAL_DEPTH
- var LOCALIZED_NAMES
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 CompanyContacts (**kwargs)
- 
Expand source codeclass CompanyContacts(WellknownFolder): DISTINGUISHED_FOLDER_ID = "companycontacts" CONTAINER_CLASS = "IPF.Contact.Company" supported_from = EXCHANGE_O365 supported_item_models = CONTACT_ITEM_CLASSES LOCALIZED_NAMES = { "da_DK": ("Firmaer",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
- var supported_from
- var supported_item_models
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Conflicts (**kwargs)
- 
Expand source codeclass Conflicts(WellknownFolder): DISTINGUISHED_FOLDER_ID = "conflicts" supported_from = EXCHANGE_2013Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Contacts (**kwargs)
- 
Expand source codeclass Contacts(WellknownFolder): DISTINGUISHED_FOLDER_ID = "contacts" CONTAINER_CLASS = "IPF.Contact" supported_item_models = CONTACT_ITEM_CLASSES LOCALIZED_NAMES = { "da_DK": ("Kontaktpersoner",), "de_DE": ("Kontakte",), "en_US": ("Contacts",), "es_ES": ("Contactos",), "fr_CA": ("Contacts",), "nl_NL": ("Contactpersonen",), "ru_RU": ("Контакты",), "sv_SE": ("Kontakter",), "zh_CN": ("联系人",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
- var supported_item_models
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ConversationHistory (**kwargs)
- 
Expand source codeclass ConversationHistory(WellknownFolder): DISTINGUISHED_FOLDER_ID = "conversationhistory" supported_from = EXCHANGE_2013Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ConversationSettings (**kwargs)
- 
Expand source codeclass ConversationSettings(NonDeletableFolder): CONTAINER_CLASS = "IPF.Configuration" LOCALIZED_NAMES = { "da_DK": ("Indstillinger for samtalehandlinger",), }A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var LOCALIZED_NAMES
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 CrawlerData (**kwargs)
- 
Expand source codeclass CrawlerData(Folder): CONTAINER_CLASS = "IPF.StoreItem.CrawlerData"Ancestors- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- Folder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 DefaultFoldersChangeHistory (**kwargs)
- 
Expand source codeclass DefaultFoldersChangeHistory(NonDeletableFolder): CONTAINER_CLASS = "IPM.DefaultFolderHistoryItem"A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 DeferredAction (**kwargs)
- 
Expand source codeclass DeferredAction(NonDeletableFolder): LOCALIZED_NAMES = { None: ("Deferred Action",), }A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var LOCALIZED_NAMES
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 DeletedItems (**kwargs)
- 
Expand source codeclass DeletedItems(WellknownFolder): DISTINGUISHED_FOLDER_ID = "deleteditems" CONTAINER_CLASS = "IPF.Note" supported_item_models = ITEM_CLASSES LOCALIZED_NAMES = { "da_DK": ("Slettet post",), "de_DE": ("Gelöschte Elemente",), "en_US": ("Deleted Items",), "es_ES": ("Elementos eliminados",), "fr_CA": ("Éléments supprimés",), "nl_NL": ("Verwijderde items",), "ru_RU": ("Удаленные",), "sv_SE": ("Borttaget",), "zh_CN": ("已删除邮件",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
- var supported_item_models
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Directory (**kwargs)
- 
Expand source codeclass Directory(WellknownFolder): DISTINGUISHED_FOLDER_ID = "directory" supported_from = EXCHANGE_2013_SP1Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 DistinguishedFolderId (*args, **kwargs)
- 
Expand source codeclass DistinguishedFolderId(FolderId): """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/distinguishedfolderid""" ELEMENT_NAME = "DistinguishedFolderId" mailbox = MailboxField() @classmethod def from_xml(cls, elem, account): return cls(id=elem.text or None) def clean(self, version=None): from .folders import PublicFoldersRoot super().clean(version=version) if self.id == PublicFoldersRoot.DISTINGUISHED_FOLDER_ID: # Avoid "ErrorInvalidOperation: It is not valid to specify a mailbox with the public folder root" from EWS self.mailbox = None elif not self.mailbox: raise ValueError(f"DistinguishedFolderId {self.id} must have a mailbox")AncestorsClass variables- var ELEMENT_NAME
- var FIELDS
 Static methods- def from_xml(elem, account)
 Instance variables- var mailbox
 Methods- def clean(self, version=None)
- 
Expand source codedef clean(self, version=None): from .folders import PublicFoldersRoot super().clean(version=version) if self.id == PublicFoldersRoot.DISTINGUISHED_FOLDER_ID: # Avoid "ErrorInvalidOperation: It is not valid to specify a mailbox with the public folder root" from EWS self.mailbox = None elif not self.mailbox: raise ValueError(f"DistinguishedFolderId {self.id} must have a mailbox")
 Inherited members
- class DlpPolicyEvaluation (**kwargs)
- 
Expand source codeclass DlpPolicyEvaluation(WellknownFolder): DISTINGUISHED_FOLDER_ID = "dlppolicyevaluation" CONTAINER_CLASS = "IPF.StoreItem.DlpPolicyEvaluation" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Drafts (**kwargs)
- 
Expand source codeclass Drafts(WellknownFolder): CONTAINER_CLASS = "IPF.Note" DISTINGUISHED_FOLDER_ID = "drafts" supported_item_models = MESSAGE_ITEM_CLASSES LOCALIZED_NAMES = { "da_DK": ("Kladder",), "de_DE": ("Entwürfe",), "en_US": ("Drafts",), "es_ES": ("Borradores",), "fr_CA": ("Brouillons",), "nl_NL": ("Concepten",), "ru_RU": ("Черновики",), "sv_SE": ("Utkast",), "zh_CN": ("草稿",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
- var supported_item_models
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 EventCheckPoints (**kwargs)
- 
Expand source codeclass EventCheckPoints(Folder): CONTAINER_CLASS = "IPF.StoreItem.EventCheckPoints"Ancestors- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- Folder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ExchangeSyncData (**kwargs)
- 
Expand source codeclass ExchangeSyncData(NonDeletableFolder): passA mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ExternalContacts (**kwargs)
- 
Expand source codeclass ExternalContacts(NonDeletableFolder): CONTAINER_CLASS = "IPF.Contact" supported_item_models = CONTACT_ITEM_CLASSESA mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var supported_item_models
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Favorites (**kwargs)
- 
Expand source codeclass Favorites(WellknownFolder): DISTINGUISHED_FOLDER_ID = "favorites" CONTAINER_CLASS = "IPF.Note" supported_from = EXCHANGE_2013Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Files (**kwargs)
- 
Expand source codeclass Files(NonDeletableFolder): CONTAINER_CLASS = "IPF.Files" LOCALIZED_NAMES = { "da_DK": ("Filer",), }A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var LOCALIZED_NAMES
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Folder (**kwargs)
- 
Expand source codeclass Folder(BaseFolder): """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/folder""" permission_set = PermissionSetField(field_uri="folder:PermissionSet", supported_from=EXCHANGE_2007_SP1) effective_rights = EffectiveRightsField( field_uri="folder:EffectiveRights", is_read_only=True, supported_from=EXCHANGE_2007_SP1 ) __slots__ = ("_root",) def __init__(self, **kwargs): self._root = kwargs.pop("root", None) # This is a pointer to the root of the folder hierarchy parent = kwargs.pop("parent", None) if parent: if self.root: if parent.root != self.root: raise ValueError("'parent.root' must match 'root'") else: self._root = parent.root if "parent_folder_id" in kwargs and parent.id != kwargs["parent_folder_id"]: raise ValueError("'parent_folder_id' must match 'parent' ID") kwargs["parent_folder_id"] = ParentFolderId(id=parent.id, changekey=parent.changekey) super().__init__(**kwargs) @property def account(self): if self.root is None: return None return self.root.account @property def root(self): return self._root @classmethod def register(cls, *args, **kwargs): if cls is not Folder: raise TypeError("For folders, custom fields must be registered on the Folder class") return super().register(*args, **kwargs) @classmethod def deregister(cls, *args, **kwargs): if cls is not Folder: raise TypeError("For folders, custom fields must be registered on the Folder class") return super().deregister(*args, **kwargs) @property def parent(self): if not self.parent_folder_id: return None if self.parent_folder_id.id == self.id: # Some folders have a parent that references itself. Avoid circular references here return None return self.root.get_folder(self.parent_folder_id) @parent.setter def parent(self, value): if value is None: self.parent_folder_id = None else: if not isinstance(value, BaseFolder): raise InvalidTypeError("value", value, BaseFolder) self._root = value.root self.parent_folder_id = ParentFolderId(id=value.id, changekey=value.changekey) def clean(self, version=None): from .roots import RootOfHierarchy super().clean(version=version) if self.root and not isinstance(self.root, RootOfHierarchy): raise InvalidTypeError("root", self.root, RootOfHierarchy) @classmethod def get_distinguished(cls, root): """Get the distinguished folder for this folder class. :param root: :return: """ return cls._get_distinguished( folder=cls( _distinguished_id=DistinguishedFolderId( id=cls.DISTINGUISHED_FOLDER_ID, mailbox=Mailbox(email_address=root.account.primary_smtp_address), ), root=root, ) ) @classmethod def from_xml_with_root(cls, elem, root): folder = cls.from_xml(elem=elem, account=root.account) folder_cls = cls if cls == Folder: # We were called on the generic Folder class. Try to find a more specific class to return objects as. # # The "FolderClass" element value is the only indication we have in the FindFolder response of which # folder class we should create the folder with. And many folders share the same 'FolderClass' value, e.g. # Inbox and DeletedItems. We want to distinguish between these because otherwise we can't locate the right # folders types for e.g. Account.inbox and Account.trash. # # We should be able to just use the name, but apparently default folder names can be renamed to a set of # localized names using a PowerShell command: # https://docs.microsoft.com/en-us/powershell/module/exchange/client-access/Set-MailboxRegionalConfiguration # # Instead, search for a folder class using the localized name. If none are found, fall back to getting the # folder class by the "FolderClass" value. # # The returned XML may contain neither folder class nor name. In that case, we default to the generic # Folder class. if folder.name: with suppress(KeyError): # TODO: fld_class.LOCALIZED_NAMES is most definitely neither complete nor authoritative folder_cls = root.folder_cls_from_folder_name( folder_name=folder.name, folder_class=folder.folder_class, locale=root.account.locale, ) log.debug("Folder class %s matches localized folder name %s", folder_cls, folder.name) if folder.folder_class and folder_cls == Folder: with suppress(KeyError): folder_cls = cls.folder_cls_from_container_class(container_class=folder.folder_class) log.debug( "Folder class %s matches container class %s (%s)", folder_cls, folder.folder_class, folder.name ) if folder_cls == Folder: log.debug("Fallback to class Folder (folder_class %s, name %s)", folder.folder_class, folder.name) # Some servers return folders in a FindFolder result that have a DistinguishedFolderId element that the same # server cannot handle in a GetFolder request. Only set the DistinguishedFolderId field if we recognize the ID. if folder._distinguished_id and not folder_cls.DISTINGUISHED_FOLDER_ID: folder._distinguished_id = None return folder_cls(root=root, **{f.name: getattr(folder, f.name) for f in folder.FIELDS})AncestorsSubclasses- Birthdays
- CrawlerData
- EventCheckPoints
- FolderMemberships
- FreeBusyCache
- NonDeletableFolder
- RecoveryPoints
- SkypeTeamsMessages
- SwssItems
- WellknownFolder
 Class variables- var FIELDS
 Static methods- def from_xml_with_root(elem, root)
- def get_distinguished(root)
- 
Get the distinguished folder for this folder class. :param root: :return: 
 Instance variables- var effective_rights
- var permission_set
 Methods- def clean(self, version=None)
- 
Expand source codedef clean(self, version=None): from .roots import RootOfHierarchy super().clean(version=version) if self.root and not isinstance(self.root, RootOfHierarchy): raise InvalidTypeError("root", self.root, RootOfHierarchy)
 Inherited members- BaseFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_events
- get_streaming_events
- 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 FolderCollection (account, folders)
- 
Expand source codeclass FolderCollection(SearchableMixIn): """A class that implements an API for searching folders.""" # These fields are required in a FindFolder or GetFolder call to properly identify folder types REQUIRED_FOLDER_FIELDS = ("name", "folder_class") def __init__(self, account, folders): """Implement a search API on a collection of folders. :param account: An Account object :param folders: An iterable of folders, e.g. Folder.walk(), Folder.glob(), or [a.calendar, a.inbox] """ self.account = account self._folders = folders @threaded_cached_property def folders(self): # Resolve the list of folders, in case it's a generator return tuple(self._folders) def __len__(self): return len(self.folders) def __iter__(self): yield from self.folders def get(self, *args, **kwargs): return QuerySet(self).get(*args, **kwargs) def all(self): return QuerySet(self).all() def none(self): return QuerySet(self).none() def filter(self, *args, **kwargs): """Find items in the folder(s). Non-keyword args may be a list of Q instances. Optional extra keyword arguments follow a Django-like QuerySet filter syntax (see https://docs.djangoproject.com/en/1.10/ref/models/querysets/#field-lookups). We don't support '__year' and other date-related lookups. We also don't support '__endswith' or '__iendswith'. We support the additional '__not' lookup in place of Django's exclude() for simple cases. For more complicated cases you need to create a Q object and use ~Q(). Examples: my_account.inbox.filter(datetime_received__gt=EWSDateTime(2016, 1, 1)) my_account.calendar.filter(start__range=(EWSDateTime(2016, 1, 1), EWSDateTime(2017, 1, 1))) my_account.tasks.filter(subject='Hi mom') my_account.tasks.filter(subject__not='Hi mom') my_account.tasks.filter(subject__contains='Foo') my_account.tasks.filter(subject__icontains='foo') 'endswith' and 'iendswith' could be emulated by searching with 'contains' or 'icontains' and then post-processing items. Fetch the field in question with additional_fields and remove items where the search string is not a postfix. """ return QuerySet(self).filter(*args, **kwargs) def exclude(self, *args, **kwargs): return QuerySet(self).exclude(*args, **kwargs) def people(self): return QuerySet(self).people() def view(self, start, end, max_items=None): """Implement the CalendarView option to FindItem. The difference between 'filter' and 'view' is that 'filter' only returns the master CalendarItem for recurring items, while 'view' unfolds recurring items and returns all CalendarItem occurrences as one would normally expect when presenting a calendar. Supports the same semantics as filter, except for 'start' and 'end' keyword attributes which are both required and behave differently than filter. Here, they denote the start and end of the timespan of the view. All items the overlap the timespan are returned (items that end exactly on 'start' are also returned, for some reason). EWS does not allow combining CalendarView with search restrictions (filter and exclude). 'max_items' defines the maximum number of items returned in this view. Optional. :param start: :param end: :param max_items: (Default value = None) :return: """ qs = QuerySet(self) qs.calendar_view = CalendarView(start=start, end=end, max_items=max_items) return qs def allowed_item_fields(self): # Return non-ID fields of all item classes allowed in this folder type fields = set() for item_model in self.supported_item_models: fields.update(set(item_model.supported_fields(version=self.account.version))) return fields @property def supported_item_models(self): return tuple(item_model for folder in self.folders for item_model in folder.supported_item_models) def validate_item_field(self, field, version): # Takes a fieldname, Field or FieldPath object pointing to an item field, and checks that it is valid # for the item types supported by this folder collection. for item_model in self.supported_item_models: try: item_model.validate_field(field=field, version=version) break except InvalidField: continue else: raise InvalidField(f"{field!r} is not a valid field on {self.supported_item_models}") def _rinse_args(self, q, depth, additional_fields, field_validator): if depth is None: depth = self._get_default_item_traversal_depth() if additional_fields: for f in additional_fields: field_validator(field=f, version=self.account.version) if f.field.is_complex: raise ValueError(f"Field {f.field.name!r} not supported for this service") # Build up any restrictions if q.is_empty(): restriction = None query_string = None elif q.query_string: restriction = None query_string = Restriction(q, folders=self.folders, applies_to=Restriction.ITEMS) else: restriction = Restriction(q, folders=self.folders, applies_to=Restriction.ITEMS) query_string = None return depth, restriction, query_string def find_items( self, q, shape=ID_ONLY, depth=None, additional_fields=None, order_fields=None, calendar_view=None, page_size=None, max_items=None, offset=0, ): """Private method to call the FindItem service. :param q: a Q instance containing any restrictions :param shape: controls whether to return (id, changekey) tuples or Item objects. If additional_fields is non-null, we always return Item objects. (Default value = ID_ONLY) :param depth: controls the whether to return soft-deleted items or not. (Default value = None) :param additional_fields: the extra properties we want on the return objects. Default is no properties. Be aware that complex fields can only be fetched with fetch() (i.e. the GetItem service). :param order_fields: the SortOrder fields, if any (Default value = None) :param calendar_view: a CalendarView instance, if any (Default value = None) :param page_size: the requested number of items per page (Default value = None) :param max_items: the max number of items to return (Default value = None) :param offset: the offset relative to the first item in the item collection (Default value = 0) :return: a generator for the returned item IDs or items """ from ..services import FindItem if not self.folders: log.debug("Folder list is empty") return if q.is_never(): log.debug("Query will never return results") return depth, restriction, query_string = self._rinse_args( q=q, depth=depth, additional_fields=additional_fields, field_validator=self.validate_item_field ) if calendar_view is not None and not isinstance(calendar_view, CalendarView): raise InvalidTypeError("calendar_view", calendar_view, CalendarView) log.debug( "Finding %s items in folders %s (shape: %s, depth: %s, additional_fields: %s, restriction: %s)", self.account, self.folders, shape, depth, additional_fields, restriction.q if restriction else None, ) yield from FindItem(account=self.account, page_size=page_size).call( folders=self.folders, additional_fields=additional_fields, restriction=restriction, order_fields=order_fields, shape=shape, query_string=query_string, depth=depth, calendar_view=calendar_view, max_items=calendar_view.max_items if calendar_view else max_items, offset=offset, ) def _get_single_folder(self): if len(self.folders) > 1: raise ValueError("Syncing folder hierarchy can only be done on a single folder") if not self.folders: log.debug("Folder list is empty") return None return self.folders[0] def find_people( self, q, shape=ID_ONLY, depth=None, additional_fields=None, order_fields=None, page_size=None, max_items=None, offset=0, ): """Private method to call the FindPeople service. :param q: a Q instance containing any restrictions :param shape: controls whether to return (id, changekey) tuples or Persona objects. If additional_fields is non-null, we always return Persona objects. (Default value = ID_ONLY) :param depth: controls the whether to return soft-deleted items or not. (Default value = None) :param additional_fields: the extra properties we want on the return objects. Default is no properties. :param order_fields: the SortOrder fields, if any (Default value = None) :param page_size: the requested number of items per page (Default value = None) :param max_items: the max number of items to return (Default value = None) :param offset: the offset relative to the first item in the item collection (Default value = 0) :return: a generator for the returned personas """ from ..services import FindPeople folder = self._get_single_folder() if q.is_never(): log.debug("Query will never return results") return depth, restriction, query_string = self._rinse_args( q=q, depth=depth, additional_fields=additional_fields, field_validator=Persona.validate_field ) yield from FindPeople(account=self.account, page_size=page_size).call( folder=folder, additional_fields=additional_fields, restriction=restriction, order_fields=order_fields, shape=shape, query_string=query_string, depth=depth, max_items=max_items, offset=offset, ) def get_folder_fields(self, target_cls, is_complex=None): return { FieldPath(field=f) for f in target_cls.supported_fields(version=self.account.version) if is_complex is None or f.is_complex is is_complex } def _get_target_cls(self): # We may have root folders that don't support the same set of fields as normal folders. If there is a mix of # both folder types in self.folders, raise an error, so we don't risk losing some fields in the query. from .base import Folder from .roots import RootOfHierarchy has_roots = False has_non_roots = False for f in self.folders: if isinstance(f, RootOfHierarchy): if has_non_roots: raise ValueError(f"Cannot call GetFolder on a mix of folder types: {self.folders}") has_roots = True else: if has_roots: raise ValueError(f"Cannot call GetFolder on a mix of folder types: {self.folders}") has_non_roots = True return RootOfHierarchy if has_roots else Folder def _get_default_traversal_depth(self, traversal_attr): unique_depths = {getattr(f, traversal_attr) for f in self.folders} if len(unique_depths) == 1: return unique_depths.pop() raise ValueError( f"Folders in this collection do not have a common {traversal_attr} value. You need to define an explicit " f"traversal depth with QuerySet.depth() (values: {unique_depths})" ) def _get_default_item_traversal_depth(self): # When searching folders, some folders require 'Shallow' and others 'Associated' traversal depth. return self._get_default_traversal_depth("DEFAULT_ITEM_TRAVERSAL_DEPTH") def _get_default_folder_traversal_depth(self): # When searching folders, some folders require 'Shallow' and others 'Deep' traversal depth. return self._get_default_traversal_depth("DEFAULT_FOLDER_TRAVERSAL_DEPTH") def resolve(self): # Looks up the folders or folder IDs in the collection and returns full Folder instances with all fields set. from .base import BaseFolder resolveable_folders = [] for f in self.folders: if isinstance(f, BaseFolder) and not f.get_folder_allowed: log.debug("GetFolder not allowed on folder %s. Non-complex fields must be fetched with FindFolder", f) yield f else: resolveable_folders.append(f) # Fetch all properties for the remaining folders of folder IDs additional_fields = self.get_folder_fields(target_cls=self._get_target_cls()) yield from self.__class__(account=self.account, folders=resolveable_folders).get_folders( additional_fields=additional_fields ) @require_account def find_folders( self, q=None, shape=ID_ONLY, depth=None, additional_fields=None, page_size=None, max_items=None, offset=0 ): from ..services import FindFolder # 'depth' controls whether to return direct children or recurse into sub-folders from .base import BaseFolder, Folder if q is None: q = Q() if not self.folders: log.debug("Folder list is empty") return if q.is_never(): log.debug("Query will never return results") return if q.is_empty(): restriction = None else: restriction = Restriction(q, folders=self.folders, applies_to=Restriction.FOLDERS) if depth is None: depth = self._get_default_folder_traversal_depth() if additional_fields is None: # Default to all non-complex properties. Sub-folders will always be of class Folder additional_fields = self.get_folder_fields(target_cls=Folder, is_complex=False) else: for f in additional_fields: if f.field.is_complex: raise ValueError(f"find_folders() does not support field {f.field.name!r}. Use get_folders().") # Add required fields additional_fields.update( (FieldPath(field=BaseFolder.get_field_by_fieldname(f)) for f in self.REQUIRED_FOLDER_FIELDS) ) yield from FindFolder(account=self.account, page_size=page_size).call( folders=self.folders, additional_fields=additional_fields, restriction=restriction, shape=shape, depth=depth, max_items=max_items, offset=offset, ) def get_folders(self, additional_fields=None): from ..services import GetFolder # Expand folders with their full set of properties from .base import BaseFolder if not self.folders: log.debug("Folder list is empty") return if additional_fields is None: # Default to all complex properties additional_fields = self.get_folder_fields(target_cls=self._get_target_cls(), is_complex=True) # Add required fields additional_fields.update( (FieldPath(field=BaseFolder.get_field_by_fieldname(f)) for f in self.REQUIRED_FOLDER_FIELDS) ) yield from GetFolder(account=self.account).call( folders=self.folders, additional_fields=additional_fields, shape=ID_ONLY, ) def subscribe_to_pull(self, event_types=None, watermark=None, timeout=60): from ..services import SubscribeToPull if not self.folders: log.debug("Folder list is empty") return None if event_types is None: event_types = SubscribeToPull.EVENT_TYPES return SubscribeToPull(account=self.account).get( folders=self.folders, event_types=event_types, watermark=watermark, timeout=timeout, ) def subscribe_to_push(self, callback_url, event_types=None, watermark=None, status_frequency=1): from ..services import SubscribeToPush if not self.folders: log.debug("Folder list is empty") return None if event_types is None: event_types = SubscribeToPush.EVENT_TYPES return SubscribeToPush(account=self.account).get( folders=self.folders, event_types=event_types, watermark=watermark, status_frequency=status_frequency, url=callback_url, ) def subscribe_to_streaming(self, event_types=None): from ..services import SubscribeToStreaming if not self.folders: log.debug("Folder list is empty") return None if event_types is None: event_types = SubscribeToStreaming.EVENT_TYPES return SubscribeToStreaming(account=self.account).get(folders=self.folders, event_types=event_types) def pull_subscription(self, **kwargs): return PullSubscription(target=self, **kwargs) def push_subscription(self, **kwargs): return PushSubscription(target=self, **kwargs) def streaming_subscription(self, **kwargs): return StreamingSubscription(target=self, **kwargs) def unsubscribe(self, subscription_id): """Unsubscribe. Only applies to pull and streaming notifications. :param subscription_id: A subscription ID as acquired by .subscribe_to_[pull|streaming]() :return: True This method doesn't need the current collection instance, but it makes sense to keep the method along the other sync methods. """ from ..services import Unsubscribe return Unsubscribe(account=self.account).get(subscription_id=subscription_id) def sync_items(self, sync_state=None, only_fields=None, ignore=None, max_changes_returned=None, sync_scope=None): from ..services import SyncFolderItems folder = self._get_single_folder() if only_fields is None: # We didn't restrict list of field paths. Get all fields from the server, including extended properties. additional_fields = {FieldPath(field=f) for f in folder.allowed_item_fields(version=self.account.version)} else: for field in only_fields: folder.validate_item_field(field=field, version=self.account.version) # Remove ItemId and ChangeKey. We get them unconditionally additional_fields = {f for f in folder.normalize_fields(fields=only_fields) if not f.field.is_attribute} svc = SyncFolderItems(account=self.account) while True: yield from svc.call( folder=folder, shape=ID_ONLY, additional_fields=additional_fields, sync_state=sync_state, ignore=ignore, max_changes_returned=max_changes_returned, sync_scope=sync_scope, ) if svc.sync_state == sync_state: # We sometimes get the same sync_state back, even though includes_last_item_in_range is False. Stop here break sync_state = svc.sync_state # Set the new sync state in the next call if svc.includes_last_item_in_range: # Try again if there are more items break raise SyncCompleted(sync_state=svc.sync_state) def sync_hierarchy(self, sync_state=None, only_fields=None): from ..services import SyncFolderHierarchy folder = self._get_single_folder() if only_fields is None: # We didn't restrict list of field paths. Get all fields from the server, including extended properties. additional_fields = {FieldPath(field=f) for f in folder.supported_fields(version=self.account.version)} else: additional_fields = set() for field_name in only_fields: folder.validate_field(field=field_name, version=self.account.version) f = folder.get_field_by_fieldname(fieldname=field_name) if not f.is_attribute: # Remove ItemId and ChangeKey. We get them unconditionally additional_fields.add(FieldPath(field=f)) # Add required fields additional_fields.update( (FieldPath(field=folder.get_field_by_fieldname(f)) for f in self.REQUIRED_FOLDER_FIELDS) ) svc = SyncFolderHierarchy(account=self.account) while True: yield from svc.call( folder=folder, shape=ID_ONLY, additional_fields=additional_fields, sync_state=sync_state, ) if svc.sync_state == sync_state: # We sometimes get the same sync_state back, even though includes_last_item_in_range is False. Stop here break sync_state = svc.sync_state # Set the new sync state in the next call if svc.includes_last_item_in_range: # Try again if there are more items break raise SyncCompleted(sync_state=svc.sync_state)A class that implements an API for searching folders. Implement a search API on a collection of folders. :param account: An Account object :param folders: An iterable of folders, e.g. Folder.walk(), Folder.glob(), or [a.calendar, a.inbox] AncestorsClass variables- var REQUIRED_FOLDER_FIELDS
 Instance variables- var folders
- 
Expand source codedef __get__(self, obj, cls): if obj is None: return self obj_dict = obj.__dict__ name = self.func.__name__ with self.lock: try: # check if the value was computed before the lock was acquired return obj_dict[name] except KeyError: # if not, do the calculation and release the lock return obj_dict.setdefault(name, self.func(obj))
- prop supported_item_models
- 
Expand source code@property def supported_item_models(self): return tuple(item_model for folder in self.folders for item_model in folder.supported_item_models)
 Methods- def allowed_item_fields(self)
- 
Expand source codedef allowed_item_fields(self): # Return non-ID fields of all item classes allowed in this folder type fields = set() for item_model in self.supported_item_models: fields.update(set(item_model.supported_fields(version=self.account.version))) return fields
- def filter(self, *args, **kwargs)
- 
Expand source codedef filter(self, *args, **kwargs): """Find items in the folder(s). Non-keyword args may be a list of Q instances. Optional extra keyword arguments follow a Django-like QuerySet filter syntax (see https://docs.djangoproject.com/en/1.10/ref/models/querysets/#field-lookups). We don't support '__year' and other date-related lookups. We also don't support '__endswith' or '__iendswith'. We support the additional '__not' lookup in place of Django's exclude() for simple cases. For more complicated cases you need to create a Q object and use ~Q(). Examples: my_account.inbox.filter(datetime_received__gt=EWSDateTime(2016, 1, 1)) my_account.calendar.filter(start__range=(EWSDateTime(2016, 1, 1), EWSDateTime(2017, 1, 1))) my_account.tasks.filter(subject='Hi mom') my_account.tasks.filter(subject__not='Hi mom') my_account.tasks.filter(subject__contains='Foo') my_account.tasks.filter(subject__icontains='foo') 'endswith' and 'iendswith' could be emulated by searching with 'contains' or 'icontains' and then post-processing items. Fetch the field in question with additional_fields and remove items where the search string is not a postfix. """ return QuerySet(self).filter(*args, **kwargs)Find items in the folder(s). Non-keyword args may be a list of Q instances. Optional extra keyword arguments follow a Django-like QuerySet filter syntax (see https://docs.djangoproject.com/en/1.10/ref/models/querysets/#field-lookups). We don't support '__year' and other date-related lookups. We also don't support '__endswith' or '__iendswith'. We support the additional '__not' lookup in place of Django's exclude() for simple cases. For more complicated cases you need to create a Q object and use ~Q(). Examplesmy_account.inbox.filter(datetime_received__gt=EWSDateTime(2016, 1, 1)) my_account.calendar.filter(start__range=(EWSDateTime(2016, 1, 1), EWSDateTime(2017, 1, 1))) my_account.tasks.filter(subject='Hi mom') my_account.tasks.filter(subject__not='Hi mom') my_account.tasks.filter(subject__contains='Foo') my_account.tasks.filter(subject__icontains='foo') 'endswith' and 'iendswith' could be emulated by searching with 'contains' or 'icontains' and then post-processing items. Fetch the field in question with additional_fields and remove items where the search string is not a postfix. 
- def find_folders(self,
 q=None,
 shape='IdOnly',
 depth=None,
 additional_fields=None,
 page_size=None,
 max_items=None,
 offset=0)
- 
Expand source code@require_account def find_folders( self, q=None, shape=ID_ONLY, depth=None, additional_fields=None, page_size=None, max_items=None, offset=0 ): from ..services import FindFolder # 'depth' controls whether to return direct children or recurse into sub-folders from .base import BaseFolder, Folder if q is None: q = Q() if not self.folders: log.debug("Folder list is empty") return if q.is_never(): log.debug("Query will never return results") return if q.is_empty(): restriction = None else: restriction = Restriction(q, folders=self.folders, applies_to=Restriction.FOLDERS) if depth is None: depth = self._get_default_folder_traversal_depth() if additional_fields is None: # Default to all non-complex properties. Sub-folders will always be of class Folder additional_fields = self.get_folder_fields(target_cls=Folder, is_complex=False) else: for f in additional_fields: if f.field.is_complex: raise ValueError(f"find_folders() does not support field {f.field.name!r}. Use get_folders().") # Add required fields additional_fields.update( (FieldPath(field=BaseFolder.get_field_by_fieldname(f)) for f in self.REQUIRED_FOLDER_FIELDS) ) yield from FindFolder(account=self.account, page_size=page_size).call( folders=self.folders, additional_fields=additional_fields, restriction=restriction, shape=shape, depth=depth, max_items=max_items, offset=offset, )
- def find_items(self,
 q,
 shape='IdOnly',
 depth=None,
 additional_fields=None,
 order_fields=None,
 calendar_view=None,
 page_size=None,
 max_items=None,
 offset=0)
- 
Expand source codedef find_items( self, q, shape=ID_ONLY, depth=None, additional_fields=None, order_fields=None, calendar_view=None, page_size=None, max_items=None, offset=0, ): """Private method to call the FindItem service. :param q: a Q instance containing any restrictions :param shape: controls whether to return (id, changekey) tuples or Item objects. If additional_fields is non-null, we always return Item objects. (Default value = ID_ONLY) :param depth: controls the whether to return soft-deleted items or not. (Default value = None) :param additional_fields: the extra properties we want on the return objects. Default is no properties. Be aware that complex fields can only be fetched with fetch() (i.e. the GetItem service). :param order_fields: the SortOrder fields, if any (Default value = None) :param calendar_view: a CalendarView instance, if any (Default value = None) :param page_size: the requested number of items per page (Default value = None) :param max_items: the max number of items to return (Default value = None) :param offset: the offset relative to the first item in the item collection (Default value = 0) :return: a generator for the returned item IDs or items """ from ..services import FindItem if not self.folders: log.debug("Folder list is empty") return if q.is_never(): log.debug("Query will never return results") return depth, restriction, query_string = self._rinse_args( q=q, depth=depth, additional_fields=additional_fields, field_validator=self.validate_item_field ) if calendar_view is not None and not isinstance(calendar_view, CalendarView): raise InvalidTypeError("calendar_view", calendar_view, CalendarView) log.debug( "Finding %s items in folders %s (shape: %s, depth: %s, additional_fields: %s, restriction: %s)", self.account, self.folders, shape, depth, additional_fields, restriction.q if restriction else None, ) yield from FindItem(account=self.account, page_size=page_size).call( folders=self.folders, additional_fields=additional_fields, restriction=restriction, order_fields=order_fields, shape=shape, query_string=query_string, depth=depth, calendar_view=calendar_view, max_items=calendar_view.max_items if calendar_view else max_items, offset=offset, )Private method to call the FindItem service. :param q: a Q instance containing any restrictions :param shape: controls whether to return (id, changekey) tuples or Item objects. If additional_fields is non-null, we always return Item objects. (Default value = ID_ONLY) :param depth: controls the whether to return soft-deleted items or not. (Default value = None) :param additional_fields: the extra properties we want on the return objects. Default is no properties. Be aware that complex fields can only be fetched with fetch() (i.e. the GetItem service). :param order_fields: the SortOrder fields, if any (Default value = None) :param calendar_view: a CalendarView instance, if any (Default value = None) :param page_size: the requested number of items per page (Default value = None) :param max_items: the max number of items to return (Default value = None) :param offset: the offset relative to the first item in the item collection (Default value = 0) :return: a generator for the returned item IDs or items 
- def find_people(self,
 q,
 shape='IdOnly',
 depth=None,
 additional_fields=None,
 order_fields=None,
 page_size=None,
 max_items=None,
 offset=0)
- 
Expand source codedef find_people( self, q, shape=ID_ONLY, depth=None, additional_fields=None, order_fields=None, page_size=None, max_items=None, offset=0, ): """Private method to call the FindPeople service. :param q: a Q instance containing any restrictions :param shape: controls whether to return (id, changekey) tuples or Persona objects. If additional_fields is non-null, we always return Persona objects. (Default value = ID_ONLY) :param depth: controls the whether to return soft-deleted items or not. (Default value = None) :param additional_fields: the extra properties we want on the return objects. Default is no properties. :param order_fields: the SortOrder fields, if any (Default value = None) :param page_size: the requested number of items per page (Default value = None) :param max_items: the max number of items to return (Default value = None) :param offset: the offset relative to the first item in the item collection (Default value = 0) :return: a generator for the returned personas """ from ..services import FindPeople folder = self._get_single_folder() if q.is_never(): log.debug("Query will never return results") return depth, restriction, query_string = self._rinse_args( q=q, depth=depth, additional_fields=additional_fields, field_validator=Persona.validate_field ) yield from FindPeople(account=self.account, page_size=page_size).call( folder=folder, additional_fields=additional_fields, restriction=restriction, order_fields=order_fields, shape=shape, query_string=query_string, depth=depth, max_items=max_items, offset=offset, )Private method to call the FindPeople service. :param q: a Q instance containing any restrictions :param shape: controls whether to return (id, changekey) tuples or Persona objects. If additional_fields is non-null, we always return Persona objects. (Default value = ID_ONLY) :param depth: controls the whether to return soft-deleted items or not. (Default value = None) :param additional_fields: the extra properties we want on the return objects. Default is no properties. :param order_fields: the SortOrder fields, if any (Default value = None) :param page_size: the requested number of items per page (Default value = None) :param max_items: the max number of items to return (Default value = None) :param offset: the offset relative to the first item in the item collection (Default value = 0) :return: a generator for the returned personas 
- def get_folder_fields(self, target_cls, is_complex=None)
- 
Expand source codedef get_folder_fields(self, target_cls, is_complex=None): return { FieldPath(field=f) for f in target_cls.supported_fields(version=self.account.version) if is_complex is None or f.is_complex is is_complex }
- def get_folders(self, additional_fields=None)
- 
Expand source codedef get_folders(self, additional_fields=None): from ..services import GetFolder # Expand folders with their full set of properties from .base import BaseFolder if not self.folders: log.debug("Folder list is empty") return if additional_fields is None: # Default to all complex properties additional_fields = self.get_folder_fields(target_cls=self._get_target_cls(), is_complex=True) # Add required fields additional_fields.update( (FieldPath(field=BaseFolder.get_field_by_fieldname(f)) for f in self.REQUIRED_FOLDER_FIELDS) ) yield from GetFolder(account=self.account).call( folders=self.folders, additional_fields=additional_fields, shape=ID_ONLY, )
- def pull_subscription(self, **kwargs)
- 
Expand source codedef pull_subscription(self, **kwargs): return PullSubscription(target=self, **kwargs)
- def push_subscription(self, **kwargs)
- 
Expand source codedef push_subscription(self, **kwargs): return PushSubscription(target=self, **kwargs)
- def resolve(self)
- 
Expand source codedef resolve(self): # Looks up the folders or folder IDs in the collection and returns full Folder instances with all fields set. from .base import BaseFolder resolveable_folders = [] for f in self.folders: if isinstance(f, BaseFolder) and not f.get_folder_allowed: log.debug("GetFolder not allowed on folder %s. Non-complex fields must be fetched with FindFolder", f) yield f else: resolveable_folders.append(f) # Fetch all properties for the remaining folders of folder IDs additional_fields = self.get_folder_fields(target_cls=self._get_target_cls()) yield from self.__class__(account=self.account, folders=resolveable_folders).get_folders( additional_fields=additional_fields )
- def streaming_subscription(self, **kwargs)
- 
Expand source codedef streaming_subscription(self, **kwargs): return StreamingSubscription(target=self, **kwargs)
- def subscribe_to_pull(self, event_types=None, watermark=None, timeout=60)
- 
Expand source codedef subscribe_to_pull(self, event_types=None, watermark=None, timeout=60): from ..services import SubscribeToPull if not self.folders: log.debug("Folder list is empty") return None if event_types is None: event_types = SubscribeToPull.EVENT_TYPES return SubscribeToPull(account=self.account).get( folders=self.folders, event_types=event_types, watermark=watermark, timeout=timeout, )
- def subscribe_to_push(self, callback_url, event_types=None, watermark=None, status_frequency=1)
- 
Expand source codedef subscribe_to_push(self, callback_url, event_types=None, watermark=None, status_frequency=1): from ..services import SubscribeToPush if not self.folders: log.debug("Folder list is empty") return None if event_types is None: event_types = SubscribeToPush.EVENT_TYPES return SubscribeToPush(account=self.account).get( folders=self.folders, event_types=event_types, watermark=watermark, status_frequency=status_frequency, url=callback_url, )
- def subscribe_to_streaming(self, event_types=None)
- 
Expand source codedef subscribe_to_streaming(self, event_types=None): from ..services import SubscribeToStreaming if not self.folders: log.debug("Folder list is empty") return None if event_types is None: event_types = SubscribeToStreaming.EVENT_TYPES return SubscribeToStreaming(account=self.account).get(folders=self.folders, event_types=event_types)
- def sync_hierarchy(self, sync_state=None, only_fields=None)
- 
Expand source codedef sync_hierarchy(self, sync_state=None, only_fields=None): from ..services import SyncFolderHierarchy folder = self._get_single_folder() if only_fields is None: # We didn't restrict list of field paths. Get all fields from the server, including extended properties. additional_fields = {FieldPath(field=f) for f in folder.supported_fields(version=self.account.version)} else: additional_fields = set() for field_name in only_fields: folder.validate_field(field=field_name, version=self.account.version) f = folder.get_field_by_fieldname(fieldname=field_name) if not f.is_attribute: # Remove ItemId and ChangeKey. We get them unconditionally additional_fields.add(FieldPath(field=f)) # Add required fields additional_fields.update( (FieldPath(field=folder.get_field_by_fieldname(f)) for f in self.REQUIRED_FOLDER_FIELDS) ) svc = SyncFolderHierarchy(account=self.account) while True: yield from svc.call( folder=folder, shape=ID_ONLY, additional_fields=additional_fields, sync_state=sync_state, ) if svc.sync_state == sync_state: # We sometimes get the same sync_state back, even though includes_last_item_in_range is False. Stop here break sync_state = svc.sync_state # Set the new sync state in the next call if svc.includes_last_item_in_range: # Try again if there are more items break raise SyncCompleted(sync_state=svc.sync_state)
- def sync_items(self,
 sync_state=None,
 only_fields=None,
 ignore=None,
 max_changes_returned=None,
 sync_scope=None)
- 
Expand source codedef sync_items(self, sync_state=None, only_fields=None, ignore=None, max_changes_returned=None, sync_scope=None): from ..services import SyncFolderItems folder = self._get_single_folder() if only_fields is None: # We didn't restrict list of field paths. Get all fields from the server, including extended properties. additional_fields = {FieldPath(field=f) for f in folder.allowed_item_fields(version=self.account.version)} else: for field in only_fields: folder.validate_item_field(field=field, version=self.account.version) # Remove ItemId and ChangeKey. We get them unconditionally additional_fields = {f for f in folder.normalize_fields(fields=only_fields) if not f.field.is_attribute} svc = SyncFolderItems(account=self.account) while True: yield from svc.call( folder=folder, shape=ID_ONLY, additional_fields=additional_fields, sync_state=sync_state, ignore=ignore, max_changes_returned=max_changes_returned, sync_scope=sync_scope, ) if svc.sync_state == sync_state: # We sometimes get the same sync_state back, even though includes_last_item_in_range is False. Stop here break sync_state = svc.sync_state # Set the new sync state in the next call if svc.includes_last_item_in_range: # Try again if there are more items break raise SyncCompleted(sync_state=svc.sync_state)
- def unsubscribe(self, subscription_id)
- 
Expand source codedef unsubscribe(self, subscription_id): """Unsubscribe. Only applies to pull and streaming notifications. :param subscription_id: A subscription ID as acquired by .subscribe_to_[pull|streaming]() :return: True This method doesn't need the current collection instance, but it makes sense to keep the method along the other sync methods. """ from ..services import Unsubscribe return Unsubscribe(account=self.account).get(subscription_id=subscription_id)Unsubscribe. Only applies to pull and streaming notifications. :param subscription_id: A subscription ID as acquired by .subscribe_to_pull|streaming :return: True This method doesn't need the current collection instance, but it makes sense to keep the method along the other sync methods. 
- def validate_item_field(self, field, version)
- 
Expand source codedef validate_item_field(self, field, version): # Takes a fieldname, Field or FieldPath object pointing to an item field, and checks that it is valid # for the item types supported by this folder collection. for item_model in self.supported_item_models: try: item_model.validate_field(field=field, version=version) break except InvalidField: continue else: raise InvalidField(f"{field!r} is not a valid field on {self.supported_item_models}")
- def view(self, start, end, max_items=None)
- 
Expand source codedef view(self, start, end, max_items=None): """Implement the CalendarView option to FindItem. The difference between 'filter' and 'view' is that 'filter' only returns the master CalendarItem for recurring items, while 'view' unfolds recurring items and returns all CalendarItem occurrences as one would normally expect when presenting a calendar. Supports the same semantics as filter, except for 'start' and 'end' keyword attributes which are both required and behave differently than filter. Here, they denote the start and end of the timespan of the view. All items the overlap the timespan are returned (items that end exactly on 'start' are also returned, for some reason). EWS does not allow combining CalendarView with search restrictions (filter and exclude). 'max_items' defines the maximum number of items returned in this view. Optional. :param start: :param end: :param max_items: (Default value = None) :return: """ qs = QuerySet(self) qs.calendar_view = CalendarView(start=start, end=end, max_items=max_items) return qsImplement the CalendarView option to FindItem. The difference between 'filter' and 'view' is that 'filter' only returns the master CalendarItem for recurring items, while 'view' unfolds recurring items and returns all CalendarItem occurrences as one would normally expect when presenting a calendar. Supports the same semantics as filter, except for 'start' and 'end' keyword attributes which are both required and behave differently than filter. Here, they denote the start and end of the timespan of the view. All items the overlap the timespan are returned (items that end exactly on 'start' are also returned, for some reason). EWS does not allow combining CalendarView with search restrictions (filter and exclude). 'max_items' defines the maximum number of items returned in this view. Optional. :param start: :param end: :param max_items: (Default value = None) :return: 
 Inherited members
- class FolderId (*args, **kwargs)
- 
Expand source codeclass FolderId(ItemId): """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/folderid""" ELEMENT_NAME = "FolderId"AncestorsSubclassesClass variables- var ELEMENT_NAME
 Inherited members
- class FolderMemberships (**kwargs)
- 
Expand source codeclass FolderMemberships(Folder): CONTAINER_CLASS = "IPF.Task" LOCALIZED_NAMES = { None: ("Folder Memberships",), }Ancestors- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var LOCALIZED_NAMES
 Inherited members- Folder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 FolderQuerySet (folder_collection)
- 
Expand source codeclass FolderQuerySet: """A QuerySet-like class for finding sub-folders of a folder collection.""" def __init__(self, folder_collection): from .collections import FolderCollection if not isinstance(folder_collection, FolderCollection): raise InvalidTypeError("folder_collection", folder_collection, FolderCollection) self.folder_collection = folder_collection self.q = Q() # Default to no restrictions self.only_fields = None self._depth = None def _copy_cls(self): return self.__class__(folder_collection=self.folder_collection) def _copy_self(self): """Chaining operations must make a copy of self before making any modifications.""" new_qs = self._copy_cls() new_qs.q = deepcopy(self.q) new_qs.only_fields = self.only_fields new_qs._depth = self._depth return new_qs def only(self, *args): """Restrict the fields returned. 'name' and 'folder_class' are always returned.""" from .base import Folder # Sub-folders will always be of class Folder all_fields = self.folder_collection.get_folder_fields(target_cls=Folder, is_complex=None) all_fields.update(Folder.attribute_fields()) only_fields = [] for arg in args: for field_path in all_fields: if field_path.field.name == arg: only_fields.append(field_path) break else: raise InvalidField(f"Unknown field {arg!r} on folders {self.folder_collection.folders}") new_qs = self._copy_self() new_qs.only_fields = only_fields return new_qs def depth(self, depth): """Specify the search depth. Possible values are: SHALLOW or DEEP. :param depth: """ new_qs = self._copy_self() new_qs._depth = depth return new_qs def get(self, *args, **kwargs): """Return the query result as exactly one item. Raises DoesNotExist if there are no results, and MultipleObjectsReturned if there are multiple results. """ from .base import Folder from .collections import FolderCollection if not args and set(kwargs) in ({"id"}, {"id", "changekey"}): roots = {f.root for f in self.folder_collection.folders} if len(roots) != 1: raise ValueError(f"All folders must have the same root hierarchy ({roots})") folders = list( FolderCollection( account=self.folder_collection.account, folders=[ Folder( _id=FolderId(**kwargs), root=roots.pop(), ) ], ).resolve() ) elif args or kwargs: folders = list(self.filter(*args, **kwargs)) else: folders = list(self.all()) if not folders: raise DoesNotExist("Could not find a child folder matching the query") if len(folders) != 1: raise MultipleObjectsReturned(f"Expected result length 1, but got {folders}") f = folders[0] if isinstance(f, Exception): raise f return f def all(self): """ """ new_qs = self._copy_self() return new_qs def filter(self, *args, **kwargs): """Add restrictions to the folder search.""" new_qs = self._copy_self() q = Q(*args, **kwargs) new_qs.q = new_qs.q & q return new_qs def __iter__(self): return self._query() def _query(self): from .base import Folder from .collections import FolderCollection if self.only_fields is None: # Sub-folders will always be of class Folder non_complex_fields = self.folder_collection.get_folder_fields(target_cls=Folder, is_complex=False) complex_fields = self.folder_collection.get_folder_fields(target_cls=Folder, is_complex=True) else: non_complex_fields = {f for f in self.only_fields if not f.field.is_complex} complex_fields = {f for f in self.only_fields if f.field.is_complex} # First, fetch all non-complex fields using FindFolder. We do this because some folders do not support # GetFolder, but we still want to get as much information as possible. folders = self.folder_collection.find_folders(q=self.q, depth=self._depth, additional_fields=non_complex_fields) if not complex_fields: yield from folders return # Fetch all properties for the found folders resolveable_folders = [] for f in folders: if isinstance(f, Exception): yield f continue if not f.get_folder_allowed: log.debug("GetFolder not allowed on folder %s. Non-complex fields must be fetched with FindFolder", f) yield f else: resolveable_folders.append(f) # Get the complex fields using GetFolder, for the folders that support it, and add the extra field values complex_folders = FolderCollection( account=self.folder_collection.account, folders=resolveable_folders ).get_folders(additional_fields=complex_fields) for f, complex_f in zip(resolveable_folders, complex_folders): if isinstance(f, MISSING_FOLDER_ERRORS): # We were unlucky. The folder disappeared between the FindFolder and the GetFolder calls continue if isinstance(complex_f, Exception): yield complex_f continue # Add the extra field values to the folders we fetched with find_folders() if f.__class__ != complex_f.__class__: raise ValueError(f"Type mismatch: {f} vs {complex_f}") for complex_field in complex_fields: field_name = complex_field.field.name setattr(f, field_name, getattr(complex_f, field_name)) yield fA QuerySet-like class for finding sub-folders of a folder collection. SubclassesMethods- def all(self)
- 
Expand source codedef all(self): """ """ new_qs = self._copy_self() return new_qs
- def depth(self, depth)
- 
Expand source codedef depth(self, depth): """Specify the search depth. Possible values are: SHALLOW or DEEP. :param depth: """ new_qs = self._copy_self() new_qs._depth = depth return new_qsSpecify the search depth. Possible values are: SHALLOW or DEEP. :param depth: 
- def filter(self, *args, **kwargs)
- 
Expand source codedef filter(self, *args, **kwargs): """Add restrictions to the folder search.""" new_qs = self._copy_self() q = Q(*args, **kwargs) new_qs.q = new_qs.q & q return new_qsAdd restrictions to the folder search. 
- def get(self, *args, **kwargs)
- 
Expand source codedef get(self, *args, **kwargs): """Return the query result as exactly one item. Raises DoesNotExist if there are no results, and MultipleObjectsReturned if there are multiple results. """ from .base import Folder from .collections import FolderCollection if not args and set(kwargs) in ({"id"}, {"id", "changekey"}): roots = {f.root for f in self.folder_collection.folders} if len(roots) != 1: raise ValueError(f"All folders must have the same root hierarchy ({roots})") folders = list( FolderCollection( account=self.folder_collection.account, folders=[ Folder( _id=FolderId(**kwargs), root=roots.pop(), ) ], ).resolve() ) elif args or kwargs: folders = list(self.filter(*args, **kwargs)) else: folders = list(self.all()) if not folders: raise DoesNotExist("Could not find a child folder matching the query") if len(folders) != 1: raise MultipleObjectsReturned(f"Expected result length 1, but got {folders}") f = folders[0] if isinstance(f, Exception): raise f return fReturn the query result as exactly one item. Raises DoesNotExist if there are no results, and MultipleObjectsReturned if there are multiple results. 
- def only(self, *args)
- 
Expand source codedef only(self, *args): """Restrict the fields returned. 'name' and 'folder_class' are always returned.""" from .base import Folder # Sub-folders will always be of class Folder all_fields = self.folder_collection.get_folder_fields(target_cls=Folder, is_complex=None) all_fields.update(Folder.attribute_fields()) only_fields = [] for arg in args: for field_path in all_fields: if field_path.field.name == arg: only_fields.append(field_path) break else: raise InvalidField(f"Unknown field {arg!r} on folders {self.folder_collection.folders}") new_qs = self._copy_self() new_qs.only_fields = only_fields return new_qsRestrict the fields returned. 'name' and 'folder_class' are always returned. 
 
- class FreeBusyCache (**kwargs)
- 
Expand source codeclass FreeBusyCache(Folder): CONTAINER_CLASS = "IPF.StoreItem.FreeBusyCache"Ancestors- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- Folder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 FreebusyData (**kwargs)
- 
Expand source codeclass FreebusyData(NonDeletableFolder): LOCALIZED_NAMES = { None: ("Freebusy Data",), }A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var LOCALIZED_NAMES
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Friends (**kwargs)
- 
Expand source codeclass Friends(NonDeletableFolder): CONTAINER_CLASS = "IPF.Note" supported_item_models = CONTACT_ITEM_CLASSES LOCALIZED_NAMES = { "de_DE": ("Bekannte",), }A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var LOCALIZED_NAMES
- var supported_item_models
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 FromFavoriteSenders (**kwargs)
- 
Expand source codeclass FromFavoriteSenders(WellknownFolder): DISTINGUISHED_FOLDER_ID = "fromfavoritesenders" CONTAINER_CLASS = "IPF.Note" supported_from = EXCHANGE_O365 LOCALIZED_NAMES = { "da_DK": ("Personer jeg kender",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 GALContacts (**kwargs)
- 
Expand source codeclass GALContacts(NonDeletableFolder): CONTAINER_CLASS = "IPF.Contact.GalContacts" supported_item_models = CONTACT_ITEM_CLASSES LOCALIZED_NAMES = { None: ("GAL Contacts",), }A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var LOCALIZED_NAMES
- var supported_item_models
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 GraphAnalytics (**kwargs)
- 
Expand source codeclass GraphAnalytics(NonDeletableFolder): CONTAINER_CLASS = "IPF.StoreItem.GraphAnalytics"A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 IMContactList (**kwargs)
- 
Expand source codeclass IMContactList(WellknownFolder): DISTINGUISHED_FOLDER_ID = "imcontactlist" CONTAINER_CLASS = "IPF.Contact.MOC.ImContactList" supported_from = EXCHANGE_2013Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Inbox (**kwargs)
- 
Expand source codeclass Inbox(WellknownFolder): CONTAINER_CLASS = "IPF.Note" DISTINGUISHED_FOLDER_ID = "inbox" supported_item_models = MESSAGE_ITEM_CLASSES LOCALIZED_NAMES = { "da_DK": ("Indbakke",), "de_DE": ("Posteingang",), "en_US": ("Inbox",), "es_ES": ("Bandeja de entrada",), "fr_CA": ("Boîte de réception",), "nl_NL": ("Postvak IN",), "ru_RU": ("Входящие",), "sv_SE": ("Inkorgen",), "zh_CN": ("收件箱",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
- var supported_item_models
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Inference (**kwargs)
- 
Expand source codeclass Inference(WellknownFolder): DISTINGUISHED_FOLDER_ID = "inference" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Journal (**kwargs)
- 
Expand source codeclass Journal(WellknownFolder): CONTAINER_CLASS = "IPF.Journal" DISTINGUISHED_FOLDER_ID = "journal"Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 JunkEmail (**kwargs)
- 
Expand source codeclass JunkEmail(WellknownFolder): CONTAINER_CLASS = "IPF.Note" DISTINGUISHED_FOLDER_ID = "junkemail" supported_item_models = MESSAGE_ITEM_CLASSES LOCALIZED_NAMES = { "da_DK": ("Uønsket e-mail",), "de_DE": ("Junk-E-Mail",), "en_US": ("Junk E-mail",), "es_ES": ("Correo no deseado",), "fr_CA": ("Courrier indésirables",), "nl_NL": ("Ongewenste e-mail",), "ru_RU": ("Нежелательная почта",), "sv_SE": ("Skräppost",), "zh_CN": ("垃圾邮件",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
- var supported_item_models
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 LocalFailures (**kwargs)
- 
Expand source codeclass LocalFailures(WellknownFolder): DISTINGUISHED_FOLDER_ID = "localfailures" supported_from = EXCHANGE_2013Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Location (**kwargs)
- 
Expand source codeclass Location(NonDeletableFolder): passA mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 MailboxAssociations (**kwargs)
- 
Expand source codeclass MailboxAssociations(NonDeletableFolder): passA mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Messages (**kwargs)
- 
Expand source codeclass Messages(WellknownFolder): CONTAINER_CLASS = "IPF.Note" supported_item_models = MESSAGE_ITEM_CLASSESBase class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 SubclassesClass variables- var CONTAINER_CLASS
- var supported_item_models
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 MsgFolderRoot (**kwargs)
- 
Expand source codeclass MsgFolderRoot(WellknownFolder): """Also known as the 'Top of Information Store' folder.""" DISTINGUISHED_FOLDER_ID = "msgfolderroot" LOCALIZED_NAMES = { None: ("Top of Information Store",), "da_DK": ("Informationslagerets øverste niveau",), "zh_CN": ("信息存储顶部",), }Also known as the 'Top of Information Store' folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 MyContacts (**kwargs)
- 
Expand source codeclass MyContacts(WellknownFolder): CONTAINER_CLASS = "IPF.Note" DISTINGUISHED_FOLDER_ID = "mycontacts" supported_from = EXCHANGE_2013Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 MyContactsExtended (**kwargs)
- 
Expand source codeclass MyContactsExtended(NonDeletableFolder): CONTAINER_CLASS = "IPF.Note" supported_item_models = CONTACT_ITEM_CLASSESA mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var supported_item_models
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 NonDeletableFolder (**kwargs)
- 
Expand source codeclass NonDeletableFolder(Folder): """A mixin for non-wellknown folders than that are not deletable.""" @property def is_deletable(self): return FalseA mixin for non-wellknown folders than that are not deletable. Ancestors- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Subclasses- AllTodoTasks
- ApplicationData
- Audits
- CalendarLogging
- CalendarSearchCache
- CommonViews
- ConversationSettings
- DefaultFoldersChangeHistory
- DeferredAction
- ExchangeSyncData
- ExternalContacts
- Files
- FreebusyData
- Friends
- GALContacts
- GraphAnalytics
- Location
- MailboxAssociations
- MyContactsExtended
- OrganizationalContacts
- ParkedMessages
- PassThroughSearchResults
- PdpProfileV2Secured
- PersonMetadata
- RSSFeeds
- Reminders
- Schedule
- ShadowItems
- Sharing
- Shortcuts
- Signal
- SmsAndChatsSync
- SpoolerQueue
- System
- System1
- Views
- WorkingSet
 Instance variables- prop is_deletable
- 
Expand source code@property def is_deletable(self): return False
 Inherited members- Folder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Notes (**kwargs)
- 
Expand source codeclass Notes(WellknownFolder): CONTAINER_CLASS = "IPF.StickyNote" DISTINGUISHED_FOLDER_ID = "notes" LOCALIZED_NAMES = { "da_DK": ("Noter",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 OneNotePagePreviews (**kwargs)
- 
Expand source codeclass OneNotePagePreviews(WellknownFolder): DISTINGUISHED_FOLDER_ID = "onenotepagepreviews" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 OrganizationalContacts (**kwargs)
- 
Expand source codeclass OrganizationalContacts(NonDeletableFolder): CONTAINER_CLASS = "IPF.Contact.OrganizationalContacts" supported_item_models = CONTACT_ITEM_CLASSES LOCALIZED_NAMES = { None: ("Organizational Contacts",), }A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var LOCALIZED_NAMES
- var supported_item_models
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Outbox (**kwargs)
- 
Expand source codeclass Outbox(Messages): DISTINGUISHED_FOLDER_ID = "outbox" LOCALIZED_NAMES = { "da_DK": ("Udbakke",), "de_DE": ("Postausgang",), "en_US": ("Outbox",), "es_ES": ("Bandeja de salida",), "fr_CA": ("Boîte d'envoi",), "nl_NL": ("Postvak UIT",), "ru_RU": ("Исходящие",), "sv_SE": ("Utkorgen",), "zh_CN": ("发件箱",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- Messages
- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
 Inherited members- Messages:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ParkedMessages (**kwargs)
- 
Expand source codeclass ParkedMessages(NonDeletableFolder): CONTAINER_CLASS = NoneA mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 PassThroughSearchResults (**kwargs)
- 
Expand source codeclass PassThroughSearchResults(NonDeletableFolder): CONTAINER_CLASS = "IPF.StoreItem.PassThroughSearchResults" LOCALIZED_NAMES = { None: ("Pass-Through Search Results",), }A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var LOCALIZED_NAMES
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 PdpProfileV2Secured (**kwargs)
- 
Expand source codeclass PdpProfileV2Secured(NonDeletableFolder): CONTAINER_CLASS = "IPF.StoreItem.PdpProfileSecured"A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 PeopleCentricConversationBuddies (**kwargs)
- 
Expand source codeclass PeopleCentricConversationBuddies(WellknownFolder): DISTINGUISHED_FOLDER_ID = "peoplecentricconversationbuddies" CONTAINER_CLASS = "IPF.Contact.PeopleCentricConversationBuddies" supported_from = EXCHANGE_O365 LOCALIZED_NAMES = { None: ("PeopleCentricConversation Buddies",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 PeopleConnect (**kwargs)
- 
Expand source codeclass PeopleConnect(WellknownFolder): DISTINGUISHED_FOLDER_ID = "peopleconnect" supported_from = EXCHANGE_2013Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 PersonMetadata (**kwargs)
- 
Expand source codeclass PersonMetadata(NonDeletableFolder): CONTAINER_CLASS = "IPF.Contact"A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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)
- 
Expand source codeclass PublicFoldersRoot(RootOfHierarchy): """The root of the public folder hierarchy. Not available on all mailboxes.""" DISTINGUISHED_FOLDER_ID = "publicfoldersroot" DEFAULT_FOLDER_TRAVERSAL_DEPTH = SHALLOW supported_from = EXCHANGE_2007_SP1 def __init__(self, **kwargs): super().__init__(**kwargs) if self._distinguished_id: self._distinguished_id.mailbox = None # See DistinguishedFolderId.clean() @property def _folders_map(self): # Top-level public folders may point to the root folder of the owning account and not the public folders root # of this account. This breaks the assumption of get_children(). Fix it by overwriting the parent folder. fix_parents = self._subfolders is None res = super()._folders_map if fix_parents: with self._subfolders_lock: for f in res.values(): if f.id != self.id: f.parent = self return res 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 = {} with suppress(ErrorAccessDenied): 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 # 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)The root of the public folder hierarchy. Not available on all mailboxes. Ancestors- RootOfHierarchy
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DEFAULT_FOLDER_TRAVERSAL_DEPTH
- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Methods- def get_children(self, folder)
- 
Expand source codedef 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 = {} with suppress(ErrorAccessDenied): 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 # 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
- get
- get_default_folder
- get_distinguished
- get_events
- get_streaming_events
- 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 QedcDefaultRetention (**kwargs)
- 
Expand source codeclass QedcDefaultRetention(WellknownFolder): DISTINGUISHED_FOLDER_ID = "qedcdefaultretention" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 QedcLongRetention (**kwargs)
- 
Expand source codeclass QedcLongRetention(WellknownFolder): DISTINGUISHED_FOLDER_ID = "qedclongretention" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 QedcMediumRetention (**kwargs)
- 
Expand source codeclass QedcMediumRetention(WellknownFolder): DISTINGUISHED_FOLDER_ID = "qedcmediumretention" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 QedcShortRetention (**kwargs)
- 
Expand source codeclass QedcShortRetention(WellknownFolder): DISTINGUISHED_FOLDER_ID = "qedcshortretention" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 QuarantinedEmail (**kwargs)
- 
Expand source codeclass QuarantinedEmail(WellknownFolder): DISTINGUISHED_FOLDER_ID = "quarantinedemail" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 QuarantinedEmailDefaultCategory (**kwargs)
- 
Expand source codeclass QuarantinedEmailDefaultCategory(WellknownFolder): DISTINGUISHED_FOLDER_ID = "quarantinedemaildefaultcategory" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 QuickContacts (**kwargs)
- 
Expand source codeclass QuickContacts(WellknownFolder): DISTINGUISHED_FOLDER_ID = "quickcontacts" CONTAINER_CLASS = "IPF.Contact.MOC.QuickContacts" supported_from = EXCHANGE_2013Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 RSSFeeds (**kwargs)
- 
Expand source codeclass RSSFeeds(NonDeletableFolder): CONTAINER_CLASS = "IPF.Note.OutlookHomepage" LOCALIZED_NAMES = { None: ("RSS Feeds",), }A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var LOCALIZED_NAMES
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 RecipientCache (**kwargs)
- 
Expand source codeclass RecipientCache(WellknownFolder): DISTINGUISHED_FOLDER_ID = "recipientcache" CONTAINER_CLASS = "IPF.Contact.RecipientCache" supported_from = EXCHANGE_2013Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 RecoverableItemsDeletions (**kwargs)
- 
Expand source codeclass RecoverableItemsDeletions(WellknownFolder): DISTINGUISHED_FOLDER_ID = "recoverableitemsdeletions" supported_from = EXCHANGE_2010_SP1Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 RecoverableItemsPurges (**kwargs)
- 
Expand source codeclass RecoverableItemsPurges(WellknownFolder): DISTINGUISHED_FOLDER_ID = "recoverableitemspurges" supported_from = EXCHANGE_2010_SP1Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 RecoverableItemsRoot (**kwargs)
- 
Expand source codeclass RecoverableItemsRoot(WellknownFolder): DISTINGUISHED_FOLDER_ID = "recoverableitemsroot" supported_from = EXCHANGE_2010_SP1Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 RecoverableItemsSubstrateHolds (**kwargs)
- 
Expand source codeclass RecoverableItemsSubstrateHolds(WellknownFolder): DISTINGUISHED_FOLDER_ID = "recoverableitemssubstrateholds" supported_from = EXCHANGE_O365 LOCALIZED_NAMES = { None: ("SubstrateHolds",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 RecoverableItemsVersions (**kwargs)
- 
Expand source codeclass RecoverableItemsVersions(WellknownFolder): DISTINGUISHED_FOLDER_ID = "recoverableitemsversions" supported_from = EXCHANGE_2010_SP1Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 RecoveryPoints (**kwargs)
- 
Expand source codeclass RecoveryPoints(Folder): CONTAINER_CLASS = "IPF.StoreItem.RecoveryPoints"Ancestors- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- Folder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 RelevantContacts (**kwargs)
- 
Expand source codeclass RelevantContacts(WellknownFolder): DISTINGUISHED_FOLDER_ID = "relevantcontacts" CONTAINER_CLASS = "IPF.Note" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Reminders (**kwargs)
- 
Expand source codeclass Reminders(NonDeletableFolder): CONTAINER_CLASS = "Outlook.Reminder" LOCALIZED_NAMES = { "da_DK": ("Påmindelser",), }A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var LOCALIZED_NAMES
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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)
- 
Expand source codeclass 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): with suppress(MISSING_FOLDER_ERRORS): return super().get_default_folder(folder_cls) # 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: with suppress(MISSING_FOLDER_ERRORS): return self._get_candidate(folder_cls=folder_cls, folder_coll=self.tois.children) # No candidates, or TOIS does not exist, or we don't have access to TOIS # 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 candidateThe root of the standard folder hierarchy. Ancestors- RootOfHierarchy
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var WELLKNOWN_FOLDERS
 Instance variables- prop 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
- get
- get_default_folder
- get_distinguished
- get_events
- get_streaming_events
- 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)
- 
Expand source codeclass 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 = [] # 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", "_subfolders_lock" # A special folder that acts as the top of a folder hierarchy. Finds and caches sub-folders 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() self._subfolders_lock = Lock() @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") with suppress(KeyError): del self._folders_map[folder.id] 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 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 FindItem instead log.debug("Testing default %s folder with FindItem", folder_cls) fld = folder_cls( _distinguished_id=DistinguishedFolderId( id=folder_cls.DISTINGUISHED_FOLDER_ID, mailbox=Mailbox(email_address=self.account.primary_smtp_address), ), root=self, ) 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") @classmethod def get_distinguished(cls, account): """Get the distinguished folder for this folder class. :param account: :return: """ return cls._get_distinguished( folder=cls( _distinguished_id=DistinguishedFolderId( id=cls.DISTINGUISHED_FOLDER_ID, mailbox=Mailbox(email_address=account.primary_smtp_address), ), account=account, ) ) @property def _folders_map(self): if self._subfolders is not None: return self._subfolders with self._subfolders_lock: # Map root, and all sub-folders 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 sub-folders of this root. folders_map = {self.id: self} distinguished_folders = [ cls( _distinguished_id=DistinguishedFolderId( id=cls.DISTINGUISHED_FOLDER_ID, mailbox=Mailbox(email_address=self.account.primary_smtp_address), ), root=self, ) 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, folder_class, locale): """Return the folder class that matches a localized folder name. Take into account the 'folder_class' of the folder, to not identify an 'IPF.Note' folder as a 'Calendar' class just because it's called e.g. 'Kalender' and the locale is 'da_DK'. Some folders, e.g. `System`, don't define a `folder_class`. For these folders, we match on localized folder name if the folder class does not have its 'CONTAINER_CLASS' set. :param folder_name: :param folder_class: :param locale: a string, e.g. 'da_DK' """ for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS + MISC_FOLDERS: if folder_cls.CONTAINER_CLASS != folder_class: continue if folder_name.lower() not in folder_cls.localized_names(locale): continue return folder_cls raise KeyError() def __getstate__(self): # The lock cannot be pickled state = {k: getattr(self, k) for k in self._slots_keys} del state["_subfolders_lock"] return state def __setstate__(self, state): # Restore the lock for k in self._slots_keys: setattr(self, k, state.get(k)) self._subfolders_lock = Lock() 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, ) )Base class for folders that implement the root of a folder hierarchy. AncestorsSubclassesClass variables- var FIELDS
- var WELLKNOWN_FOLDERS
 Static methods- def folder_cls_from_folder_name(folder_name, folder_class, locale)
- 
Return the folder class that matches a localized folder name. Take into account the 'folder_class' of the folder, to not identify an 'IPF.Note' folder as a 'Calendar' class just because it's called e.g. 'Kalender' and the locale is 'da_DK'. Some folders, e.g. System, don't define afolder_class. For these folders, we match on localized folder name if the folder class does not have its 'CONTAINER_CLASS' set.:param folder_name: :param folder_class: :param locale: a string, e.g. 'da_DK' 
- def from_xml(elem, account)
- def get_distinguished(account)
- 
Get the distinguished folder for this folder class. :param account: :return: 
 Instance variables- var effective_rights
 Methods- def add_folder(self, folder)
- 
Expand source codedef 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 codedef clear_cache(self): with self._subfolders_lock: self._subfolders = None
- def get_children(self, folder)
- 
Expand source codedef 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)
- 
Expand source codedef 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 FindItem instead log.debug("Testing default %s folder with FindItem", folder_cls) fld = folder_cls( _distinguished_id=DistinguishedFolderId( id=folder_cls.DISTINGUISHED_FOLDER_ID, mailbox=Mailbox(email_address=self.account.primary_smtp_address), ), root=self, ) 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")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' 
- def get_folder(self, folder)
- 
Expand source codedef 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 codedef remove_folder(self, folder): if not folder.id: raise ValueError("'folder' must have an ID") with suppress(KeyError): del self._folders_map[folder.id]
- def update_folder(self, folder)
- 
Expand source codedef 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
- get
- get_events
- get_streaming_events
- 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 Schedule (**kwargs)
- 
Expand source codeclass Schedule(NonDeletableFolder): passA mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 SearchFolders (**kwargs)
- 
Expand source codeclass SearchFolders(WellknownFolder): DISTINGUISHED_FOLDER_ID = "searchfolders"Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 SentItems (**kwargs)
- 
Expand source codeclass SentItems(Messages): DISTINGUISHED_FOLDER_ID = "sentitems" LOCALIZED_NAMES = { "da_DK": ("Sendt post",), "de_DE": ("Gesendete Elemente",), "en_US": ("Sent Items",), "es_ES": ("Elementos enviados",), "fr_CA": ("Éléments envoyés",), "nl_NL": ("Verzonden items",), "ru_RU": ("Отправленные",), "sv_SE": ("Skickat",), "zh_CN": ("已发送邮件",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- Messages
- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
 Inherited members- Messages:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ServerFailures (**kwargs)
- 
Expand source codeclass ServerFailures(WellknownFolder): DISTINGUISHED_FOLDER_ID = "serverfailures" supported_from = EXCHANGE_2013Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ShadowItems (**kwargs)
- 
Expand source codeclass ShadowItems(NonDeletableFolder): CONTAINER_CLASS = "IPF.StoreItem.ShadowItems"A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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
 
 
- 
Expand source codeclass SharePointNotifications(WellknownFolder): DISTINGUISHED_FOLDER_ID = "sharepointnotifications" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variablesInherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Sharing (**kwargs)
- 
Expand source codeclass Sharing(NonDeletableFolder): CONTAINER_CLASS = "IPF.Note"A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ShortNotes (**kwargs)
- 
Expand source codeclass ShortNotes(WellknownFolder): DISTINGUISHED_FOLDER_ID = "shortnotes" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Shortcuts (**kwargs)
- 
Expand source codeclass Shortcuts(NonDeletableFolder): passA mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Signal (**kwargs)
- 
Expand source codeclass Signal(NonDeletableFolder): CONTAINER_CLASS = "IPF.StoreItem.Signal"A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 SingleFolderQuerySet (account, folder)
- 
Expand source codeclass SingleFolderQuerySet(FolderQuerySet): """A helper class with simpler argument types.""" def __init__(self, account, folder): from .collections import FolderCollection folder_collection = FolderCollection(account=account, folders=[folder]) super().__init__(folder_collection=folder_collection) def _copy_cls(self): return self.__class__(account=self.folder_collection.account, folder=self.folder_collection.folders[0]) def resolve(self): return list(self.folder_collection.resolve())[0]A helper class with simpler argument types. AncestorsMethods- def resolve(self)
- 
Expand source codedef resolve(self): return list(self.folder_collection.resolve())[0]
 Inherited members
- class SkypeTeamsMessages (**kwargs)
- 
Expand source codeclass SkypeTeamsMessages(Folder): CONTAINER_CLASS = "IPF.SkypeTeams.Message" LOCALIZED_NAMES = { None: ("Team-chat",), }Ancestors- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var LOCALIZED_NAMES
 Inherited members- Folder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 SmsAndChatsSync (**kwargs)
- 
Expand source codeclass SmsAndChatsSync(NonDeletableFolder): CONTAINER_CLASS = "IPF.SmsAndChatsSync"A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 SpoolerQueue (**kwargs)
- 
Expand source codeclass SpoolerQueue(NonDeletableFolder): LOCALIZED_NAMES = { None: ("Spooler Queue",), }A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var LOCALIZED_NAMES
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 SwssItems (**kwargs)
- 
Expand source codeclass SwssItems(Folder): CONTAINER_CLASS = "IPF.StoreItem.SwssItems"Ancestors- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
 Inherited members- Folder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 SyncIssues (**kwargs)
- 
Expand source codeclass SyncIssues(WellknownFolder): CONTAINER_CLASS = "IPF.Note" DISTINGUISHED_FOLDER_ID = "syncissues" supported_from = EXCHANGE_2013Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 System (**kwargs)
- 
Expand source codeclass System(NonDeletableFolder): get_folder_allowed = FalseA mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var get_folder_allowed
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 System1 (**kwargs)
- 
Expand source codeclass System1(NonDeletableFolder): get_folder_allowed = FalseA mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var get_folder_allowed
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Tasks (**kwargs)
- 
Expand source codeclass Tasks(WellknownFolder): DISTINGUISHED_FOLDER_ID = "tasks" CONTAINER_CLASS = "IPF.Task" supported_item_models = TASK_ITEM_CLASSES LOCALIZED_NAMES = { "da_DK": ("Opgaver",), "de_DE": ("Aufgaben",), "en_US": ("Tasks",), "es_ES": ("Tareas",), "fr_CA": ("Tâches",), "nl_NL": ("Taken",), "ru_RU": ("Задачи",), "sv_SE": ("Uppgifter",), "zh_CN": ("任务",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
- var supported_item_models
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 TemporarySaves (**kwargs)
- 
Expand source codeclass TemporarySaves(WellknownFolder): DISTINGUISHED_FOLDER_ID = "temporarysaves" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 ToDoSearch (**kwargs)
- 
Expand source codeclass ToDoSearch(WellknownFolder): DISTINGUISHED_FOLDER_ID = "todosearch" CONTAINER_CLASS = "IPF.Task" supported_from = EXCHANGE_2013 LOCALIZED_NAMES = { None: ("To-Do Search",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 UserCuratedContacts (**kwargs)
- 
Expand source codeclass UserCuratedContacts(WellknownFolder): DISTINGUISHED_FOLDER_ID = "usercuratedcontacts" CONTAINER_CLASS = "IPF.Note" supported_from = EXCHANGE_O365Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var supported_from
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 Views (**kwargs)
- 
Expand source codeclass Views(NonDeletableFolder): passA mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 VoiceMail (**kwargs)
- 
Expand source codeclass VoiceMail(WellknownFolder): DISTINGUISHED_FOLDER_ID = "voicemail" CONTAINER_CLASS = "IPF.Note.Microsoft.Voicemail" LOCALIZED_NAMES = { None: ("Voice Mail",), }Base class to use until we have a more specific folder implementation for this folder. Ancestors- WellknownFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var CONTAINER_CLASS
- var DISTINGUISHED_FOLDER_ID
- var LOCALIZED_NAMES
 Inherited members- WellknownFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 WellknownFolder (**kwargs)
- 
Expand source codeclass WellknownFolder(Folder, metaclass=EWSMeta): """Base class to use until we have a more specific folder implementation for this folder.""" supported_item_models = ITEM_CLASSESBase class to use until we have a more specific folder implementation for this folder. Ancestors- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Subclasses- AdminAuditLogs
- AllCategorizedItems
- AllContacts
- AllItems
- AllPersonMetadata
- ArchiveDeletedItems
- ArchiveInbox
- ArchiveMsgFolderRoot
- ArchiveRecoverableItemsDeletions
- ArchiveRecoverableItemsPurges
- ArchiveRecoverableItemsRoot
- ArchiveRecoverableItemsVersions
- Calendar
- CompanyContacts
- Conflicts
- Contacts
- ConversationHistory
- DeletedItems
- Directory
- DlpPolicyEvaluation
- Drafts
- Favorites
- FromFavoriteSenders
- IMContactList
- Inbox
- Inference
- Journal
- JunkEmail
- LocalFailures
- Messages
- MsgFolderRoot
- MyContacts
- Notes
- OneNotePagePreviews
- PeopleCentricConversationBuddies
- PeopleConnect
- QedcDefaultRetention
- QedcLongRetention
- QedcMediumRetention
- QedcShortRetention
- QuarantinedEmail
- QuarantinedEmailDefaultCategory
- QuickContacts
- RecipientCache
- RecoverableItemsDeletions
- RecoverableItemsPurges
- RecoverableItemsRoot
- RecoverableItemsSubstrateHolds
- RecoverableItemsVersions
- RelevantContacts
- SearchFolders
- ServerFailures
- SharePointNotifications
- ShortNotes
- SyncIssues
- Tasks
- TemporarySaves
- ToDoSearch
- UserCuratedContacts
- VoiceMail
 Class variables- var supported_item_models
 Inherited members- Folder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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 WorkingSet (**kwargs)
- 
Expand source codeclass WorkingSet(NonDeletableFolder): LOCALIZED_NAMES = { None: ("Working Set",), }A mixin for non-wellknown folders than that are not deletable. Ancestors- NonDeletableFolder
- Folder
- BaseFolder
- RegisterMixIn
- IdChangeKeyMixIn
- EWSElement
- SearchableMixIn
- SupportedVersionClassMixIn
 Class variables- var LOCALIZED_NAMES
 Inherited members- NonDeletableFolder:- ID_ELEMENT_CLS
- account
- add_field
- all
- deregister
- exclude
- filter
- folder_cls_from_container_class
- get
- get_distinguished
- get_events
- get_streaming_events
- 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