diff --git a/docs/exchangelib/autodiscover/discovery.html b/docs/exchangelib/autodiscover/discovery.html index 91649b6d..9054f269 100644 --- a/docs/exchangelib/autodiscover/discovery.html +++ b/docs/exchangelib/autodiscover/discovery.html @@ -54,6 +54,13 @@
exchangelib.autodiscover.discovery
exchangelib.autodiscover.discovery
exchangelib.autodiscover.discovery
exchangelib.configuration
exchangelib.credentials
exchangelib.credentials
exchangelib.credentials
exchangelib.credentials
class OAuth2AuthorizationCodeCredentials
-(authorization_code=None, access_token=None, **kwargs)
+(authorization_code=None, access_token=None, client_id=None, client_secret=None, **kwargs)
Login info for OAuth 2.0 authentication using the authorization code grant type. This can be used in one of several ways: * Given an authorization code, client ID, and client secret, fetch a token ourselves and refresh it as needed if supplied with a refresh token. -* Given an existing access token, refresh token, client ID, and client secret, use the access token until it -expires and then refresh it as needed. +* Given an existing access token, client ID, and client secret, use the access token until it expires and then +refresh it as needed. * Given only an existing access token, use it until it expires. This can be used to let the calling application refresh tokens itself by subclassing and implementing refresh().
Unlike the base (client credentials) grant, authorization code credentials don't require a Microsoft tenant ID @@ -473,8 +473,8 @@
class OAuth2Credentials
-(client_id, client_secret, tenant_id=None, identity=None)
+(client_id, client_secret, tenant_id=None, identity=None, access_token=None)
Login info for OAuth 2.0 client credentials authentication, as well as a base for other OAuth 2.0 grant types.
@@ -544,7 +544,8 @@:param client_id: ID of an authorized OAuth application, required for automatic token fetching and refreshing :param client_secret: Secret associated with the OAuth application :param tenant_id: Microsoft tenant ID of the account to access -:param identity: An Identity object representing the account that these credentials are connected to.
exchangelib.errors
exchangelib.errors
exchangelib.errors
+class ErrorRecoverableItemsAccessDenied
+(value)
+
Global error type within this module.
class ErrorRecoverableItemsAccessDenied(ResponseMessageError):
+ pass
+
class ErrorRecurrenceEndDateTooBig
(value)
@@ -10914,6 +10935,7 @@ Subclasses
ErrorQuotaExceeded
ErrorReadEventsFailed
ErrorReadReceiptNotPending
+ErrorRecoverableItemsAccessDenied
ErrorRecurrenceEndDateTooBig
ErrorRecurrenceHasNoOccurrence
ErrorRemoveDelegatesFailed
@@ -11045,26 +11067,6 @@ Ancestors
builtins.BaseException
-
-class TimezoneDefinitionInvalidForYear
-(value)
-
-
-Global error type within this module.
-
-
-Expand source code
-
-class TimezoneDefinitionInvalidForYear(EWSError):
- pass
-
-Ancestors
-
-- EWSError
-- builtins.Exception
-- builtins.BaseException
-
-
class TransportError
(value)
@@ -12121,6 +12123,9 @@ ErrorReadReceiptNotPending
+ErrorRecoverableItemsAccessDenied
+
+
ErrorRecurrenceEndDateTooBig
@@ -12361,9 +12366,6 @@ SessionPoolMinSizeReached
-TimezoneDefinitionInvalidForYear
-
-
TransportError
diff --git a/docs/exchangelib/ewsdatetime.html b/docs/exchangelib/ewsdatetime.html
index f10a28fe..36fce92e 100644
--- a/docs/exchangelib/ewsdatetime.html
+++ b/docs/exchangelib/ewsdatetime.html
@@ -953,7 +953,7 @@ Methods
Ancestors
-- backports.zoneinfo.ZoneInfo
+- zoneinfo.ZoneInfo
- datetime.tzinfo
Class variables
diff --git a/docs/exchangelib/fields.html b/docs/exchangelib/fields.html
index e67f562f..a52f863d 100644
--- a/docs/exchangelib/fields.html
+++ b/docs/exchangelib/fields.html
@@ -6611,7 +6611,10 @@ Class variables
var value_cls
-
-
Difference between two datetime values.
+Difference between two datetime values.
+timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)
+All arguments are optional and default to 0.
+Arguments may be integers or floats, and may be positive or negative.
Methods
diff --git a/docs/exchangelib/folders/base.html b/docs/exchangelib/folders/base.html
index df412a1b..7d1b336a 100644
--- a/docs/exchangelib/folders/base.html
+++ b/docs/exchangelib/folders/base.html
@@ -38,6 +38,7 @@ Module exchangelib.folders.base
ErrorDeleteDistinguishedFolder,
ErrorFolderNotFound,
ErrorItemNotFound,
+ ErrorRecoverableItemsAccessDenied,
InvalidTypeError,
)
from ..fields import (
@@ -75,6 +76,13 @@ Module exchangelib.folders.base
log = logging.getLogger(__name__)
+DELETE_FOLDER_ERRORS = (
+ ErrorAccessDenied,
+ ErrorCannotDeleteObject,
+ ErrorCannotEmptyFolder,
+ ErrorItemNotFound,
+)
+
class BaseFolder(RegisterMixIn, SearchableMixIn, metaclass=EWSMeta):
"""Base class for all classes that implement a folder."""
@@ -257,27 +265,41 @@ Module exchangelib.folders.base
:return:
"""
from .known_folders import (
+ ApplicationData,
Calendar,
Contacts,
ConversationSettings,
+ CrawlerData,
+ DlpPolicyEvaluation,
+ FreeBusyCache,
GALContacts,
Messages,
RecipientCache,
+ RecoveryPoints,
Reminders,
RSSFeeds,
+ Signal,
+ SwssItems,
Tasks,
)
for folder_cls in (
- Messages,
- Tasks,
+ ApplicationData,
Calendar,
- ConversationSettings,
Contacts,
+ ConversationSettings,
+ CrawlerData,
+ DlpPolicyEvaluation,
+ FreeBusyCache,
GALContacts,
- Reminders,
- RecipientCache,
+ Messages,
RSSFeeds,
+ RecipientCache,
+ RecoveryPoints,
+ Reminders,
+ Signal,
+ SwssItems,
+ Tasks,
):
if folder_cls.CONTAINER_CLASS == container_class:
return folder_cls
@@ -430,12 +452,21 @@ Module exchangelib.folders.base
def wipe(self, page_size=None, chunk_size=None, _seen=None, _level=0):
# Recursively deletes all items in this folder, and all subfolders 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_distinguished_subfolders = any(f.is_distinguished for f in self.children)
try:
@@ -443,12 +474,15 @@ Module exchangelib.folders.base
self.empty(delete_sub_folders=False)
else:
self.empty(delete_sub_folders=True)
- except (ErrorAccessDenied, ErrorCannotEmptyFolder, ErrorItemNotFound):
+ except ErrorRecoverableItemsAccessDenied:
+ log.warning("Access denied to %s. Skipping", self)
+ return
+ except DELETE_FOLDER_ERRORS:
try:
if has_distinguished_subfolders:
raise # We already tried this
self.empty(delete_sub_folders=False)
- except (ErrorAccessDenied, ErrorCannotEmptyFolder, ErrorItemNotFound):
+ except DELETE_FOLDER_ERRORS:
log.warning("Not allowed to empty %s. Trying to delete items instead", self)
kwargs = {}
if page_size is not None:
@@ -457,7 +491,7 @@ Module exchangelib.folders.base
kwargs["chunk_size"] = chunk_size
try:
self.all().delete(**kwargs)
- except (ErrorAccessDenied, ErrorCannotDeleteObject, ErrorItemNotFound):
+ except DELETE_FOLDER_ERRORS:
log.warning("Not allowed to delete items in %s", self)
_level += 1
for f in self.children:
@@ -1127,27 +1161,41 @@ Classes
:return:
"""
from .known_folders import (
+ ApplicationData,
Calendar,
Contacts,
ConversationSettings,
+ CrawlerData,
+ DlpPolicyEvaluation,
+ FreeBusyCache,
GALContacts,
Messages,
RecipientCache,
+ RecoveryPoints,
Reminders,
RSSFeeds,
+ Signal,
+ SwssItems,
Tasks,
)
for folder_cls in (
- Messages,
- Tasks,
+ ApplicationData,
Calendar,
- ConversationSettings,
Contacts,
+ ConversationSettings,
+ CrawlerData,
+ DlpPolicyEvaluation,
+ FreeBusyCache,
GALContacts,
- Reminders,
- RecipientCache,
+ Messages,
RSSFeeds,
+ RecipientCache,
+ RecoveryPoints,
+ Reminders,
+ Signal,
+ SwssItems,
+ Tasks,
):
if folder_cls.CONTAINER_CLASS == container_class:
return folder_cls
@@ -1300,12 +1348,21 @@ Classes
def wipe(self, page_size=None, chunk_size=None, _seen=None, _level=0):
# Recursively deletes all items in this folder, and all subfolders 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_distinguished_subfolders = any(f.is_distinguished for f in self.children)
try:
@@ -1313,12 +1370,15 @@ Classes
self.empty(delete_sub_folders=False)
else:
self.empty(delete_sub_folders=True)
- except (ErrorAccessDenied, ErrorCannotEmptyFolder, ErrorItemNotFound):
+ except ErrorRecoverableItemsAccessDenied:
+ log.warning("Access denied to %s. Skipping", self)
+ return
+ except DELETE_FOLDER_ERRORS:
try:
if has_distinguished_subfolders:
raise # We already tried this
self.empty(delete_sub_folders=False)
- except (ErrorAccessDenied, ErrorCannotEmptyFolder, ErrorItemNotFound):
+ except DELETE_FOLDER_ERRORS:
log.warning("Not allowed to empty %s. Trying to delete items instead", self)
kwargs = {}
if page_size is not None:
@@ -1327,7 +1387,7 @@ Classes
kwargs["chunk_size"] = chunk_size
try:
self.all().delete(**kwargs)
- except (ErrorAccessDenied, ErrorCannotDeleteObject, ErrorItemNotFound):
+ except DELETE_FOLDER_ERRORS:
log.warning("Not allowed to delete items in %s", self)
_level += 1
for f in self.children:
@@ -1777,27 +1837,41 @@ Static methods
:return:
"""
from .known_folders import (
+ ApplicationData,
Calendar,
Contacts,
ConversationSettings,
+ CrawlerData,
+ DlpPolicyEvaluation,
+ FreeBusyCache,
GALContacts,
Messages,
RecipientCache,
+ RecoveryPoints,
Reminders,
RSSFeeds,
+ Signal,
+ SwssItems,
Tasks,
)
for folder_cls in (
- Messages,
- Tasks,
+ ApplicationData,
Calendar,
- ConversationSettings,
Contacts,
+ ConversationSettings,
+ CrawlerData,
+ DlpPolicyEvaluation,
+ FreeBusyCache,
GALContacts,
- Reminders,
- RecipientCache,
+ Messages,
RSSFeeds,
+ RecipientCache,
+ RecoveryPoints,
+ Reminders,
+ Signal,
+ SwssItems,
+ Tasks,
):
if folder_cls.CONTAINER_CLASS == container_class:
return folder_cls
@@ -2788,12 +2862,21 @@ Methods
def wipe(self, page_size=None, chunk_size=None, _seen=None, _level=0):
# Recursively deletes all items in this folder, and all subfolders 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_distinguished_subfolders = any(f.is_distinguished for f in self.children)
try:
@@ -2801,12 +2884,15 @@ Methods
self.empty(delete_sub_folders=False)
else:
self.empty(delete_sub_folders=True)
- except (ErrorAccessDenied, ErrorCannotEmptyFolder, ErrorItemNotFound):
+ except ErrorRecoverableItemsAccessDenied:
+ log.warning("Access denied to %s. Skipping", self)
+ return
+ except DELETE_FOLDER_ERRORS:
try:
if has_distinguished_subfolders:
raise # We already tried this
self.empty(delete_sub_folders=False)
- except (ErrorAccessDenied, ErrorCannotEmptyFolder, ErrorItemNotFound):
+ except DELETE_FOLDER_ERRORS:
log.warning("Not allowed to empty %s. Trying to delete items instead", self)
kwargs = {}
if page_size is not None:
@@ -2815,7 +2901,7 @@ Methods
kwargs["chunk_size"] = chunk_size
try:
self.all().delete(**kwargs)
- except (ErrorAccessDenied, ErrorCannotDeleteObject, ErrorItemNotFound):
+ except DELETE_FOLDER_ERRORS:
log.warning("Not allowed to delete items in %s", self)
_level += 1
for f in self.children:
@@ -3005,17 +3091,22 @@ Ancestors
Subclasses
- AllItems
+- ApplicationData
- Audits
+- Birthdays
- Calendar
- CalendarLogging
- CommonViews
- Contacts
- ConversationSettings
+- CrawlerData
- DefaultFoldersChangeHistory
- DeferredAction
- DeletedItems
+- DlpPolicyEvaluation
- ExchangeSyncData
- Files
+- FreeBusyCache
- FreebusyData
- GraphAnalytics
- Location
@@ -3025,13 +3116,16 @@ Subclasses
- PassThroughSearchResults
- PdpProfileV2Secured
- RSSFeeds
+- RecoveryPoints
- Reminders
- Schedule
- Sharing
- Shortcuts
- Signal
+- SkypeTeamsMessages
- SmsAndChatsSync
- SpoolerQueue
+- SwssItems
- System
- System1
- Tasks
diff --git a/docs/exchangelib/folders/index.html b/docs/exchangelib/folders/index.html
index d29c8485..af9ef9c2 100644
--- a/docs/exchangelib/folders/index.html
+++ b/docs/exchangelib/folders/index.html
@@ -34,6 +34,7 @@ Module exchangelib.folders
AdminAuditLogs,
AllContacts,
AllItems,
+ ApplicationData,
ArchiveDeletedItems,
ArchiveInbox,
ArchiveMsgFolderRoot,
@@ -42,6 +43,7 @@ Module exchangelib.folders
ArchiveRecoverableItemsRoot,
ArchiveRecoverableItemsVersions,
Audits,
+ Birthdays,
Calendar,
CalendarLogging,
CommonViews,
@@ -50,14 +52,17 @@ Module exchangelib.folders
Contacts,
ConversationHistory,
ConversationSettings,
+ CrawlerData,
DefaultFoldersChangeHistory,
DeferredAction,
DeletedItems,
Directory,
+ DlpPolicyEvaluation,
Drafts,
ExchangeSyncData,
Favorites,
Files,
+ FreeBusyCache,
FreebusyData,
Friends,
GALContacts,
@@ -88,6 +93,7 @@ Module exchangelib.folders
RecoverableItemsPurges,
RecoverableItemsRoot,
RecoverableItemsVersions,
+ RecoveryPoints,
Reminders,
RSSFeeds,
Schedule,
@@ -97,8 +103,10 @@ Module exchangelib.folders
Sharing,
Shortcuts,
Signal,
+ SkypeTeamsMessages,
SmsAndChatsSync,
SpoolerQueue,
+ SwssItems,
SyncIssues,
System,
Tasks,
@@ -113,14 +121,10 @@ Module exchangelib.folders
from .roots import ArchiveRoot, PublicFoldersRoot, Root, RootOfHierarchy
__all__ = [
- "FolderId",
- "DistinguishedFolderId",
- "FolderCollection",
- "BaseFolder",
- "Folder",
"AdminAuditLogs",
"AllContacts",
"AllItems",
+ "ApplicationData",
"ArchiveDeletedItems",
"ArchiveInbox",
"ArchiveMsgFolderRoot",
@@ -128,22 +132,36 @@ Module exchangelib.folders
"ArchiveRecoverableItemsPurges",
"ArchiveRecoverableItemsRoot",
"ArchiveRecoverableItemsVersions",
+ "ArchiveRoot",
"Audits",
+ "BaseFolder",
+ "Birthdays",
"Calendar",
"CalendarLogging",
"CommonViews",
+ "Companies",
"Conflicts",
"Contacts",
"ConversationHistory",
"ConversationSettings",
+ "CrawlerData",
+ "DEEP",
"DefaultFoldersChangeHistory",
"DeferredAction",
"DeletedItems",
"Directory",
+ "DistinguishedFolderId",
+ "DlpPolicyEvaluation",
"Drafts",
"ExchangeSyncData",
+ "FOLDER_TRAVERSAL_CHOICES",
"Favorites",
"Files",
+ "Folder",
+ "FolderCollection",
+ "FolderId",
+ "FolderQuerySet",
+ "FreeBusyCache",
"FreebusyData",
"Friends",
"GALContacts",
@@ -159,13 +177,17 @@ Module exchangelib.folders
"MsgFolderRoot",
"MyContacts",
"MyContactsExtended",
+ "NON_DELETABLE_FOLDERS",
"NonDeletableFolderMixin",
"Notes",
+ "OrganizationalContacts",
"Outbox",
"ParkedMessages",
"PassThroughSearchResults",
"PdpProfileV2Secured",
+ "PeopleCentricConversationBuddies",
"PeopleConnect",
+ "PublicFoldersRoot",
"QuickContacts",
"RSSFeeds",
"RecipientCache",
@@ -173,7 +195,12 @@ Module exchangelib.folders
"RecoverableItemsPurges",
"RecoverableItemsRoot",
"RecoverableItemsVersions",
+ "RecoveryPoints",
"Reminders",
+ "Root",
+ "RootOfHierarchy",
+ "SHALLOW",
+ "SOFT_DELETED",
"Schedule",
"SearchFolders",
"SentItems",
@@ -181,8 +208,11 @@ Module exchangelib.folders
"Sharing",
"Shortcuts",
"Signal",
+ "SingleFolderQuerySet",
+ "SkypeTeamsMessages",
"SmsAndChatsSync",
"SpoolerQueue",
+ "SwssItems",
"SyncIssues",
"System",
"Tasks",
@@ -192,20 +222,6 @@ Module exchangelib.folders
"VoiceMail",
"WellknownFolder",
"WorkingSet",
- "Companies",
- "OrganizationalContacts",
- "PeopleCentricConversationBuddies",
- "NON_DELETABLE_FOLDERS",
- "FolderQuerySet",
- "SingleFolderQuerySet",
- "FOLDER_TRAVERSAL_CHOICES",
- "SHALLOW",
- "DEEP",
- "SOFT_DELETED",
- "Root",
- "ArchiveRoot",
- "PublicFoldersRoot",
- "RootOfHierarchy",
]
@@ -475,6 +491,75 @@ Inherited members
+
+class ApplicationData
+(**kwargs)
+
+
+A mixin for non-wellknown folders than that are not deletable.
+
+
+Expand source code
+
+class ApplicationData(NonDeletableFolderMixin, Folder):
+ CONTAINER_CLASS = "IPM.ApplicationData"
+
+Ancestors
+
+- NonDeletableFolderMixin
+- Folder
+- BaseFolder
+- RegisterMixIn
+- IdChangeKeyMixIn
+- EWSElement
+- SearchableMixIn
+
+Class variables
+
+var CONTAINER_CLASS
+-
+
+
+
+Inherited members
+
+Folder
:
+
+ID_ELEMENT_CLS
+account
+add_field
+all
+deregister
+exclude
+filter
+folder_cls_from_container_class
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class ArchiveDeletedItems
(**kwargs)
@@ -1342,27 +1427,41 @@ Inherited members
:return:
"""
from .known_folders import (
+ ApplicationData,
Calendar,
Contacts,
ConversationSettings,
+ CrawlerData,
+ DlpPolicyEvaluation,
+ FreeBusyCache,
GALContacts,
Messages,
RecipientCache,
+ RecoveryPoints,
Reminders,
RSSFeeds,
+ Signal,
+ SwssItems,
Tasks,
)
for folder_cls in (
- Messages,
- Tasks,
+ ApplicationData,
Calendar,
- ConversationSettings,
Contacts,
+ ConversationSettings,
+ CrawlerData,
+ DlpPolicyEvaluation,
+ FreeBusyCache,
GALContacts,
- Reminders,
- RecipientCache,
+ Messages,
RSSFeeds,
+ RecipientCache,
+ RecoveryPoints,
+ Reminders,
+ Signal,
+ SwssItems,
+ Tasks,
):
if folder_cls.CONTAINER_CLASS == container_class:
return folder_cls
@@ -1515,12 +1614,21 @@ Inherited members
def wipe(self, page_size=None, chunk_size=None, _seen=None, _level=0):
# Recursively deletes all items in this folder, and all subfolders 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_distinguished_subfolders = any(f.is_distinguished for f in self.children)
try:
@@ -1528,12 +1636,15 @@ Inherited members
self.empty(delete_sub_folders=False)
else:
self.empty(delete_sub_folders=True)
- except (ErrorAccessDenied, ErrorCannotEmptyFolder, ErrorItemNotFound):
+ except ErrorRecoverableItemsAccessDenied:
+ log.warning("Access denied to %s. Skipping", self)
+ return
+ except DELETE_FOLDER_ERRORS:
try:
if has_distinguished_subfolders:
raise # We already tried this
self.empty(delete_sub_folders=False)
- except (ErrorAccessDenied, ErrorCannotEmptyFolder, ErrorItemNotFound):
+ except DELETE_FOLDER_ERRORS:
log.warning("Not allowed to empty %s. Trying to delete items instead", self)
kwargs = {}
if page_size is not None:
@@ -1542,7 +1653,7 @@ Inherited members
kwargs["chunk_size"] = chunk_size
try:
self.all().delete(**kwargs)
- except (ErrorAccessDenied, ErrorCannotDeleteObject, ErrorItemNotFound):
+ except DELETE_FOLDER_ERRORS:
log.warning("Not allowed to delete items in %s", self)
_level += 1
for f in self.children:
@@ -1992,27 +2103,41 @@ Static methods
:return:
"""
from .known_folders import (
+ ApplicationData,
Calendar,
Contacts,
ConversationSettings,
+ CrawlerData,
+ DlpPolicyEvaluation,
+ FreeBusyCache,
GALContacts,
Messages,
RecipientCache,
+ RecoveryPoints,
Reminders,
RSSFeeds,
+ Signal,
+ SwssItems,
Tasks,
)
for folder_cls in (
- Messages,
- Tasks,
+ ApplicationData,
Calendar,
- ConversationSettings,
Contacts,
+ ConversationSettings,
+ CrawlerData,
+ DlpPolicyEvaluation,
+ FreeBusyCache,
GALContacts,
- Reminders,
- RecipientCache,
+ Messages,
RSSFeeds,
+ RecipientCache,
+ RecoveryPoints,
+ Reminders,
+ Signal,
+ SwssItems,
+ Tasks,
):
if folder_cls.CONTAINER_CLASS == container_class:
return folder_cls
@@ -3003,12 +3128,21 @@ Methods
def wipe(self, page_size=None, chunk_size=None, _seen=None, _level=0):
# Recursively deletes all items in this folder, and all subfolders 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_distinguished_subfolders = any(f.is_distinguished for f in self.children)
try:
@@ -3016,12 +3150,15 @@ Methods
self.empty(delete_sub_folders=False)
else:
self.empty(delete_sub_folders=True)
- except (ErrorAccessDenied, ErrorCannotEmptyFolder, ErrorItemNotFound):
+ except ErrorRecoverableItemsAccessDenied:
+ log.warning("Access denied to %s. Skipping", self)
+ return
+ except DELETE_FOLDER_ERRORS:
try:
if has_distinguished_subfolders:
raise # We already tried this
self.empty(delete_sub_folders=False)
- except (ErrorAccessDenied, ErrorCannotEmptyFolder, ErrorItemNotFound):
+ except DELETE_FOLDER_ERRORS:
log.warning("Not allowed to empty %s. Trying to delete items instead", self)
kwargs = {}
if page_size is not None:
@@ -3030,7 +3167,7 @@ Methods
kwargs["chunk_size"] = chunk_size
try:
self.all().delete(**kwargs)
- except (ErrorAccessDenied, ErrorCannotDeleteObject, ErrorItemNotFound):
+ except DELETE_FOLDER_ERRORS:
log.warning("Not allowed to delete items in %s", self)
_level += 1
for f in self.children:
@@ -3069,6 +3206,82 @@ Inherited members
+
+class Birthdays
+(**kwargs)
+
+
+
+
+
+Expand source code
+
+class Birthdays(Folder):
+ CONTAINER_CLASS = "IPF.Appointment.Birthday"
+ LOCALIZED_NAMES = {
+ None: ("Birthdays",),
+ "da_DK": ("Fødselsdage",),
+ }
+
+Ancestors
+
+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
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class Calendar
(**kwargs)
@@ -3346,6 +3559,7 @@ Inherited members
CONTAINTER_CLASS = "IPF.Contact.Company"
LOCALIZED_NAMES = {
None: ("Companies",),
+ "da_DK": ("Firmaer",),
}
Ancestors
@@ -3742,6 +3956,74 @@ Inherited members
+
+class CrawlerData
+(**kwargs)
+
+
+
+
+
+Expand source code
+
+class CrawlerData(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.CrawlerData"
+
+Ancestors
+
+Class variables
+
+var CONTAINER_CLASS
+-
+
+
+
+Inherited members
+
+Folder
:
+
+ID_ELEMENT_CLS
+account
+add_field
+all
+deregister
+exclude
+filter
+folder_cls_from_container_class
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class DefaultFoldersChangeHistory
(**kwargs)
@@ -4140,6 +4422,74 @@ Inherited members
+
+class DlpPolicyEvaluation
+(**kwargs)
+
+
+
+
+
+Expand source code
+
+class DlpPolicyEvaluation(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.DlpPolicyEvaluation"
+
+Ancestors
+
+Class variables
+
+var CONTAINER_CLASS
+-
+
+
+
+Inherited members
+
+Folder
:
+
+ID_ELEMENT_CLS
+account
+add_field
+all
+deregister
+exclude
+filter
+folder_cls_from_container_class
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class Drafts
(**kwargs)
@@ -4603,17 +4953,22 @@ Ancestors
Subclasses
- AllItems
+- ApplicationData
- Audits
+- Birthdays
- Calendar
- CalendarLogging
- CommonViews
- Contacts
- ConversationSettings
+- CrawlerData
- DefaultFoldersChangeHistory
- DeferredAction
- DeletedItems
+- DlpPolicyEvaluation
- ExchangeSyncData
- Files
+- FreeBusyCache
- FreebusyData
- GraphAnalytics
- Location
@@ -4623,13 +4978,16 @@ Subclasses
- PassThroughSearchResults
- PdpProfileV2Secured
- RSSFeeds
+- RecoveryPoints
- Reminders
- Schedule
- Sharing
- Shortcuts
- Signal
+- SkypeTeamsMessages
- SmsAndChatsSync
- SpoolerQueue
+- SwssItems
- System
- System1
- Tasks
@@ -6061,6 +6419,7 @@ Ancestors
Subclasses
Class variables
@@ -6354,6 +6713,74 @@ Methods
+
+class FreeBusyCache
+(**kwargs)
+
+-
+
+
+
+Expand source code
+
+class FreeBusyCache(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.FreeBusyCache"
+
+Ancestors
+
+Class variables
+
+var CONTAINER_CLASS
+-
+
+
+
+Inherited members
+
+Folder
:
+
+ID_ELEMENT_CLS
+account
+add_field
+all
+deregister
+exclude
+filter
+folder_cls_from_container_class
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class FreebusyData
(**kwargs)
@@ -7297,6 +7724,8 @@ Inherited members
DISTINGUISHED_FOLDER_ID = "msgfolderroot"
LOCALIZED_NAMES = {
+ None: ("Top of Information Store",),
+ "da_DK": ("Informationslagerets øverste niveau",),
"zh_CN": ("信息存储顶部",),
}
@@ -7536,6 +7965,7 @@ Subclasses
- AllContacts
- AllItems
+- ApplicationData
- Audits
- CalendarLogging
- CommonViews
@@ -7768,7 +8198,7 @@ Inherited members
"de_DE": ("Postausgang",),
"en_US": ("Outbox",),
"es_ES": ("Bandeja de salida",),
- "fr_CA": (u"Boîte d'envoi",),
+ "fr_CA": ("Boîte d'envoi",),
"nl_NL": ("Postvak UIT",),
"ru_RU": ("Исходящие",),
"sv_SE": ("Utkorgen",),
@@ -8928,6 +9358,74 @@ Inherited members
+
+class RecoveryPoints
+(**kwargs)
+
+-
+
+
+
+Expand source code
+
+class RecoveryPoints(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.RecoveryPoints"
+
+Ancestors
+
+Class variables
+
+var CONTAINER_CLASS
+-
+
+
+
+Inherited members
+
+Folder
:
+
+ID_ELEMENT_CLS
+account
+add_field
+all
+deregister
+exclude
+filter
+folder_cls_from_container_class
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class Reminders
(**kwargs)
@@ -9351,7 +9849,7 @@ Inherited members
:param folder_name:
:param locale: a string, e.g. 'da_DK'
"""
- for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS:
+ for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS + MISC_FOLDERS:
if folder_name.lower() in folder_cls.localized_names(locale):
return folder_cls
raise KeyError()
@@ -9417,7 +9915,7 @@ Static methods
:param folder_name:
:param locale: a string, e.g. 'da_DK'
"""
- for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS:
+ for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS + MISC_FOLDERS:
if folder_name.lower() in folder_cls.localized_names(locale):
return folder_cls
raise KeyError()
@@ -10226,6 +10724,81 @@ Inherited members
+
+class SkypeTeamsMessages
+(**kwargs)
+
+
+
+
+
+Expand source code
+
+class SkypeTeamsMessages(Folder):
+ CONTAINER_CLASS = "IPF.SkypeTeams.Message"
+ LOCALIZED_NAMES = {
+ None: ("Team-chat",),
+ }
+
+Ancestors
+
+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
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class SmsAndChatsSync
(**kwargs)
@@ -10373,6 +10946,74 @@ Inherited members
+
+class SwssItems
+(**kwargs)
+
+
+
+
+
+Expand source code
+
+class SwssItems(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.SwssItems"
+
+Ancestors
+
+Class variables
+
+var CONTAINER_CLASS
+-
+
+
+
+Inherited members
+
+Folder
:
+
+ID_ELEMENT_CLS
+account
+add_field
+all
+deregister
+exclude
+filter
+folder_cls_from_container_class
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class SyncIssues
(**kwargs)
@@ -11153,6 +11794,12 @@ ApplicationData
+
+
+
ArchiveDeletedItems
DISTINGUISHED_FOLDER_ID
@@ -11290,6 +11937,13 @@ Birthdays
+
+
+
Calendar
CONTAINER_CLASS
@@ -11351,6 +12005,12 @@ CrawlerData
+
+
+
DefaultFoldersChangeHistory
CONTAINER_CLASS
@@ -11389,6 +12049,12 @@ DlpPolicyEvaluation
+
+
+
Drafts
DISTINGUISHED_FOLDER_ID
@@ -11471,6 +12137,12 @@ FreeBusyCache
+
+
+
FreebusyData
LOCALIZED_NAMES
@@ -11702,6 +12374,12 @@ RecoveryPoints
+
+
+
Reminders
CONTAINER_CLASS
@@ -11787,6 +12465,13 @@ SkypeTeamsMessages
+
+
+
SmsAndChatsSync
CONTAINER_CLASS
@@ -11800,6 +12485,12 @@ SwssItems
+
+
+
SyncIssues
CONTAINER_CLASS
diff --git a/docs/exchangelib/folders/known_folders.html b/docs/exchangelib/folders/known_folders.html
index f55eba1d..0605b174 100644
--- a/docs/exchangelib/folders/known_folders.html
+++ b/docs/exchangelib/folders/known_folders.html
@@ -90,6 +90,41 @@ Module exchangelib.folders.known_folders
supported_item_models = (Message, MeetingRequest, MeetingResponse, MeetingCancellation)
+class CrawlerData(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.CrawlerData"
+
+
+class DlpPolicyEvaluation(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.DlpPolicyEvaluation"
+
+
+class FreeBusyCache(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.FreeBusyCache"
+
+
+class RecoveryPoints(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.RecoveryPoints"
+
+
+class SwssItems(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.SwssItems"
+
+
+class SkypeTeamsMessages(Folder):
+ CONTAINER_CLASS = "IPF.SkypeTeams.Message"
+ LOCALIZED_NAMES = {
+ None: ("Team-chat",),
+ }
+
+
+class Birthdays(Folder):
+ CONTAINER_CLASS = "IPF.Appointment.Birthday"
+ LOCALIZED_NAMES = {
+ None: ("Birthdays",),
+ "da_DK": ("Fødselsdage",),
+ }
+
+
class Drafts(Messages):
DISTINGUISHED_FOLDER_ID = "drafts"
@@ -130,7 +165,7 @@ Module exchangelib.folders.known_folders
"de_DE": ("Postausgang",),
"en_US": ("Outbox",),
"es_ES": ("Bandeja de salida",),
- "fr_CA": (u"Boîte d'envoi",),
+ "fr_CA": ("Boîte d'envoi",),
"nl_NL": ("Postvak UIT",),
"ru_RU": ("Исходящие",),
"sv_SE": ("Utkorgen",),
@@ -295,6 +330,8 @@ Module exchangelib.folders.known_folders
DISTINGUISHED_FOLDER_ID = "msgfolderroot"
LOCALIZED_NAMES = {
+ None: ("Top of Information Store",),
+ "da_DK": ("Informationslagerets øverste niveau",),
"zh_CN": ("信息存储顶部",),
}
@@ -409,6 +446,10 @@ Module exchangelib.folders.known_folders
}
+class ApplicationData(NonDeletableFolderMixin, Folder):
+ CONTAINER_CLASS = "IPM.ApplicationData"
+
+
class Audits(NonDeletableFolderMixin, Folder):
LOCALIZED_NAMES = {
None: ("Audits",),
@@ -434,6 +475,7 @@ Module exchangelib.folders.known_folders
CONTAINTER_CLASS = "IPF.Contact.Company"
LOCALIZED_NAMES = {
None: ("Companies",),
+ "da_DK": ("Firmaer",),
}
@@ -647,6 +689,7 @@ Module exchangelib.folders.known_folders
NON_DELETABLE_FOLDERS = [
AllContacts,
AllItems,
+ ApplicationData,
Audits,
CalendarLogging,
CommonViews,
@@ -726,6 +769,16 @@ Module exchangelib.folders.known_folders
ArchiveRecoverableItemsPurges,
ArchiveRecoverableItemsRoot,
ArchiveRecoverableItemsVersions,
+]
+
+MISC_FOLDERS = [
+ CrawlerData,
+ DlpPolicyEvaluation,
+ FreeBusyCache,
+ RecoveryPoints,
+ SwssItems,
+ SkypeTeamsMessages,
+ Birthdays,
]
@@ -972,6 +1025,75 @@ Inherited members
+
+class ApplicationData
+(**kwargs)
+
+
+A mixin for non-wellknown folders than that are not deletable.
+
+
+Expand source code
+
+class ApplicationData(NonDeletableFolderMixin, Folder):
+ CONTAINER_CLASS = "IPM.ApplicationData"
+
+Ancestors
+
+- NonDeletableFolderMixin
+- Folder
+- BaseFolder
+- RegisterMixIn
+- IdChangeKeyMixIn
+- EWSElement
+- SearchableMixIn
+
+Class variables
+
+var CONTAINER_CLASS
+-
+
+
+
+Inherited members
+
+Folder
:
+
+ID_ELEMENT_CLS
+account
+add_field
+all
+deregister
+exclude
+filter
+folder_cls_from_container_class
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class ArchiveDeletedItems
(**kwargs)
@@ -1566,6 +1688,82 @@ Inherited members
+
+class Birthdays
+(**kwargs)
+
+
+
+
+
+Expand source code
+
+class Birthdays(Folder):
+ CONTAINER_CLASS = "IPF.Appointment.Birthday"
+ LOCALIZED_NAMES = {
+ None: ("Birthdays",),
+ "da_DK": ("Fødselsdage",),
+ }
+
+Ancestors
+
+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
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class Calendar
(**kwargs)
@@ -1843,6 +2041,7 @@ Inherited members
CONTAINTER_CLASS = "IPF.Contact.Company"
LOCALIZED_NAMES = {
None: ("Companies",),
+ "da_DK": ("Firmaer",),
}
Ancestors
@@ -2239,6 +2438,74 @@ Inherited members
+
+class CrawlerData
+(**kwargs)
+
+
+
+
+
+Expand source code
+
+class CrawlerData(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.CrawlerData"
+
+Ancestors
+
+Class variables
+
+var CONTAINER_CLASS
+-
+
+
+
+Inherited members
+
+Folder
:
+
+ID_ELEMENT_CLS
+account
+add_field
+all
+deregister
+exclude
+filter
+folder_cls_from_container_class
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class DefaultFoldersChangeHistory
(**kwargs)
@@ -2554,6 +2821,74 @@ Inherited members
+
+class DlpPolicyEvaluation
+(**kwargs)
+
+
+
+
+
+Expand source code
+
+class DlpPolicyEvaluation(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.DlpPolicyEvaluation"
+
+Ancestors
+
+Class variables
+
+var CONTAINER_CLASS
+-
+
+
+
+Inherited members
+
+Folder
:
+
+ID_ELEMENT_CLS
+account
+add_field
+all
+deregister
+exclude
+filter
+folder_cls_from_container_class
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class Drafts
(**kwargs)
@@ -2789,26 +3124,98 @@ Inherited members
-
-class Files
+
+class Files
+(**kwargs)
+
+
+A mixin for non-wellknown folders than that are not deletable.
+
+
+Expand source code
+
+class Files(NonDeletableFolderMixin, Folder):
+ CONTAINER_CLASS = "IPF.Files"
+
+ LOCALIZED_NAMES = {
+ "da_DK": ("Filer",),
+ }
+
+Ancestors
+
+- NonDeletableFolderMixin
+- Folder
+- BaseFolder
+- RegisterMixIn
+- IdChangeKeyMixIn
+- EWSElement
+- SearchableMixIn
+
+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
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
+
+class FreeBusyCache
(**kwargs)
-A mixin for non-wellknown folders than that are not deletable.
+
Expand source code
-class Files(NonDeletableFolderMixin, Folder):
- CONTAINER_CLASS = "IPF.Files"
-
- LOCALIZED_NAMES = {
- "da_DK": ("Filer",),
- }
+class FreeBusyCache(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.FreeBusyCache"
Ancestors
-- NonDeletableFolderMixin
- Folder
- BaseFolder
- RegisterMixIn
@@ -2818,11 +3225,7 @@ Ancestors
Class variables
-var CONTAINER_CLASS
--
-
-
-var LOCALIZED_NAMES
+var CONTAINER_CLASS
-
@@ -3809,6 +4212,8 @@ Inherited members
DISTINGUISHED_FOLDER_ID = "msgfolderroot"
LOCALIZED_NAMES = {
+ None: ("Top of Information Store",),
+ "da_DK": ("Informationslagerets øverste niveau",),
"zh_CN": ("信息存储顶部",),
}
@@ -4048,6 +4453,7 @@ Subclasses
- AllContacts
- AllItems
+- ApplicationData
- Audits
- CalendarLogging
- CommonViews
@@ -4280,7 +4686,7 @@ Inherited members
"de_DE": ("Postausgang",),
"en_US": ("Outbox",),
"es_ES": ("Bandeja de salida",),
- "fr_CA": (u"Boîte d'envoi",),
+ "fr_CA": ("Boîte d'envoi",),
"nl_NL": ("Postvak UIT",),
"ru_RU": ("Исходящие",),
"sv_SE": ("Utkorgen",),
@@ -5267,6 +5673,74 @@ Inherited members
+
+class RecoveryPoints
+(**kwargs)
+
+
+
+
+
+Expand source code
+
+class RecoveryPoints(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.RecoveryPoints"
+
+Ancestors
+
+Class variables
+
+var CONTAINER_CLASS
+-
+
+
+
+Inherited members
+
+Folder
:
+
+ID_ELEMENT_CLS
+account
+add_field
+all
+deregister
+exclude
+filter
+folder_cls_from_container_class
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class Reminders
(**kwargs)
@@ -5865,6 +6339,81 @@ Inherited members
+
+class SkypeTeamsMessages
+(**kwargs)
+
+
+
+
+
+Expand source code
+
+class SkypeTeamsMessages(Folder):
+ CONTAINER_CLASS = "IPF.SkypeTeams.Message"
+ LOCALIZED_NAMES = {
+ None: ("Team-chat",),
+ }
+
+Ancestors
+
+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
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class SmsAndChatsSync
(**kwargs)
@@ -6012,6 +6561,74 @@ Inherited members
+
+class SwssItems
+(**kwargs)
+
+
+
+
+
+Expand source code
+
+class SwssItems(Folder):
+ CONTAINER_CLASS = "IPF.StoreItem.SwssItems"
+
+Ancestors
+
+Class variables
+
+var CONTAINER_CLASS
+-
+
+
+
+Inherited members
+
+Folder
:
+
+ID_ELEMENT_CLS
+account
+add_field
+all
+deregister
+exclude
+filter
+folder_cls_from_container_class
+folder_sync_state
+get
+get_distinguished
+get_events
+get_streaming_events
+is_distinguished
+item_sync_state
+none
+parent
+people
+register
+remove_field
+root
+subscribe_to_pull
+subscribe_to_push
+subscribe_to_streaming
+supported_fields
+sync_hierarchy
+sync_items
+test_access
+tree
+unsubscribe
+validate_field
+
+
+
+
class SyncIssues
(**kwargs)
@@ -6859,6 +7476,12 @@ ApplicationData
+
+
+
ArchiveDeletedItems
DISTINGUISHED_FOLDER_ID
@@ -6915,6 +7538,13 @@ Birthdays
+
+
+
Calendar
CONTAINER_CLASS
@@ -6976,6 +7606,12 @@
+CrawlerData
+
+
+
DefaultFoldersChangeHistory
CONTAINER_CLASS
@@ -7005,6 +7641,12 @@ DlpPolicyEvaluation
+
+
+
Drafts
DISTINGUISHED_FOLDER_ID
@@ -7033,6 +7675,12 @@ FreeBusyCache
+
+
+
FreebusyData
LOCALIZED_NAMES
@@ -7255,6 +7903,12 @@
+RecoveryPoints
+
+
+
Reminders
CONTAINER_CLASS
@@ -7308,6 +7962,13 @@ SkypeTeamsMessages
+
+
+
SmsAndChatsSync
CONTAINER_CLASS
@@ -7321,6 +7982,12 @@ SwssItems
+
+
+
SyncIssues
CONTAINER_CLASS
diff --git a/docs/exchangelib/folders/roots.html b/docs/exchangelib/folders/roots.html
index 557f7715..713a1971 100644
--- a/docs/exchangelib/folders/roots.html
+++ b/docs/exchangelib/folders/roots.html
@@ -36,6 +36,7 @@ Module exchangelib.folders.roots
from .base import BaseFolder
from .collections import FolderCollection
from .known_folders import (
+ MISC_FOLDERS,
NON_DELETABLE_FOLDERS,
WELLKNOWN_FOLDERS_IN_ARCHIVE_ROOT,
WELLKNOWN_FOLDERS_IN_ROOT,
@@ -233,7 +234,7 @@ Module exchangelib.folders.roots
:param folder_name:
:param locale: a string, e.g. 'da_DK'
"""
- for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS:
+ for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS + MISC_FOLDERS:
if folder_name.lower() in folder_cls.localized_names(locale):
return folder_cls
raise KeyError()
@@ -984,7 +985,7 @@ Inherited members
:param folder_name:
:param locale: a string, e.g. 'da_DK'
"""
- for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS:
+ for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS + MISC_FOLDERS:
if folder_name.lower() in folder_cls.localized_names(locale):
return folder_cls
raise KeyError()
@@ -1050,7 +1051,7 @@ Static methods
:param folder_name:
:param locale: a string, e.g. 'da_DK'
"""
- for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS:
+ for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS + MISC_FOLDERS:
if folder_name.lower() in folder_cls.localized_names(locale):
return folder_cls
raise KeyError()
diff --git a/docs/exchangelib/index.html b/docs/exchangelib/index.html
index 53a8450a..0cdb0afa 100644
--- a/docs/exchangelib/index.html
+++ b/docs/exchangelib/index.html
@@ -56,7 +56,7 @@ Package exchangelib
from .transport import BASIC, CBA, DIGEST, GSSAPI, NTLM, OAUTH2, SSPI
from .version import Build, Version
-__version__ = "4.7.2"
+__version__ = "4.7.3"
__all__ = [
"__version__",
@@ -2932,38 +2932,29 @@ Inherited members
return session
def create_oauth2_session(self):
- has_token = False
scope = ["https://outlook.office365.com/.default"]
- session_params = {}
+ session_params = {"token": self.credentials.access_token} # Token may be None
token_params = {}
if isinstance(self.credentials, OAuth2AuthorizationCodeCredentials):
# Ask for a refresh token
scope.append("offline_access")
- # We don't know (or need) the Microsoft tenant ID. Use
- # common/ to let Microsoft select the appropriate tenant
- # for the provided authorization code or refresh token.
+ # We don't know (or need) the Microsoft tenant ID. Use common/ to let Microsoft select the appropriate
+ # tenant for the provided authorization code or refresh token.
#
# Suppress looks-like-password warning from Bandit.
token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" # nosec
client_params = {}
- has_token = self.credentials.access_token is not None
- if has_token:
- session_params["token"] = self.credentials.access_token
- elif self.credentials.authorization_code is not None:
- token_params["code"] = self.credentials.authorization_code
- self.credentials.authorization_code = None
+ token_params["code"] = self.credentials.authorization_code # Auth code may be None
+ self.credentials.authorization_code = None # We can only use the code once
if self.credentials.client_id is not None and self.credentials.client_secret is not None:
- # If we're given a client ID and secret, we have enough
- # to refresh access tokens ourselves. In other cases the
- # session will raise TokenExpiredError and we'll need to
- # ask the calling application to refresh the token (that
- # covers cases where the caller doesn't have access to
- # the client secret but is working with a service that
- # can provide it refreshed tokens on a limited basis).
+ # If we're given a client ID and secret, we have enough to refresh access tokens ourselves. In other
+ # cases the session will raise TokenExpiredError, and we'll need to ask the calling application to
+ # refresh the token (that covers cases where the caller doesn't have access to the client secret but
+ # is working with a service that can provide it refreshed tokens on a limited basis).
session_params.update(
{
"auto_refresh_kwargs": {
@@ -2980,7 +2971,7 @@ Inherited members
client = BackendApplicationClient(client_id=self.credentials.client_id)
session = self.raw_session(self.service_endpoint, oauth2_client=client, oauth2_session_params=session_params)
- if not has_token:
+ if not session.token:
# Fetch the token explicitly -- it doesn't occur implicitly
token = session.fetch_token(
token_url=token_url,
@@ -2990,8 +2981,8 @@ Inherited members
timeout=self.TIMEOUT,
**token_params,
)
- # Allow the credentials object to update its copy of the new
- # token, and give the application an opportunity to cache it
+ # Allow the credentials object to update its copy of the new token, and give the application an opportunity
+ # to cache it.
self.credentials.on_token_auto_refreshed(token)
session.auth = get_auth_instance(auth_type=OAUTH2, client=client)
@@ -3237,38 +3228,29 @@ Methods
Expand source code
def create_oauth2_session(self):
- has_token = False
scope = ["https://outlook.office365.com/.default"]
- session_params = {}
+ session_params = {"token": self.credentials.access_token} # Token may be None
token_params = {}
if isinstance(self.credentials, OAuth2AuthorizationCodeCredentials):
# Ask for a refresh token
scope.append("offline_access")
- # We don't know (or need) the Microsoft tenant ID. Use
- # common/ to let Microsoft select the appropriate tenant
- # for the provided authorization code or refresh token.
+ # We don't know (or need) the Microsoft tenant ID. Use common/ to let Microsoft select the appropriate
+ # tenant for the provided authorization code or refresh token.
#
# Suppress looks-like-password warning from Bandit.
token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" # nosec
client_params = {}
- has_token = self.credentials.access_token is not None
- if has_token:
- session_params["token"] = self.credentials.access_token
- elif self.credentials.authorization_code is not None:
- token_params["code"] = self.credentials.authorization_code
- self.credentials.authorization_code = None
+ token_params["code"] = self.credentials.authorization_code # Auth code may be None
+ self.credentials.authorization_code = None # We can only use the code once
if self.credentials.client_id is not None and self.credentials.client_secret is not None:
- # If we're given a client ID and secret, we have enough
- # to refresh access tokens ourselves. In other cases the
- # session will raise TokenExpiredError and we'll need to
- # ask the calling application to refresh the token (that
- # covers cases where the caller doesn't have access to
- # the client secret but is working with a service that
- # can provide it refreshed tokens on a limited basis).
+ # If we're given a client ID and secret, we have enough to refresh access tokens ourselves. In other
+ # cases the session will raise TokenExpiredError, and we'll need to ask the calling application to
+ # refresh the token (that covers cases where the caller doesn't have access to the client secret but
+ # is working with a service that can provide it refreshed tokens on a limited basis).
session_params.update(
{
"auto_refresh_kwargs": {
@@ -3285,7 +3267,7 @@ Methods
client = BackendApplicationClient(client_id=self.credentials.client_id)
session = self.raw_session(self.service_endpoint, oauth2_client=client, oauth2_session_params=session_params)
- if not has_token:
+ if not session.token:
# Fetch the token explicitly -- it doesn't occur implicitly
token = session.fetch_token(
token_url=token_url,
@@ -3295,8 +3277,8 @@ Methods
timeout=self.TIMEOUT,
**token_params,
)
- # Allow the credentials object to update its copy of the new
- # token, and give the application an opportunity to cache it
+ # Allow the credentials object to update its copy of the new token, and give the application an opportunity
+ # to cache it.
self.credentials.on_token_auto_refreshed(token)
session.auth = get_auth_instance(auth_type=OAUTH2, client=client)
@@ -4668,12 +4650,12 @@ Inherited members
if auth_type is None:
# Set a default auth type for the credentials where this makes sense
auth_type = DEFAULT_AUTH_TYPE.get(type(credentials))
- elif credentials is None and auth_type in CREDENTIALS_REQUIRED:
+ if auth_type is not None and auth_type not in AUTH_TYPE_MAP:
+ raise InvalidEnumValue("auth_type", auth_type, AUTH_TYPE_MAP)
+ if credentials is None and auth_type in CREDENTIALS_REQUIRED:
raise ValueError(f"Auth type {auth_type!r} was detected but no credentials were provided")
if server and service_endpoint:
raise AttributeError("Only one of 'server' or 'service_endpoint' must be provided")
- if auth_type is not None and auth_type not in AUTH_TYPE_MAP:
- raise InvalidEnumValue("auth_type", auth_type, AUTH_TYPE_MAP)
if not retry_policy:
retry_policy = FailFast()
if not isinstance(version, (Version, type(None))):
@@ -5918,7 +5900,7 @@ Methods
Ancestors
-- backports.zoneinfo.ZoneInfo
+- zoneinfo.ZoneInfo
- datetime.tzinfo
Class variables
@@ -7193,17 +7175,22 @@ Ancestors
Subclasses
- AllItems
+- ApplicationData
- Audits
+- Birthdays
- Calendar
- CalendarLogging
- CommonViews
- Contacts
- ConversationSettings
+- CrawlerData
- DefaultFoldersChangeHistory
- DeferredAction
- DeletedItems
+- DlpPolicyEvaluation
- ExchangeSyncData
- Files
+- FreeBusyCache
- FreebusyData
- GraphAnalytics
- Location
@@ -7213,13 +7200,16 @@ Subclasses
- PassThroughSearchResults
- PdpProfileV2Secured
- RSSFeeds
+- RecoveryPoints
- Reminders
- Schedule
- Sharing
- Shortcuts
- Signal
+- SkypeTeamsMessages
- SmsAndChatsSync
- SpoolerQueue
+- SwssItems
- System
- System1
- Tasks
@@ -8931,6 +8921,7 @@ Subclasses
- ConversationId
- FolderId
- MovedItemId
+- OldItemId
- ParentFolderId
- ParentItemId
- PersonaId
@@ -9170,7 +9161,7 @@ Inherited members
conversation_topic = CharField(field_uri="message:ConversationTopic", is_read_only=True)
# Rename 'From' to 'author'. We can't use fieldname 'from' since it's a Python keyword.
author = MailboxField(field_uri="message:From", is_read_only_after_send=True)
- message_id = CharField(field_uri="message:InternetMessageId", is_read_only_after_send=True)
+ message_id = TextField(field_uri="message:InternetMessageId", is_read_only_after_send=True)
is_read = BooleanField(field_uri="message:IsRead", is_required=True, default=False)
is_response_requested = BooleanField(field_uri="message:IsResponseRequested", default=False, is_required=True)
references = TextField(field_uri="message:References")
@@ -9302,7 +9293,7 @@ Inherited members
from ..services import MarkAsJunk
res = MarkAsJunk(account=self.account).get(
- items=[self], is_junk=is_junk, move_item=move_item, expect_result=move_item
+ items=[self], is_junk=is_junk, move_item=move_item, expect_result=None
)
if res is None:
return
@@ -9476,7 +9467,7 @@ Methods
from ..services import MarkAsJunk
res = MarkAsJunk(account=self.account).get(
- items=[self], is_junk=is_junk, move_item=move_item, expect_result=move_item
+ items=[self], is_junk=is_junk, move_item=move_item, expect_result=None
)
if res is None:
return
@@ -9672,15 +9663,15 @@ Methods
class OAuth2AuthorizationCodeCredentials
-(authorization_code=None, access_token=None, **kwargs)
+(authorization_code=None, access_token=None, client_id=None, client_secret=None, **kwargs)
-
Login info for OAuth 2.0 authentication using the authorization code grant type. This can be used in one of
several ways:
* Given an authorization code, client ID, and client secret, fetch a token ourselves and refresh it as needed if
supplied with a refresh token.
-* Given an existing access token, refresh token, client ID, and client secret, use the access token until it
-expires and then refresh it as needed.
+* Given an existing access token, client ID, and client secret, use the access token until it expires and then
+refresh it as needed.
* Given only an existing access token, use it until it expires. This can be used to let the calling application
refresh tokens itself by subclassing and implementing refresh().
Unlike the base (client credentials) grant, authorization code credentials don't require a Microsoft tenant ID
@@ -9703,8 +9694,8 @@
Methods
several ways:
* Given an authorization code, client ID, and client secret, fetch a token ourselves and refresh it as needed if
supplied with a refresh token.
- * Given an existing access token, refresh token, client ID, and client secret, use the access token until it
- expires and then refresh it as needed.
+ * Given an existing access token, client ID, and client secret, use the access token until it expires and then
+ refresh it as needed.
* Given only an existing access token, use it until it expires. This can be used to let the calling application
refresh tokens itself by subclassing and implementing refresh().
@@ -9713,7 +9704,7 @@ Methods
tenant.
"""
- def __init__(self, authorization_code=None, access_token=None, **kwargs):
+ def __init__(self, authorization_code=None, access_token=None, client_id=None, client_secret=None, **kwargs):
"""
:param client_id: ID of an authorized OAuth application, required for automatic token fetching and refreshing
@@ -9725,7 +9716,7 @@ Methods
:param access_token: Previously-obtained access token. If a token exists and the application will handle
refreshing by itself (or opts not to handle it), this parameter alone is sufficient.
"""
- super().__init__(**kwargs)
+ super().__init__(client_id=client_id, client_secret=client_secret, **kwargs)
self.authorization_code = authorization_code
if access_token is not None and not isinstance(access_token, dict):
raise InvalidTypeError("access_token", access_token, OAuth2Token)
@@ -9763,7 +9754,7 @@ Inherited members
class OAuth2Credentials
-(client_id, client_secret, tenant_id=None, identity=None)
+(client_id, client_secret, tenant_id=None, identity=None, access_token=None)
-
Login info for OAuth 2.0 client credentials authentication, as well as a base for other OAuth 2.0 grant types.
@@ -9774,7 +9765,8 @@ Inherited members
:param client_id: ID of an authorized OAuth application, required for automatic token fetching and refreshing
:param client_secret: Secret associated with the OAuth application
:param tenant_id: Microsoft tenant ID of the account to access
-:param identity: An Identity object representing the account that these credentials are connected to.
+:param identity: An Identity object representing the account that these credentials are connected to.
+:param access_token: Previously-obtained access token, as a dict or an oauthlib.oauth2.OAuth2Token
Expand source code
@@ -9788,21 +9780,21 @@ Inherited members
the associated auth code grant type for multi-tenant applications.
"""
- def __init__(self, client_id, client_secret, tenant_id=None, identity=None):
+ def __init__(self, client_id, client_secret, tenant_id=None, identity=None, access_token=None):
"""
:param client_id: ID of an authorized OAuth application, required for automatic token fetching and refreshing
:param client_secret: Secret associated with the OAuth application
:param tenant_id: Microsoft tenant ID of the account to access
:param identity: An Identity object representing the account that these credentials are connected to.
+ :param access_token: Previously-obtained access token, as a dict or an oauthlib.oauth2.OAuth2Token
"""
super().__init__()
self.client_id = client_id
self.client_secret = client_secret
self.tenant_id = tenant_id
self.identity = identity
- # When set, access_token is a dict (or an oauthlib.oauth2.OAuth2Token, which is also a dict)
- self.access_token = None
+ self.access_token = access_token
def refresh(self, session):
# Creating a new session gets a new access token, so there's no work here to refresh the credentials. This
@@ -11597,7 +11589,7 @@ Inherited members
:param folder_name:
:param locale: a string, e.g. 'da_DK'
"""
- for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS:
+ for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS + MISC_FOLDERS:
if folder_name.lower() in folder_cls.localized_names(locale):
return folder_cls
raise KeyError()
@@ -11663,7 +11655,7 @@ Static methods
:param folder_name:
:param locale: a string, e.g. 'da_DK'
"""
- for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS:
+ for folder_cls in cls.WELLKNOWN_FOLDERS + NON_DELETABLE_FOLDERS + MISC_FOLDERS:
if folder_name.lower() in folder_cls.localized_names(locale):
return folder_cls
raise KeyError()
diff --git a/docs/exchangelib/items/contact.html b/docs/exchangelib/items/contact.html
index cd576c2f..a479a399 100644
--- a/docs/exchangelib/items/contact.html
+++ b/docs/exchangelib/items/contact.html
@@ -809,7 +809,9 @@ Inherited members
hobbies = StringAttributedValueField(field_uri="persona:Hobbies")
wedding_anniversaries = StringAttributedValueField(field_uri="persona:WeddingAnniversaries")
birthdays = StringAttributedValueField(field_uri="persona:Birthdays")
- locations = StringAttributedValueField(field_uri="persona:Locations")
+ locations = StringAttributedValueField(field_uri="persona:Locations")
+ # This class has an additional field of type "ExtendedPropertyAttributedValueField" and
+ # field_uri 'persona:ExtendedProperties'
Ancestors
diff --git a/docs/exchangelib/items/index.html b/docs/exchangelib/items/index.html
index ca08feaa..b7463e18 100644
--- a/docs/exchangelib/items/index.html
+++ b/docs/exchangelib/items/index.html
@@ -3015,7 +3015,7 @@ Inherited members
conversation_topic = CharField(field_uri="message:ConversationTopic", is_read_only=True)
# Rename 'From' to 'author'. We can't use fieldname 'from' since it's a Python keyword.
author = MailboxField(field_uri="message:From", is_read_only_after_send=True)
- message_id = CharField(field_uri="message:InternetMessageId", is_read_only_after_send=True)
+ message_id = TextField(field_uri="message:InternetMessageId", is_read_only_after_send=True)
is_read = BooleanField(field_uri="message:IsRead", is_required=True, default=False)
is_response_requested = BooleanField(field_uri="message:IsResponseRequested", default=False, is_required=True)
references = TextField(field_uri="message:References")
@@ -3147,7 +3147,7 @@ Inherited members
from ..services import MarkAsJunk
res = MarkAsJunk(account=self.account).get(
- items=[self], is_junk=is_junk, move_item=move_item, expect_result=move_item
+ items=[self], is_junk=is_junk, move_item=move_item, expect_result=None
)
if res is None:
return
@@ -3321,7 +3321,7 @@ Methods
from ..services import MarkAsJunk
res = MarkAsJunk(account=self.account).get(
- items=[self], is_junk=is_junk, move_item=move_item, expect_result=move_item
+ items=[self], is_junk=is_junk, move_item=move_item, expect_result=None
)
if res is None:
return
@@ -3576,7 +3576,9 @@ Inherited members
hobbies = StringAttributedValueField(field_uri="persona:Hobbies")
wedding_anniversaries = StringAttributedValueField(field_uri="persona:WeddingAnniversaries")
birthdays = StringAttributedValueField(field_uri="persona:Birthdays")
- locations = StringAttributedValueField(field_uri="persona:Locations")
+ locations = StringAttributedValueField(field_uri="persona:Locations")
+ # This class has an additional field of type "ExtendedPropertyAttributedValueField" and
+ # field_uri 'persona:ExtendedProperties'
Ancestors
diff --git a/docs/exchangelib/items/message.html b/docs/exchangelib/items/message.html
index a5aafdb6..e724dee9 100644
--- a/docs/exchangelib/items/message.html
+++ b/docs/exchangelib/items/message.html
@@ -65,7 +65,7 @@ Module exchangelib.items.message
conversation_topic = CharField(field_uri="message:ConversationTopic", is_read_only=True)
# Rename 'From' to 'author'. We can't use fieldname 'from' since it's a Python keyword.
author = MailboxField(field_uri="message:From", is_read_only_after_send=True)
- message_id = CharField(field_uri="message:InternetMessageId", is_read_only_after_send=True)
+ message_id = TextField(field_uri="message:InternetMessageId", is_read_only_after_send=True)
is_read = BooleanField(field_uri="message:IsRead", is_required=True, default=False)
is_response_requested = BooleanField(field_uri="message:IsResponseRequested", default=False, is_required=True)
references = TextField(field_uri="message:References")
@@ -197,7 +197,7 @@ Module exchangelib.items.message
from ..services import MarkAsJunk
res = MarkAsJunk(account=self.account).get(
- items=[self], is_junk=is_junk, move_item=move_item, expect_result=move_item
+ items=[self], is_junk=is_junk, move_item=move_item, expect_result=None
)
if res is None:
return
@@ -316,7 +316,7 @@ Inherited members
conversation_topic = CharField(field_uri="message:ConversationTopic", is_read_only=True)
# Rename 'From' to 'author'. We can't use fieldname 'from' since it's a Python keyword.
author = MailboxField(field_uri="message:From", is_read_only_after_send=True)
- message_id = CharField(field_uri="message:InternetMessageId", is_read_only_after_send=True)
+ message_id = TextField(field_uri="message:InternetMessageId", is_read_only_after_send=True)
is_read = BooleanField(field_uri="message:IsRead", is_required=True, default=False)
is_response_requested = BooleanField(field_uri="message:IsResponseRequested", default=False, is_required=True)
references = TextField(field_uri="message:References")
@@ -448,7 +448,7 @@ Inherited members
from ..services import MarkAsJunk
res = MarkAsJunk(account=self.account).get(
- items=[self], is_junk=is_junk, move_item=move_item, expect_result=move_item
+ items=[self], is_junk=is_junk, move_item=move_item, expect_result=None
)
if res is None:
return
@@ -622,7 +622,7 @@ Methods
from ..services import MarkAsJunk
res = MarkAsJunk(account=self.account).get(
- items=[self], is_junk=is_junk, move_item=move_item, expect_result=move_item
+ items=[self], is_junk=is_junk, move_item=move_item, expect_result=None
)
if res is None:
return
diff --git a/docs/exchangelib/properties.html b/docs/exchangelib/properties.html
index 8684a767..68480f6d 100644
--- a/docs/exchangelib/properties.html
+++ b/docs/exchangelib/properties.html
@@ -35,7 +35,7 @@ Module exchangelib.properties
from inspect import getmro
from threading import Lock
-from .errors import InvalidTypeError, TimezoneDefinitionInvalidForYear
+from .errors import InvalidTypeError
from .fields import (
WEEKDAY_NAMES,
AssociatedCalendarItemIdField,
@@ -247,15 +247,15 @@ Module exchangelib.properties
# Folder class, making the custom field available for subclasses).
if local_fields:
kwargs["FIELDS"] = fields
- cls = super().__new__(mcs, name, bases, kwargs)
- cls._slots_keys = mcs._get_slots_keys(cls)
- return cls
+ klass = super().__new__(mcs, name, bases, kwargs)
+ klass._slots_keys = mcs._get_slots_keys(klass)
+ return klass
@staticmethod
- def _get_slots_keys(cls):
+ def _get_slots_keys(klass):
seen = set()
keys = []
- for c in reversed(getmro(cls)):
+ for c in reversed(getmro(klass)):
if not hasattr(c, "__slots__"):
continue
for k in c.__slots__:
@@ -610,6 +610,24 @@ Module exchangelib.properties
return item.id, item.changekey
+class OldItemId(ItemId):
+ """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/oldfolderid"""
+
+ ELEMENT_NAME = "OldItemId"
+
+
+class OldFolderId(FolderId):
+ """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/olditemid"""
+
+ ELEMENT_NAME = "OldFolderId"
+
+
+class OldParentFolderId(ParentFolderId):
+ """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/oldparentfolderid"""
+
+ ELEMENT_NAME = "OldParentFolderId"
+
+
class Mailbox(EWSElement):
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/mailbox"""
@@ -1733,9 +1751,9 @@ Module exchangelib.properties
ITEM = "item"
timestamp = DateTimeField(field_uri="TimeStamp")
- item_id = EWSElementField(field_uri="ItemId", value_cls=ItemId)
- folder_id = EWSElementField(field_uri="FolderId", value_cls=FolderId)
- parent_folder_id = EWSElementField(field_uri="ParentFolderId", value_cls=ParentFolderId)
+ item_id = EWSElementField(value_cls=ItemId)
+ folder_id = EWSElementField(value_cls=FolderId)
+ parent_folder_id = EWSElementField(value_cls=ParentFolderId)
@property
def event_type(self):
@@ -1749,9 +1767,9 @@ Module exchangelib.properties
class OldTimestampEvent(TimestampEvent, metaclass=EWSMeta):
"""Base class for both item and folder copy/move events."""
- old_item_id = EWSElementField(field_uri="OldItemId", value_cls=ItemId)
- old_folder_id = EWSElementField(field_uri="OldFolderId", value_cls=FolderId)
- old_parent_folder_id = EWSElementField(field_uri="OldParentFolderId", value_cls=ParentFolderId)
+ old_item_id = EWSElementField(value_cls=OldItemId)
+ old_folder_id = EWSElementField(value_cls=OldFolderId)
+ old_parent_folder_id = EWSElementField(value_cls=OldParentFolderId)
class CopiedEvent(OldTimestampEvent):
@@ -1892,18 +1910,6 @@ Module exchangelib.properties
name = CharField(field_uri="Name", is_attribute=True)
bias = TimeDeltaField(field_uri="Bias", is_attribute=True)
- def _split_id(self):
- to_year, to_type = self.id.rsplit("/", 1)[1].split("-")
- return int(to_year), to_type
-
- @property
- def year(self):
- return self._split_id()[0]
-
- @property
- def type(self):
- return self._split_id()[1]
-
@property
def bias_in_minutes(self):
return int(self.bias.total_seconds()) // 60 # Convert to minutes
@@ -1934,18 +1940,15 @@ Module exchangelib.properties
def from_xml(cls, elem, account):
return super().from_xml(elem, account)
- def _get_standard_period(self, for_year):
- # Look through periods and pick a relevant period according to the 'for_year' value
- valid_period = None
- for period in sorted(self.periods, key=lambda p: (p.year, p.type)):
- if period.year > for_year:
- break
- if period.type != "Standard":
+ def _get_standard_period(self, transitions_group):
+ # Find the first standard period referenced from transitions_group
+ standard_periods_map = {p.id: p for p in self.periods if p.name == "Standard"}
+ for transition in transitions_group.transitions:
+ try:
+ return standard_periods_map[transition.to]
+ except KeyError:
continue
- valid_period = period
- if valid_period is None:
- raise TimezoneDefinitionInvalidForYear(f"Year {for_year} not included in periods {self.periods}")
- return valid_period
+ raise ValueError(f"No standard period matching any transition in {transitions_group}")
def _get_transitions_group(self, for_year):
# Look through the transitions, and pick the relevant transition group according to the 'for_year' value
@@ -1967,7 +1970,7 @@ Module exchangelib.properties
if not 0 <= len(transitions_group.transitions) <= 2:
raise ValueError(f"Expected 0-2 transitions in transitions group {transitions_group}")
- standard_period = self._get_standard_period(for_year)
+ standard_period = self._get_standard_period(transitions_group)
periods_map = {p.id: p for p in self.periods}
standard_time, daylight_time = None, None
if len(transitions_group.transitions) == 1:
@@ -3535,7 +3538,9 @@ Inherited members
class ConversationId(ItemId):
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/conversationid"""
- ELEMENT_NAME = "ConversationId"
+ ELEMENT_NAME = "ConversationId"
+
+ # ChangeKey attribute is sometimes required, see MSDN link
Ancestors
@@ -4615,9 +4620,8 @@ Methods
(*args, **kwargs)
-type(object_or_name, bases, dict)
-type(object) -> the object's type
-type(name, bases, dict) -> a new type
+type(object) -> the object's type
+type(name, bases, dict, **kwds) -> a new type
Expand source code
@@ -4653,15 +4657,15 @@ Methods
# Folder class, making the custom field available for subclasses).
if local_fields:
kwargs["FIELDS"] = fields
- cls = super().__new__(mcs, name, bases, kwargs)
- cls._slots_keys = mcs._get_slots_keys(cls)
- return cls
+ klass = super().__new__(mcs, name, bases, kwargs)
+ klass._slots_keys = mcs._get_slots_keys(klass)
+ return klass
@staticmethod
- def _get_slots_keys(cls):
+ def _get_slots_keys(klass):
seen = set()
keys = []
- for c in reversed(getmro(cls)):
+ for c in reversed(getmro(klass)):
if not hasattr(c, "__slots__"):
continue
for k in c.__slots__:
@@ -5435,6 +5439,7 @@ Ancestors
Subclasses
Class variables
@@ -6023,6 +6028,7 @@ Subclasses
ConversationId
FolderId
MovedItemId
+OldItemId
ParentFolderId
ParentItemId
PersonaId
@@ -6859,6 +6865,128 @@ Inherited members
+
+class OldFolderId
+(*args, **kwargs)
+
+
+
+
+
+Expand source code
+
+class OldFolderId(FolderId):
+ """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/olditemid"""
+
+ ELEMENT_NAME = "OldFolderId"
+
+Ancestors
+
+- FolderId
+- ItemId
+- BaseItemId
+- EWSElement
+
+Class variables
+
+var ELEMENT_NAME
+-
+
+
+
+Inherited members
+
+FolderId
:
+
+
+
+
+
+class OldItemId
+(*args, **kwargs)
+
+
+
+
+
+Expand source code
+
+class OldItemId(ItemId):
+ """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/oldfolderid"""
+
+ ELEMENT_NAME = "OldItemId"
+
+Ancestors
+
+- ItemId
+- BaseItemId
+- EWSElement
+
+Class variables
+
+var ELEMENT_NAME
+-
+
+
+
+Inherited members
+
+ItemId
:
+
+
+
+
+
+class OldParentFolderId
+(*args, **kwargs)
+
+
+MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/oldparentfolderid
+
+
+Expand source code
+
+class OldParentFolderId(ParentFolderId):
+ """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/oldparentfolderid"""
+
+ ELEMENT_NAME = "OldParentFolderId"
+
+Ancestors
+
+Class variables
+
+var ELEMENT_NAME
+-
+
+
+
+Inherited members
+
+ParentFolderId
:
+
+
+
+
class OldTimestampEvent
(**kwargs)
@@ -6872,9 +7000,9 @@ Inherited members
class OldTimestampEvent(TimestampEvent, metaclass=EWSMeta):
"""Base class for both item and folder copy/move events."""
- old_item_id = EWSElementField(field_uri="OldItemId", value_cls=ItemId)
- old_folder_id = EWSElementField(field_uri="OldFolderId", value_cls=FolderId)
- old_parent_folder_id = EWSElementField(field_uri="OldParentFolderId", value_cls=ParentFolderId)
+ old_item_id = EWSElementField(value_cls=OldItemId)
+ old_folder_id = EWSElementField(value_cls=OldFolderId)
+ old_parent_folder_id = EWSElementField(value_cls=OldParentFolderId)
Ancestors
@@ -7066,6 +7194,10 @@ Ancestors
- BaseItemId
- EWSElement
+Subclasses
+
Class variables
var ELEMENT_NAME
@@ -7149,18 +7281,6 @@ Inherited members
name = CharField(field_uri="Name", is_attribute=True)
bias = TimeDeltaField(field_uri="Bias", is_attribute=True)
- def _split_id(self):
- to_year, to_type = self.id.rsplit("/", 1)[1].split("-")
- return int(to_year), to_type
-
- @property
- def year(self):
- return self._split_id()[0]
-
- @property
- def type(self):
- return self._split_id()[1]
-
@property
def bias_in_minutes(self):
return int(self.bias.total_seconds()) // 60 # Convert to minutes
@@ -7206,30 +7326,6 @@ Instance variables
-var type
-
-
-
-
-Expand source code
-
-@property
-def type(self):
- return self._split_id()[1]
-
-
-var year
-
-
-
-
-Expand source code
-
-@property
-def year(self):
- return self._split_id()[0]
-
-
Inherited members
@@ -9236,18 +9332,15 @@ Inherited members
def from_xml(cls, elem, account):
return super().from_xml(elem, account)
- def _get_standard_period(self, for_year):
- # Look through periods and pick a relevant period according to the 'for_year' value
- valid_period = None
- for period in sorted(self.periods, key=lambda p: (p.year, p.type)):
- if period.year > for_year:
- break
- if period.type != "Standard":
+ def _get_standard_period(self, transitions_group):
+ # Find the first standard period referenced from transitions_group
+ standard_periods_map = {p.id: p for p in self.periods if p.name == "Standard"}
+ for transition in transitions_group.transitions:
+ try:
+ return standard_periods_map[transition.to]
+ except KeyError:
continue
- valid_period = period
- if valid_period is None:
- raise TimezoneDefinitionInvalidForYear(f"Year {for_year} not included in periods {self.periods}")
- return valid_period
+ raise ValueError(f"No standard period matching any transition in {transitions_group}")
def _get_transitions_group(self, for_year):
# Look through the transitions, and pick the relevant transition group according to the 'for_year' value
@@ -9269,7 +9362,7 @@ Inherited members
if not 0 <= len(transitions_group.transitions) <= 2:
raise ValueError(f"Expected 0-2 transitions in transitions group {transitions_group}")
- standard_period = self._get_standard_period(for_year)
+ standard_period = self._get_standard_period(transitions_group)
periods_map = {p.id: p for p in self.periods}
standard_time, daylight_time = None, None
if len(transitions_group.transitions) == 1:
@@ -9373,7 +9466,7 @@ Methods
if not 0 <= len(transitions_group.transitions) <= 2:
raise ValueError(f"Expected 0-2 transitions in transitions group {transitions_group}")
- standard_period = self._get_standard_period(for_year)
+ standard_period = self._get_standard_period(transitions_group)
periods_map = {p.id: p for p in self.periods}
standard_time, daylight_time = None, None
if len(transitions_group.transitions) == 1:
@@ -9563,9 +9656,9 @@ Inherited members
ITEM = "item"
timestamp = DateTimeField(field_uri="TimeStamp")
- item_id = EWSElementField(field_uri="ItemId", value_cls=ItemId)
- folder_id = EWSElementField(field_uri="FolderId", value_cls=FolderId)
- parent_folder_id = EWSElementField(field_uri="ParentFolderId", value_cls=ParentFolderId)
+ item_id = EWSElementField(value_cls=ItemId)
+ folder_id = EWSElementField(value_cls=FolderId)
+ parent_folder_id = EWSElementField(value_cls=ParentFolderId)
@property
def event_type(self):
@@ -10830,6 +10923,24 @@ OldFolderId
+
+ELEMENT_NAME
+
+
+-
+
OldItemId
+
+ELEMENT_NAME
+
+
+-
+
OldParentFolderId
+
+ELEMENT_NAME
+
+
+-
OldTimestampEvent
FIELDS
@@ -10872,8 +10983,6 @@ bias_in_minutes
id
name
-type
-year
diff --git a/docs/exchangelib/protocol.html b/docs/exchangelib/protocol.html
index bde37eaa..ce1a0410 100644
--- a/docs/exchangelib/protocol.html
+++ b/docs/exchangelib/protocol.html
@@ -340,38 +340,29 @@ Module exchangelib.protocol
return session
def create_oauth2_session(self):
- has_token = False
scope = ["https://outlook.office365.com/.default"]
- session_params = {}
+ session_params = {"token": self.credentials.access_token} # Token may be None
token_params = {}
if isinstance(self.credentials, OAuth2AuthorizationCodeCredentials):
# Ask for a refresh token
scope.append("offline_access")
- # We don't know (or need) the Microsoft tenant ID. Use
- # common/ to let Microsoft select the appropriate tenant
- # for the provided authorization code or refresh token.
+ # We don't know (or need) the Microsoft tenant ID. Use common/ to let Microsoft select the appropriate
+ # tenant for the provided authorization code or refresh token.
#
# Suppress looks-like-password warning from Bandit.
token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" # nosec
client_params = {}
- has_token = self.credentials.access_token is not None
- if has_token:
- session_params["token"] = self.credentials.access_token
- elif self.credentials.authorization_code is not None:
- token_params["code"] = self.credentials.authorization_code
- self.credentials.authorization_code = None
+ token_params["code"] = self.credentials.authorization_code # Auth code may be None
+ self.credentials.authorization_code = None # We can only use the code once
if self.credentials.client_id is not None and self.credentials.client_secret is not None:
- # If we're given a client ID and secret, we have enough
- # to refresh access tokens ourselves. In other cases the
- # session will raise TokenExpiredError and we'll need to
- # ask the calling application to refresh the token (that
- # covers cases where the caller doesn't have access to
- # the client secret but is working with a service that
- # can provide it refreshed tokens on a limited basis).
+ # If we're given a client ID and secret, we have enough to refresh access tokens ourselves. In other
+ # cases the session will raise TokenExpiredError, and we'll need to ask the calling application to
+ # refresh the token (that covers cases where the caller doesn't have access to the client secret but
+ # is working with a service that can provide it refreshed tokens on a limited basis).
session_params.update(
{
"auto_refresh_kwargs": {
@@ -388,7 +379,7 @@ Module exchangelib.protocol
client = BackendApplicationClient(client_id=self.credentials.client_id)
session = self.raw_session(self.service_endpoint, oauth2_client=client, oauth2_session_params=session_params)
- if not has_token:
+ if not session.token:
# Fetch the token explicitly -- it doesn't occur implicitly
token = session.fetch_token(
token_url=token_url,
@@ -398,8 +389,8 @@ Module exchangelib.protocol
timeout=self.TIMEOUT,
**token_params,
)
- # Allow the credentials object to update its copy of the new
- # token, and give the application an opportunity to cache it
+ # Allow the credentials object to update its copy of the new token, and give the application an opportunity
+ # to cache it.
self.credentials.on_token_auto_refreshed(token)
session.auth = get_auth_instance(auth_type=OAUTH2, client=client)
@@ -1139,38 +1130,29 @@ Classes
return session
def create_oauth2_session(self):
- has_token = False
scope = ["https://outlook.office365.com/.default"]
- session_params = {}
+ session_params = {"token": self.credentials.access_token} # Token may be None
token_params = {}
if isinstance(self.credentials, OAuth2AuthorizationCodeCredentials):
# Ask for a refresh token
scope.append("offline_access")
- # We don't know (or need) the Microsoft tenant ID. Use
- # common/ to let Microsoft select the appropriate tenant
- # for the provided authorization code or refresh token.
+ # We don't know (or need) the Microsoft tenant ID. Use common/ to let Microsoft select the appropriate
+ # tenant for the provided authorization code or refresh token.
#
# Suppress looks-like-password warning from Bandit.
token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" # nosec
client_params = {}
- has_token = self.credentials.access_token is not None
- if has_token:
- session_params["token"] = self.credentials.access_token
- elif self.credentials.authorization_code is not None:
- token_params["code"] = self.credentials.authorization_code
- self.credentials.authorization_code = None
+ token_params["code"] = self.credentials.authorization_code # Auth code may be None
+ self.credentials.authorization_code = None # We can only use the code once
if self.credentials.client_id is not None and self.credentials.client_secret is not None:
- # If we're given a client ID and secret, we have enough
- # to refresh access tokens ourselves. In other cases the
- # session will raise TokenExpiredError and we'll need to
- # ask the calling application to refresh the token (that
- # covers cases where the caller doesn't have access to
- # the client secret but is working with a service that
- # can provide it refreshed tokens on a limited basis).
+ # If we're given a client ID and secret, we have enough to refresh access tokens ourselves. In other
+ # cases the session will raise TokenExpiredError, and we'll need to ask the calling application to
+ # refresh the token (that covers cases where the caller doesn't have access to the client secret but
+ # is working with a service that can provide it refreshed tokens on a limited basis).
session_params.update(
{
"auto_refresh_kwargs": {
@@ -1187,7 +1169,7 @@ Classes
client = BackendApplicationClient(client_id=self.credentials.client_id)
session = self.raw_session(self.service_endpoint, oauth2_client=client, oauth2_session_params=session_params)
- if not has_token:
+ if not session.token:
# Fetch the token explicitly -- it doesn't occur implicitly
token = session.fetch_token(
token_url=token_url,
@@ -1197,8 +1179,8 @@ Classes
timeout=self.TIMEOUT,
**token_params,
)
- # Allow the credentials object to update its copy of the new
- # token, and give the application an opportunity to cache it
+ # Allow the credentials object to update its copy of the new token, and give the application an opportunity
+ # to cache it.
self.credentials.on_token_auto_refreshed(token)
session.auth = get_auth_instance(auth_type=OAUTH2, client=client)
@@ -1444,38 +1426,29 @@ Methods
Expand source code
def create_oauth2_session(self):
- has_token = False
scope = ["https://outlook.office365.com/.default"]
- session_params = {}
+ session_params = {"token": self.credentials.access_token} # Token may be None
token_params = {}
if isinstance(self.credentials, OAuth2AuthorizationCodeCredentials):
# Ask for a refresh token
scope.append("offline_access")
- # We don't know (or need) the Microsoft tenant ID. Use
- # common/ to let Microsoft select the appropriate tenant
- # for the provided authorization code or refresh token.
+ # We don't know (or need) the Microsoft tenant ID. Use common/ to let Microsoft select the appropriate
+ # tenant for the provided authorization code or refresh token.
#
# Suppress looks-like-password warning from Bandit.
token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" # nosec
client_params = {}
- has_token = self.credentials.access_token is not None
- if has_token:
- session_params["token"] = self.credentials.access_token
- elif self.credentials.authorization_code is not None:
- token_params["code"] = self.credentials.authorization_code
- self.credentials.authorization_code = None
+ token_params["code"] = self.credentials.authorization_code # Auth code may be None
+ self.credentials.authorization_code = None # We can only use the code once
if self.credentials.client_id is not None and self.credentials.client_secret is not None:
- # If we're given a client ID and secret, we have enough
- # to refresh access tokens ourselves. In other cases the
- # session will raise TokenExpiredError and we'll need to
- # ask the calling application to refresh the token (that
- # covers cases where the caller doesn't have access to
- # the client secret but is working with a service that
- # can provide it refreshed tokens on a limited basis).
+ # If we're given a client ID and secret, we have enough to refresh access tokens ourselves. In other
+ # cases the session will raise TokenExpiredError, and we'll need to ask the calling application to
+ # refresh the token (that covers cases where the caller doesn't have access to the client secret but
+ # is working with a service that can provide it refreshed tokens on a limited basis).
session_params.update(
{
"auto_refresh_kwargs": {
@@ -1492,7 +1465,7 @@ Methods
client = BackendApplicationClient(client_id=self.credentials.client_id)
session = self.raw_session(self.service_endpoint, oauth2_client=client, oauth2_session_params=session_params)
- if not has_token:
+ if not session.token:
# Fetch the token explicitly -- it doesn't occur implicitly
token = session.fetch_token(
token_url=token_url,
@@ -1502,8 +1475,8 @@ Methods
timeout=self.TIMEOUT,
**token_params,
)
- # Allow the credentials object to update its copy of the new
- # token, and give the application an opportunity to cache it
+ # Allow the credentials object to update its copy of the new token, and give the application an opportunity
+ # to cache it.
self.credentials.on_token_auto_refreshed(token)
session.auth = get_auth_instance(auth_type=OAUTH2, client=client)
diff --git a/docs/exchangelib/services/common.html b/docs/exchangelib/services/common.html
index a4ee88eb..866fef8e 100644
--- a/docs/exchangelib/services/common.html
+++ b/docs/exchangelib/services/common.html
@@ -28,55 +28,29 @@ Module exchangelib.services.common
import abc
import logging
-import traceback
from itertools import chain
from .. import errors
from ..attachments import AttachmentId
from ..credentials import IMPERSONATION, OAuth2Credentials
from ..errors import (
- ErrorAccessDenied,
- ErrorADUnavailable,
ErrorBatchProcessingStopped,
ErrorCannotDeleteObject,
ErrorCannotDeleteTaskOccurrence,
- ErrorCannotEmptyFolder,
- ErrorConnectionFailed,
- ErrorConnectionFailedTransientError,
ErrorCorruptData,
- ErrorCreateItemAccessDenied,
- ErrorDelegateNoUser,
- ErrorDeleteDistinguishedFolder,
ErrorExceededConnectionCount,
- ErrorFolderNotFound,
- ErrorImpersonateUserDenied,
- ErrorImpersonationFailed,
ErrorIncorrectSchemaVersion,
- ErrorInternalServerError,
- ErrorInternalServerTransientError,
ErrorInvalidChangeKey,
ErrorInvalidIdMalformed,
- ErrorInvalidLicense,
ErrorInvalidRequest,
ErrorInvalidSchemaVersionForMailboxVersion,
ErrorInvalidServerVersion,
- ErrorInvalidSubscription,
- ErrorInvalidSyncStateData,
- ErrorInvalidWatermark,
ErrorItemCorrupt,
ErrorItemNotFound,
ErrorItemSave,
- ErrorMailboxMoveInProgress,
- ErrorMailboxStoreUnavailable,
+ ErrorMailRecipientNotFound,
ErrorMessageSizeExceeded,
ErrorMimeContentConversionFailed,
- ErrorNameResolutionMultipleResults,
- ErrorNameResolutionNoResults,
- ErrorNonExistentMailbox,
- ErrorNoPublicFolderReplicaAvailable,
- ErrorNoRespondingCASInDestinationSite,
- ErrorNotDelegate,
- ErrorQuotaExceeded,
ErrorRecurrenceHasNoOccurrence,
ErrorServerBusy,
ErrorTimeoutExpired,
@@ -84,11 +58,9 @@ Module exchangelib.services.common
EWSWarning,
InvalidTypeError,
MalformedResponseError,
- RateLimitError,
SessionPoolMinSizeReached,
SOAPError,
TransportError,
- UnauthorizedError,
)
from ..folders import BaseFolder, Folder, RootOfHierarchy
from ..items import BaseItem
@@ -126,44 +98,6 @@ Module exchangelib.services.common
PAGE_SIZE = 100 # A default page size for all paging services. This is the number of items we request per page
CHUNK_SIZE = 100 # A default chunk size for all services. This is the number of items we send in a single request
-KNOWN_EXCEPTIONS = (
- ErrorAccessDenied,
- ErrorADUnavailable,
- ErrorBatchProcessingStopped,
- ErrorCannotDeleteObject,
- ErrorCannotEmptyFolder,
- ErrorConnectionFailed,
- ErrorConnectionFailedTransientError,
- ErrorCreateItemAccessDenied,
- ErrorDelegateNoUser,
- ErrorDeleteDistinguishedFolder,
- ErrorExceededConnectionCount,
- ErrorFolderNotFound,
- ErrorImpersonateUserDenied,
- ErrorImpersonationFailed,
- ErrorInternalServerError,
- ErrorInternalServerTransientError,
- ErrorInvalidChangeKey,
- ErrorInvalidLicense,
- ErrorInvalidSubscription,
- ErrorInvalidSyncStateData,
- ErrorInvalidWatermark,
- ErrorItemCorrupt,
- ErrorItemNotFound,
- ErrorMailboxMoveInProgress,
- ErrorMailboxStoreUnavailable,
- ErrorNameResolutionMultipleResults,
- ErrorNameResolutionNoResults,
- ErrorNonExistentMailbox,
- ErrorNoPublicFolderReplicaAvailable,
- ErrorNoRespondingCASInDestinationSite,
- ErrorNotDelegate,
- ErrorQuotaExceeded,
- ErrorTimeoutExpired,
- RateLimitError,
- UnauthorizedError,
-)
-
class EWSService(metaclass=abc.ABCMeta):
"""Base class for all EWS services."""
@@ -186,6 +120,7 @@ Module exchangelib.services.common
ErrorRecurrenceHasNoOccurrence,
ErrorCorruptData,
ErrorItemCorrupt,
+ ErrorMailRecipientNotFound,
)
# Similarly, define the warnings we want to return unraised
WARNINGS_TO_CATCH_IN_RESPONSE = ErrorBatchProcessingStopped
@@ -360,9 +295,6 @@ Module exchangelib.services.common
except ErrorServerBusy as e:
self._handle_backoff(e)
continue
- except KNOWN_EXCEPTIONS:
- # These are known and understood, and don't require a backtrace.
- raise
except (ErrorTooManyObjectsOpened, ErrorTimeoutExpired) as e:
# ErrorTooManyObjectsOpened means there are too many connections to the Exchange database. This is very
# often a symptom of sending too many requests.
@@ -377,11 +309,6 @@ Module exchangelib.services.common
# Re-raise as an ErrorServerBusy with a default delay of 5 minutes
raise ErrorServerBusy(f"Reraised from {e.__class__.__name__}({e})")
- except Exception:
- # This may run in a thread, which obfuscates the stack trace. Print trace immediately.
- account = self.account if isinstance(self, EWSAccountService) else None
- log.warning("Account %s: Exception in _get_elements: %s", account, traceback.format_exc(20))
- raise
finally:
if self.streaming:
self.stop_streaming()
@@ -809,7 +736,7 @@ Module exchangelib.services.common
def _account_to_impersonate(self):
if self.account.access_type == IMPERSONATION:
return self.account.identity
- return None
+ return super()._account_to_impersonate
@property
def _timezone(self):
@@ -1232,7 +1159,7 @@ Classes
def _account_to_impersonate(self):
if self.account.access_type == IMPERSONATION:
return self.account.identity
- return None
+ return super()._account_to_impersonate
@property
def _timezone(self):
@@ -1504,6 +1431,7 @@ Inherited members
ErrorRecurrenceHasNoOccurrence,
ErrorCorruptData,
ErrorItemCorrupt,
+ ErrorMailRecipientNotFound,
)
# Similarly, define the warnings we want to return unraised
WARNINGS_TO_CATCH_IN_RESPONSE = ErrorBatchProcessingStopped
@@ -1678,9 +1606,6 @@ Inherited members
except ErrorServerBusy as e:
self._handle_backoff(e)
continue
- except KNOWN_EXCEPTIONS:
- # These are known and understood, and don't require a backtrace.
- raise
except (ErrorTooManyObjectsOpened, ErrorTimeoutExpired) as e:
# ErrorTooManyObjectsOpened means there are too many connections to the Exchange database. This is very
# often a symptom of sending too many requests.
@@ -1695,11 +1620,6 @@ Inherited members
# Re-raise as an ErrorServerBusy with a default delay of 5 minutes
raise ErrorServerBusy(f"Reraised from {e.__class__.__name__}({e})")
- except Exception:
- # This may run in a thread, which obfuscates the stack trace. Print trace immediately.
- account = self.account if isinstance(self, EWSAccountService) else None
- log.warning("Account %s: Exception in _get_elements: %s", account, traceback.format_exc(20))
- raise
finally:
if self.streaming:
self.stop_streaming()
diff --git a/docs/exchangelib/services/get_user_availability.html b/docs/exchangelib/services/get_user_availability.html
index ba93a061..6e1a0d84 100644
--- a/docs/exchangelib/services/get_user_availability.html
+++ b/docs/exchangelib/services/get_user_availability.html
@@ -71,9 +71,11 @@ Module exchangelib.services.get_user_availability
def _get_elements_in_response(self, response):
for msg in response:
- # Just check the response code and raise errors
- self._get_element_container(message=msg.find(f"{{{MNS}}}ResponseMessage"))
- yield from self._get_elements_in_container(container=msg)
+ container_or_exc = self._get_element_container(message=msg.find(f"{{{MNS}}}ResponseMessage"))
+ if isinstance(container_or_exc, Exception):
+ yield container_or_exc
+ else:
+ yield from self._get_elements_in_container(container=msg)
@classmethod
def _get_elements_in_container(cls, container):
@@ -141,9 +143,11 @@ Classes
def _get_elements_in_response(self, response):
for msg in response:
- # Just check the response code and raise errors
- self._get_element_container(message=msg.find(f"{{{MNS}}}ResponseMessage"))
- yield from self._get_elements_in_container(container=msg)
+ container_or_exc = self._get_element_container(message=msg.find(f"{{{MNS}}}ResponseMessage"))
+ if isinstance(container_or_exc, Exception):
+ yield container_or_exc
+ else:
+ yield from self._get_elements_in_container(container=msg)
@classmethod
def _get_elements_in_container(cls, container):
diff --git a/docs/exchangelib/services/index.html b/docs/exchangelib/services/index.html
index b10f1196..dac091e4 100644
--- a/docs/exchangelib/services/index.html
+++ b/docs/exchangelib/services/index.html
@@ -4225,9 +4225,11 @@ Inherited members
def _get_elements_in_response(self, response):
for msg in response:
- # Just check the response code and raise errors
- self._get_element_container(message=msg.find(f"{{{MNS}}}ResponseMessage"))
- yield from self._get_elements_in_container(container=msg)
+ container_or_exc = self._get_element_container(message=msg.find(f"{{{MNS}}}ResponseMessage"))
+ if isinstance(container_or_exc, Exception):
+ yield container_or_exc
+ else:
+ yield from self._get_elements_in_container(container=msg)
@classmethod
def _get_elements_in_container(cls, container):