diff --git a/CHANGELOG.md b/CHANGELOG.md index b9a65276..d3842602 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,15 @@ Change Log HEAD ---- + + +4.7.2 +----- - Fixed field name to match API: `BaseReplyItem.received_by_representing` to -- `BaseReplyItem.received_representing` + `BaseReplyItem.received_representing` - Added fields `received_by` and `received_representing` to `MeetingRequest`, -- `MeetingMessage` and `MeetingCancellation` + `MeetingMessage` and `MeetingCancellation` +- Fixed `AppointmentStateField.CANCELLED` enum value. 4.7.1 diff --git a/docs/exchangelib/account.html b/docs/exchangelib/account.html index eaaa09f9..4d547a5b 100644 --- a/docs/exchangelib/account.html +++ b/docs/exchangelib/account.html @@ -33,23 +33,77 @@

Module exchangelib.account

from .autodiscover import Autodiscovery from .configuration import Configuration -from .credentials import DELEGATE, IMPERSONATION, ACCESS_TYPES -from .errors import UnknownTimeZone, InvalidEnumValue, InvalidTypeError -from .ewsdatetime import EWSTimeZone, UTC +from .credentials import ACCESS_TYPES, DELEGATE, IMPERSONATION +from .errors import InvalidEnumValue, InvalidTypeError, UnknownTimeZone +from .ewsdatetime import UTC, EWSTimeZone from .fields import FieldPath -from .folders import Folder, AdminAuditLogs, ArchiveDeletedItems, ArchiveInbox, ArchiveMsgFolderRoot, \ - ArchiveRecoverableItemsDeletions, ArchiveRecoverableItemsPurges, ArchiveRecoverableItemsRoot, \ - ArchiveRecoverableItemsVersions, ArchiveRoot, Calendar, Conflicts, Contacts, ConversationHistory, DeletedItems, \ - Directory, Drafts, Favorites, IMContactList, Inbox, Journal, JunkEmail, LocalFailures, MsgFolderRoot, MyContacts, \ - Notes, Outbox, PeopleConnect, PublicFoldersRoot, QuickContacts, RecipientCache, RecoverableItemsDeletions, \ - RecoverableItemsPurges, RecoverableItemsRoot, RecoverableItemsVersions, Root, SearchFolders, SentItems, \ - ServerFailures, SyncIssues, Tasks, ToDoSearch, VoiceMail -from .items import HARD_DELETE, AUTO_RESOLVE, SEND_TO_NONE, SAVE_ONLY, ALL_OCCURRENCES, ID_ONLY +from .folders import ( + AdminAuditLogs, + ArchiveDeletedItems, + ArchiveInbox, + ArchiveMsgFolderRoot, + ArchiveRecoverableItemsDeletions, + ArchiveRecoverableItemsPurges, + ArchiveRecoverableItemsRoot, + ArchiveRecoverableItemsVersions, + ArchiveRoot, + Calendar, + Conflicts, + Contacts, + ConversationHistory, + DeletedItems, + Directory, + Drafts, + Favorites, + Folder, + IMContactList, + Inbox, + Journal, + JunkEmail, + LocalFailures, + MsgFolderRoot, + MyContacts, + Notes, + Outbox, + PeopleConnect, + PublicFoldersRoot, + QuickContacts, + RecipientCache, + RecoverableItemsDeletions, + RecoverableItemsPurges, + RecoverableItemsRoot, + RecoverableItemsVersions, + Root, + SearchFolders, + SentItems, + ServerFailures, + SyncIssues, + Tasks, + ToDoSearch, + VoiceMail, +) +from .items import ALL_OCCURRENCES, AUTO_RESOLVE, HARD_DELETE, ID_ONLY, SAVE_ONLY, SEND_TO_NONE from .properties import Mailbox, SendingAs from .protocol import Protocol from .queryset import QuerySet -from .services import ExportItems, UploadItems, GetItem, CreateItem, UpdateItem, DeleteItem, MoveItem, SendItem, \ - CopyItem, GetUserOofSettings, SetUserOofSettings, GetMailTips, ArchiveItem, GetDelegate, MarkAsJunk, GetPersona +from .services import ( + ArchiveItem, + CopyItem, + CreateItem, + DeleteItem, + ExportItems, + GetDelegate, + GetItem, + GetMailTips, + GetPersona, + GetUserOofSettings, + MarkAsJunk, + MoveItem, + SendItem, + SetUserOofSettings, + UpdateItem, + UploadItems, +) from .util import get_domain, peek log = getLogger(__name__) @@ -85,8 +139,17 @@

Module exchangelib.account

class Account: """Models an Exchange server user account.""" - def __init__(self, primary_smtp_address, fullname=None, access_type=None, autodiscover=False, credentials=None, - config=None, locale=None, default_timezone=None): + def __init__( + self, + primary_smtp_address, + fullname=None, + access_type=None, + autodiscover=False, + credentials=None, + config=None, + locale=None, + default_timezone=None, + ): """ :param primary_smtp_address: The primary email address associated with the account on the Exchange server @@ -103,37 +166,37 @@

Module exchangelib.account

assume values to be in the provided timezone. Defaults to the timezone of the host. :return: """ - if '@' not in primary_smtp_address: + if "@" not in primary_smtp_address: raise ValueError(f"primary_smtp_address {primary_smtp_address!r} is not an email address") self.fullname = fullname # Assume delegate access if individual credentials are provided. Else, assume service user with impersonation self.access_type = access_type or (DELEGATE if credentials else IMPERSONATION) if self.access_type not in ACCESS_TYPES: - raise InvalidEnumValue('access_type', self.access_type, ACCESS_TYPES) + raise InvalidEnumValue("access_type", self.access_type, ACCESS_TYPES) try: # get_locale() might not be able to determine the locale self.locale = locale or stdlib_locale.getlocale()[0] or None except ValueError as e: # getlocale() may throw ValueError if it fails to parse the system locale - log.warning('Failed to get locale (%s)', e) + log.warning("Failed to get locale (%s)", e) self.locale = None if not isinstance(self.locale, (type(None), str)): - raise InvalidTypeError('locale', self.locale, str) + raise InvalidTypeError("locale", self.locale, str) if default_timezone: try: self.default_timezone = EWSTimeZone.from_timezone(default_timezone) except TypeError: - raise InvalidTypeError('default_timezone', default_timezone, EWSTimeZone) + raise InvalidTypeError("default_timezone", default_timezone, EWSTimeZone) else: try: self.default_timezone = EWSTimeZone.localzone() except (ValueError, UnknownTimeZone) as e: # There is no translation from local timezone name to Windows timezone name, or e failed to find the # local timezone. - log.warning('%s. Fallback to UTC', e.args[0]) + log.warning("%s. Fallback to UTC", e.args[0]) self.default_timezone = UTC if not isinstance(config, (Configuration, type(None))): - raise InvalidTypeError('config', config, Configuration) + raise InvalidTypeError("config", config, Configuration) if autodiscover: if config: auth_type, retry_policy, version = config.auth_type, config.retry_policy, config.version @@ -153,7 +216,7 @@

Module exchangelib.account

primary_smtp_address = self.ad_response.autodiscover_smtp_address else: if not config: - raise AttributeError('non-autodiscover requires a config') + raise AttributeError("non-autodiscover requires a config") self.ad_response = None self.protocol = Protocol(config=config) @@ -167,7 +230,7 @@

Module exchangelib.account

# server version up-front but delegate account requests to an older backend server. Create a new instance to # avoid changing the protocol version. self.version = self.protocol.version.copy() - log.debug('Added account: %s', self) + log.debug("Added account: %s", self) @property def primary_smtp_address(self): @@ -375,7 +438,7 @@

Module exchangelib.account

# We accept generators, so it's not always convenient for caller to know up-front if 'ids' is empty. Allow # empty 'ids' and return early. return - kwargs['items'] = items + kwargs["items"] = items yield from service_cls(account=self, chunk_size=chunk_size).call(**kwargs) def export(self, items, chunk_size=None): @@ -386,9 +449,7 @@

Module exchangelib.account

:return: A list of strings, the exported representation of the object """ - return list( - self._consume_item_service(service_cls=ExportItems, items=items, chunk_size=chunk_size, kwargs={}) - ) + return list(self._consume_item_service(service_cls=ExportItems, items=items, chunk_size=chunk_size, kwargs={})) def upload(self, data, chunk_size=None): """Upload objects retrieved from an export to the given folders. @@ -410,12 +471,11 @@

Module exchangelib.account

-> [("idA", "changekey"), ("idB", "changekey"), ("idC", "changekey")] """ items = ((f, (None, False, d) if isinstance(d, str) else d) for f, d in data) - return list( - self._consume_item_service(service_cls=UploadItems, items=items, chunk_size=chunk_size, kwargs={}) - ) + return list(self._consume_item_service(service_cls=UploadItems, items=items, chunk_size=chunk_size, kwargs={})) - def bulk_create(self, folder, items, message_disposition=SAVE_ONLY, send_meeting_invitations=SEND_TO_NONE, - chunk_size=None): + def bulk_create( + self, folder, items, message_disposition=SAVE_ONLY, send_meeting_invitations=SEND_TO_NONE, chunk_size=None + ): """Create new items in 'folder'. :param folder: the folder to create the items in @@ -432,23 +492,36 @@

Module exchangelib.account

""" if isinstance(items, QuerySet): # bulk_create() on a queryset does not make sense because it returns items that have already been created - raise ValueError('Cannot bulk create items from a QuerySet') + raise ValueError("Cannot bulk create items from a QuerySet") log.debug( - 'Adding items for %s (folder %s, message_disposition: %s, send_meeting_invitations: %s)', + "Adding items for %s (folder %s, message_disposition: %s, send_meeting_invitations: %s)", self, folder, message_disposition, send_meeting_invitations, ) - return list(self._consume_item_service(service_cls=CreateItem, items=items, chunk_size=chunk_size, kwargs=dict( - folder=folder, - message_disposition=message_disposition, - send_meeting_invitations=send_meeting_invitations, - ))) - - def bulk_update(self, items, conflict_resolution=AUTO_RESOLVE, message_disposition=SAVE_ONLY, - send_meeting_invitations_or_cancellations=SEND_TO_NONE, suppress_read_receipts=True, - chunk_size=None): + return list( + self._consume_item_service( + service_cls=CreateItem, + items=items, + chunk_size=chunk_size, + kwargs=dict( + folder=folder, + message_disposition=message_disposition, + send_meeting_invitations=send_meeting_invitations, + ), + ) + ) + + def bulk_update( + self, + items, + conflict_resolution=AUTO_RESOLVE, + message_disposition=SAVE_ONLY, + send_meeting_invitations_or_cancellations=SEND_TO_NONE, + suppress_read_receipts=True, + chunk_size=None, + ): """Bulk update existing items. :param items: a list of (Item, fieldnames) tuples, where 'Item' is an Item object, and 'fieldnames' is a list @@ -468,23 +541,37 @@

Module exchangelib.account

# fact, it could be dangerous if the queryset contains an '.only()'. This would wipe out certain fields # entirely. if isinstance(items, QuerySet): - raise ValueError('Cannot bulk update on a queryset') + raise ValueError("Cannot bulk update on a queryset") log.debug( - 'Updating items for %s (conflict_resolution %s, message_disposition: %s, send_meeting_invitations: %s)', + "Updating items for %s (conflict_resolution %s, message_disposition: %s, send_meeting_invitations: %s)", self, conflict_resolution, message_disposition, send_meeting_invitations_or_cancellations, ) - return list(self._consume_item_service(service_cls=UpdateItem, items=items, chunk_size=chunk_size, kwargs=dict( - conflict_resolution=conflict_resolution, - message_disposition=message_disposition, - send_meeting_invitations_or_cancellations=send_meeting_invitations_or_cancellations, - suppress_read_receipts=suppress_read_receipts, - ))) - - def bulk_delete(self, ids, delete_type=HARD_DELETE, send_meeting_cancellations=SEND_TO_NONE, - affected_task_occurrences=ALL_OCCURRENCES, suppress_read_receipts=True, chunk_size=None): + return list( + self._consume_item_service( + service_cls=UpdateItem, + items=items, + chunk_size=chunk_size, + kwargs=dict( + conflict_resolution=conflict_resolution, + message_disposition=message_disposition, + send_meeting_invitations_or_cancellations=send_meeting_invitations_or_cancellations, + suppress_read_receipts=suppress_read_receipts, + ), + ) + ) + + def bulk_delete( + self, + ids, + delete_type=HARD_DELETE, + send_meeting_cancellations=SEND_TO_NONE, + affected_task_occurrences=ALL_OCCURRENCES, + suppress_read_receipts=True, + chunk_size=None, + ): """Bulk delete items. :param ids: an iterable of either (id, changekey) tuples or Item objects. @@ -500,19 +587,24 @@

Module exchangelib.account

:return: a list of either True or exception instances, in the same order as the input """ log.debug( - 'Deleting items for %s (delete_type: %s, send_meeting_invitations: %s, affected_task_occurrences: %s)', + "Deleting items for %s (delete_type: %s, send_meeting_invitations: %s, affected_task_occurrences: %s)", self, delete_type, send_meeting_cancellations, affected_task_occurrences, ) return list( - self._consume_item_service(service_cls=DeleteItem, items=ids, chunk_size=chunk_size, kwargs=dict( - delete_type=delete_type, - send_meeting_cancellations=send_meeting_cancellations, - affected_task_occurrences=affected_task_occurrences, - suppress_read_receipts=suppress_read_receipts, - )) + self._consume_item_service( + service_cls=DeleteItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + delete_type=delete_type, + send_meeting_cancellations=send_meeting_cancellations, + affected_task_occurrences=affected_task_occurrences, + suppress_read_receipts=suppress_read_receipts, + ), + ) ) def bulk_send(self, ids, save_copy=True, copy_to_folder=None, chunk_size=None): @@ -530,9 +622,14 @@

Module exchangelib.account

if save_copy and not copy_to_folder: copy_to_folder = self.sent # 'Sent' is default EWS behaviour return list( - self._consume_item_service(service_cls=SendItem, items=ids, chunk_size=chunk_size, kwargs=dict( - saved_item_folder=copy_to_folder, - )) + self._consume_item_service( + service_cls=SendItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + saved_item_folder=copy_to_folder, + ), + ) ) def bulk_copy(self, ids, to_folder, chunk_size=None): @@ -544,9 +641,16 @@

Module exchangelib.account

:return: Status for each send operation, in the same order as the input """ - return list(self._consume_item_service(service_cls=CopyItem, items=ids, chunk_size=chunk_size, kwargs=dict( - to_folder=to_folder, - ))) + return list( + self._consume_item_service( + service_cls=CopyItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + to_folder=to_folder, + ), + ) + ) def bulk_move(self, ids, to_folder, chunk_size=None): """Move items to another folder. @@ -558,9 +662,16 @@

Module exchangelib.account

:return: The new IDs of the moved items, in the same order as the input. If 'to_folder' is a public folder or a folder in a different mailbox, an empty list is returned. """ - return list(self._consume_item_service(service_cls=MoveItem, items=ids, chunk_size=chunk_size, kwargs=dict( - to_folder=to_folder, - ))) + return list( + self._consume_item_service( + service_cls=MoveItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + to_folder=to_folder, + ), + ) + ) def bulk_archive(self, ids, to_folder, chunk_size=None): """Archive items to a folder in the archive mailbox. An archive mailbox must be enabled in order for this @@ -572,9 +683,15 @@

Module exchangelib.account

:return: A list containing True or an exception instance in stable order of the requested items """ - return list(self._consume_item_service(service_cls=ArchiveItem, items=ids, chunk_size=chunk_size, kwargs=dict( - to_folder=to_folder, - )) + return list( + self._consume_item_service( + service_cls=ArchiveItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + to_folder=to_folder, + ), + ) ) def bulk_mark_as_junk(self, ids, is_junk, move_item, chunk_size=None): @@ -588,10 +705,17 @@

Module exchangelib.account

:return: A list containing the new IDs of the moved items, if items were moved, or True, or an exception instance, in stable order of the requested items. """ - return list(self._consume_item_service(service_cls=MarkAsJunk, items=ids, chunk_size=chunk_size, kwargs=dict( - is_junk=is_junk, - move_item=move_item, - ))) + return list( + self._consume_item_service( + service_cls=MarkAsJunk, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + is_junk=is_junk, + move_item=move_item, + ), + ) + ) def fetch(self, ids, folder=None, only_fields=None, chunk_size=None): """Fetch items by ID. @@ -616,13 +740,19 @@

Module exchangelib.account

for field in only_fields: validation_folder.validate_item_field(field=field, version=self.version) # Remove ItemId and ChangeKey. We get them unconditionally - additional_fields = {f for f in validation_folder.normalize_fields(fields=only_fields) - if not f.field.is_attribute} + additional_fields = { + f for f in validation_folder.normalize_fields(fields=only_fields) if not f.field.is_attribute + } # Always use IdOnly here, because AllProperties doesn't actually get *all* properties - yield from self._consume_item_service(service_cls=GetItem, items=ids, chunk_size=chunk_size, kwargs=dict( + yield from self._consume_item_service( + service_cls=GetItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( additional_fields=additional_fields, shape=ID_ONLY, - )) + ), + ) def fetch_personas(self, ids): """Fetch personas by ID. @@ -646,7 +776,7 @@

Module exchangelib.account

return GetMailTips(protocol=self.protocol).get( sending_as=SendingAs(email_address=self.primary_smtp_address), recipients=[Mailbox(email_address=self.primary_smtp_address)], - mail_tips_requested='All', + mail_tips_requested="All", ) @property @@ -656,7 +786,7 @@

Module exchangelib.account

def __str__(self): if self.fullname: - return f'{self.primary_smtp_address} ({self.fullname})' + return f"{self.primary_smtp_address} ({self.fullname})" return self.primary_smtp_address @@ -695,8 +825,17 @@

Classes

class Account:
     """Models an Exchange server user account."""
 
-    def __init__(self, primary_smtp_address, fullname=None, access_type=None, autodiscover=False, credentials=None,
-                 config=None, locale=None, default_timezone=None):
+    def __init__(
+        self,
+        primary_smtp_address,
+        fullname=None,
+        access_type=None,
+        autodiscover=False,
+        credentials=None,
+        config=None,
+        locale=None,
+        default_timezone=None,
+    ):
         """
 
         :param primary_smtp_address: The primary email address associated with the account on the Exchange server
@@ -713,37 +852,37 @@ 

Classes

assume values to be in the provided timezone. Defaults to the timezone of the host. :return: """ - if '@' not in primary_smtp_address: + if "@" not in primary_smtp_address: raise ValueError(f"primary_smtp_address {primary_smtp_address!r} is not an email address") self.fullname = fullname # Assume delegate access if individual credentials are provided. Else, assume service user with impersonation self.access_type = access_type or (DELEGATE if credentials else IMPERSONATION) if self.access_type not in ACCESS_TYPES: - raise InvalidEnumValue('access_type', self.access_type, ACCESS_TYPES) + raise InvalidEnumValue("access_type", self.access_type, ACCESS_TYPES) try: # get_locale() might not be able to determine the locale self.locale = locale or stdlib_locale.getlocale()[0] or None except ValueError as e: # getlocale() may throw ValueError if it fails to parse the system locale - log.warning('Failed to get locale (%s)', e) + log.warning("Failed to get locale (%s)", e) self.locale = None if not isinstance(self.locale, (type(None), str)): - raise InvalidTypeError('locale', self.locale, str) + raise InvalidTypeError("locale", self.locale, str) if default_timezone: try: self.default_timezone = EWSTimeZone.from_timezone(default_timezone) except TypeError: - raise InvalidTypeError('default_timezone', default_timezone, EWSTimeZone) + raise InvalidTypeError("default_timezone", default_timezone, EWSTimeZone) else: try: self.default_timezone = EWSTimeZone.localzone() except (ValueError, UnknownTimeZone) as e: # There is no translation from local timezone name to Windows timezone name, or e failed to find the # local timezone. - log.warning('%s. Fallback to UTC', e.args[0]) + log.warning("%s. Fallback to UTC", e.args[0]) self.default_timezone = UTC if not isinstance(config, (Configuration, type(None))): - raise InvalidTypeError('config', config, Configuration) + raise InvalidTypeError("config", config, Configuration) if autodiscover: if config: auth_type, retry_policy, version = config.auth_type, config.retry_policy, config.version @@ -763,7 +902,7 @@

Classes

primary_smtp_address = self.ad_response.autodiscover_smtp_address else: if not config: - raise AttributeError('non-autodiscover requires a config') + raise AttributeError("non-autodiscover requires a config") self.ad_response = None self.protocol = Protocol(config=config) @@ -777,7 +916,7 @@

Classes

# server version up-front but delegate account requests to an older backend server. Create a new instance to # avoid changing the protocol version. self.version = self.protocol.version.copy() - log.debug('Added account: %s', self) + log.debug("Added account: %s", self) @property def primary_smtp_address(self): @@ -985,7 +1124,7 @@

Classes

# We accept generators, so it's not always convenient for caller to know up-front if 'ids' is empty. Allow # empty 'ids' and return early. return - kwargs['items'] = items + kwargs["items"] = items yield from service_cls(account=self, chunk_size=chunk_size).call(**kwargs) def export(self, items, chunk_size=None): @@ -996,9 +1135,7 @@

Classes

:return: A list of strings, the exported representation of the object """ - return list( - self._consume_item_service(service_cls=ExportItems, items=items, chunk_size=chunk_size, kwargs={}) - ) + return list(self._consume_item_service(service_cls=ExportItems, items=items, chunk_size=chunk_size, kwargs={})) def upload(self, data, chunk_size=None): """Upload objects retrieved from an export to the given folders. @@ -1020,12 +1157,11 @@

Classes

-> [("idA", "changekey"), ("idB", "changekey"), ("idC", "changekey")] """ items = ((f, (None, False, d) if isinstance(d, str) else d) for f, d in data) - return list( - self._consume_item_service(service_cls=UploadItems, items=items, chunk_size=chunk_size, kwargs={}) - ) + return list(self._consume_item_service(service_cls=UploadItems, items=items, chunk_size=chunk_size, kwargs={})) - def bulk_create(self, folder, items, message_disposition=SAVE_ONLY, send_meeting_invitations=SEND_TO_NONE, - chunk_size=None): + def bulk_create( + self, folder, items, message_disposition=SAVE_ONLY, send_meeting_invitations=SEND_TO_NONE, chunk_size=None + ): """Create new items in 'folder'. :param folder: the folder to create the items in @@ -1042,23 +1178,36 @@

Classes

""" if isinstance(items, QuerySet): # bulk_create() on a queryset does not make sense because it returns items that have already been created - raise ValueError('Cannot bulk create items from a QuerySet') + raise ValueError("Cannot bulk create items from a QuerySet") log.debug( - 'Adding items for %s (folder %s, message_disposition: %s, send_meeting_invitations: %s)', + "Adding items for %s (folder %s, message_disposition: %s, send_meeting_invitations: %s)", self, folder, message_disposition, send_meeting_invitations, ) - return list(self._consume_item_service(service_cls=CreateItem, items=items, chunk_size=chunk_size, kwargs=dict( - folder=folder, - message_disposition=message_disposition, - send_meeting_invitations=send_meeting_invitations, - ))) - - def bulk_update(self, items, conflict_resolution=AUTO_RESOLVE, message_disposition=SAVE_ONLY, - send_meeting_invitations_or_cancellations=SEND_TO_NONE, suppress_read_receipts=True, - chunk_size=None): + return list( + self._consume_item_service( + service_cls=CreateItem, + items=items, + chunk_size=chunk_size, + kwargs=dict( + folder=folder, + message_disposition=message_disposition, + send_meeting_invitations=send_meeting_invitations, + ), + ) + ) + + def bulk_update( + self, + items, + conflict_resolution=AUTO_RESOLVE, + message_disposition=SAVE_ONLY, + send_meeting_invitations_or_cancellations=SEND_TO_NONE, + suppress_read_receipts=True, + chunk_size=None, + ): """Bulk update existing items. :param items: a list of (Item, fieldnames) tuples, where 'Item' is an Item object, and 'fieldnames' is a list @@ -1078,23 +1227,37 @@

Classes

# fact, it could be dangerous if the queryset contains an '.only()'. This would wipe out certain fields # entirely. if isinstance(items, QuerySet): - raise ValueError('Cannot bulk update on a queryset') + raise ValueError("Cannot bulk update on a queryset") log.debug( - 'Updating items for %s (conflict_resolution %s, message_disposition: %s, send_meeting_invitations: %s)', + "Updating items for %s (conflict_resolution %s, message_disposition: %s, send_meeting_invitations: %s)", self, conflict_resolution, message_disposition, send_meeting_invitations_or_cancellations, ) - return list(self._consume_item_service(service_cls=UpdateItem, items=items, chunk_size=chunk_size, kwargs=dict( - conflict_resolution=conflict_resolution, - message_disposition=message_disposition, - send_meeting_invitations_or_cancellations=send_meeting_invitations_or_cancellations, - suppress_read_receipts=suppress_read_receipts, - ))) - - def bulk_delete(self, ids, delete_type=HARD_DELETE, send_meeting_cancellations=SEND_TO_NONE, - affected_task_occurrences=ALL_OCCURRENCES, suppress_read_receipts=True, chunk_size=None): + return list( + self._consume_item_service( + service_cls=UpdateItem, + items=items, + chunk_size=chunk_size, + kwargs=dict( + conflict_resolution=conflict_resolution, + message_disposition=message_disposition, + send_meeting_invitations_or_cancellations=send_meeting_invitations_or_cancellations, + suppress_read_receipts=suppress_read_receipts, + ), + ) + ) + + def bulk_delete( + self, + ids, + delete_type=HARD_DELETE, + send_meeting_cancellations=SEND_TO_NONE, + affected_task_occurrences=ALL_OCCURRENCES, + suppress_read_receipts=True, + chunk_size=None, + ): """Bulk delete items. :param ids: an iterable of either (id, changekey) tuples or Item objects. @@ -1110,19 +1273,24 @@

Classes

:return: a list of either True or exception instances, in the same order as the input """ log.debug( - 'Deleting items for %s (delete_type: %s, send_meeting_invitations: %s, affected_task_occurrences: %s)', + "Deleting items for %s (delete_type: %s, send_meeting_invitations: %s, affected_task_occurrences: %s)", self, delete_type, send_meeting_cancellations, affected_task_occurrences, ) return list( - self._consume_item_service(service_cls=DeleteItem, items=ids, chunk_size=chunk_size, kwargs=dict( - delete_type=delete_type, - send_meeting_cancellations=send_meeting_cancellations, - affected_task_occurrences=affected_task_occurrences, - suppress_read_receipts=suppress_read_receipts, - )) + self._consume_item_service( + service_cls=DeleteItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + delete_type=delete_type, + send_meeting_cancellations=send_meeting_cancellations, + affected_task_occurrences=affected_task_occurrences, + suppress_read_receipts=suppress_read_receipts, + ), + ) ) def bulk_send(self, ids, save_copy=True, copy_to_folder=None, chunk_size=None): @@ -1140,9 +1308,14 @@

Classes

if save_copy and not copy_to_folder: copy_to_folder = self.sent # 'Sent' is default EWS behaviour return list( - self._consume_item_service(service_cls=SendItem, items=ids, chunk_size=chunk_size, kwargs=dict( - saved_item_folder=copy_to_folder, - )) + self._consume_item_service( + service_cls=SendItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + saved_item_folder=copy_to_folder, + ), + ) ) def bulk_copy(self, ids, to_folder, chunk_size=None): @@ -1154,9 +1327,16 @@

Classes

:return: Status for each send operation, in the same order as the input """ - return list(self._consume_item_service(service_cls=CopyItem, items=ids, chunk_size=chunk_size, kwargs=dict( - to_folder=to_folder, - ))) + return list( + self._consume_item_service( + service_cls=CopyItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + to_folder=to_folder, + ), + ) + ) def bulk_move(self, ids, to_folder, chunk_size=None): """Move items to another folder. @@ -1168,9 +1348,16 @@

Classes

:return: The new IDs of the moved items, in the same order as the input. If 'to_folder' is a public folder or a folder in a different mailbox, an empty list is returned. """ - return list(self._consume_item_service(service_cls=MoveItem, items=ids, chunk_size=chunk_size, kwargs=dict( - to_folder=to_folder, - ))) + return list( + self._consume_item_service( + service_cls=MoveItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + to_folder=to_folder, + ), + ) + ) def bulk_archive(self, ids, to_folder, chunk_size=None): """Archive items to a folder in the archive mailbox. An archive mailbox must be enabled in order for this @@ -1182,9 +1369,15 @@

Classes

:return: A list containing True or an exception instance in stable order of the requested items """ - return list(self._consume_item_service(service_cls=ArchiveItem, items=ids, chunk_size=chunk_size, kwargs=dict( - to_folder=to_folder, - )) + return list( + self._consume_item_service( + service_cls=ArchiveItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + to_folder=to_folder, + ), + ) ) def bulk_mark_as_junk(self, ids, is_junk, move_item, chunk_size=None): @@ -1198,10 +1391,17 @@

Classes

:return: A list containing the new IDs of the moved items, if items were moved, or True, or an exception instance, in stable order of the requested items. """ - return list(self._consume_item_service(service_cls=MarkAsJunk, items=ids, chunk_size=chunk_size, kwargs=dict( - is_junk=is_junk, - move_item=move_item, - ))) + return list( + self._consume_item_service( + service_cls=MarkAsJunk, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + is_junk=is_junk, + move_item=move_item, + ), + ) + ) def fetch(self, ids, folder=None, only_fields=None, chunk_size=None): """Fetch items by ID. @@ -1226,13 +1426,19 @@

Classes

for field in only_fields: validation_folder.validate_item_field(field=field, version=self.version) # Remove ItemId and ChangeKey. We get them unconditionally - additional_fields = {f for f in validation_folder.normalize_fields(fields=only_fields) - if not f.field.is_attribute} + additional_fields = { + f for f in validation_folder.normalize_fields(fields=only_fields) if not f.field.is_attribute + } # Always use IdOnly here, because AllProperties doesn't actually get *all* properties - yield from self._consume_item_service(service_cls=GetItem, items=ids, chunk_size=chunk_size, kwargs=dict( + yield from self._consume_item_service( + service_cls=GetItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( additional_fields=additional_fields, shape=ID_ONLY, - )) + ), + ) def fetch_personas(self, ids): """Fetch personas by ID. @@ -1256,7 +1462,7 @@

Classes

return GetMailTips(protocol=self.protocol).get( sending_as=SendingAs(email_address=self.primary_smtp_address), recipients=[Mailbox(email_address=self.primary_smtp_address)], - mail_tips_requested='All', + mail_tips_requested="All", ) @property @@ -1266,7 +1472,7 @@

Classes

def __str__(self): if self.fullname: - return f'{self.primary_smtp_address} ({self.fullname})' + return f"{self.primary_smtp_address} ({self.fullname})" return self.primary_smtp_address

Instance variables

@@ -1792,7 +1998,7 @@

Instance variables

return GetMailTips(protocol=self.protocol).get( sending_as=SendingAs(email_address=self.primary_smtp_address), recipients=[Mailbox(email_address=self.primary_smtp_address)], - mail_tips_requested='All', + mail_tips_requested="All", ) @@ -2336,9 +2542,15 @@

Methods

:return: A list containing True or an exception instance in stable order of the requested items """ - return list(self._consume_item_service(service_cls=ArchiveItem, items=ids, chunk_size=chunk_size, kwargs=dict( - to_folder=to_folder, - )) + return list( + self._consume_item_service( + service_cls=ArchiveItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + to_folder=to_folder, + ), + ) ) @@ -2364,9 +2576,16 @@

Methods

:return: Status for each send operation, in the same order as the input """ - return list(self._consume_item_service(service_cls=CopyItem, items=ids, chunk_size=chunk_size, kwargs=dict( - to_folder=to_folder, - ))) + return list( + self._consume_item_service( + service_cls=CopyItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + to_folder=to_folder, + ), + ) + )
@@ -2388,8 +2607,9 @@

Methods

Expand source code -
def bulk_create(self, folder, items, message_disposition=SAVE_ONLY, send_meeting_invitations=SEND_TO_NONE,
-                chunk_size=None):
+
def bulk_create(
+    self, folder, items, message_disposition=SAVE_ONLY, send_meeting_invitations=SEND_TO_NONE, chunk_size=None
+):
     """Create new items in 'folder'.
 
     :param folder: the folder to create the items in
@@ -2406,19 +2626,26 @@ 

Methods

""" if isinstance(items, QuerySet): # bulk_create() on a queryset does not make sense because it returns items that have already been created - raise ValueError('Cannot bulk create items from a QuerySet') + raise ValueError("Cannot bulk create items from a QuerySet") log.debug( - 'Adding items for %s (folder %s, message_disposition: %s, send_meeting_invitations: %s)', + "Adding items for %s (folder %s, message_disposition: %s, send_meeting_invitations: %s)", self, folder, message_disposition, send_meeting_invitations, ) - return list(self._consume_item_service(service_cls=CreateItem, items=items, chunk_size=chunk_size, kwargs=dict( - folder=folder, - message_disposition=message_disposition, - send_meeting_invitations=send_meeting_invitations, - )))
+ return list( + self._consume_item_service( + service_cls=CreateItem, + items=items, + chunk_size=chunk_size, + kwargs=dict( + folder=folder, + message_disposition=message_disposition, + send_meeting_invitations=send_meeting_invitations, + ), + ) + )
@@ -2440,8 +2667,15 @@

Methods

Expand source code -
def bulk_delete(self, ids, delete_type=HARD_DELETE, send_meeting_cancellations=SEND_TO_NONE,
-                affected_task_occurrences=ALL_OCCURRENCES, suppress_read_receipts=True, chunk_size=None):
+
def bulk_delete(
+    self,
+    ids,
+    delete_type=HARD_DELETE,
+    send_meeting_cancellations=SEND_TO_NONE,
+    affected_task_occurrences=ALL_OCCURRENCES,
+    suppress_read_receipts=True,
+    chunk_size=None,
+):
     """Bulk delete items.
 
     :param ids: an iterable of either (id, changekey) tuples or Item objects.
@@ -2457,19 +2691,24 @@ 

Methods

:return: a list of either True or exception instances, in the same order as the input """ log.debug( - 'Deleting items for %s (delete_type: %s, send_meeting_invitations: %s, affected_task_occurrences: %s)', + "Deleting items for %s (delete_type: %s, send_meeting_invitations: %s, affected_task_occurrences: %s)", self, delete_type, send_meeting_cancellations, affected_task_occurrences, ) return list( - self._consume_item_service(service_cls=DeleteItem, items=ids, chunk_size=chunk_size, kwargs=dict( - delete_type=delete_type, - send_meeting_cancellations=send_meeting_cancellations, - affected_task_occurrences=affected_task_occurrences, - suppress_read_receipts=suppress_read_receipts, - )) + self._consume_item_service( + service_cls=DeleteItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + delete_type=delete_type, + send_meeting_cancellations=send_meeting_cancellations, + affected_task_occurrences=affected_task_occurrences, + suppress_read_receipts=suppress_read_receipts, + ), + ) )
@@ -2499,10 +2738,17 @@

Methods

:return: A list containing the new IDs of the moved items, if items were moved, or True, or an exception instance, in stable order of the requested items. """ - return list(self._consume_item_service(service_cls=MarkAsJunk, items=ids, chunk_size=chunk_size, kwargs=dict( - is_junk=is_junk, - move_item=move_item, - )))
+ return list( + self._consume_item_service( + service_cls=MarkAsJunk, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + is_junk=is_junk, + move_item=move_item, + ), + ) + )
@@ -2529,9 +2775,16 @@

Methods

:return: The new IDs of the moved items, in the same order as the input. If 'to_folder' is a public folder or a folder in a different mailbox, an empty list is returned. """ - return list(self._consume_item_service(service_cls=MoveItem, items=ids, chunk_size=chunk_size, kwargs=dict( - to_folder=to_folder, - )))
+ return list( + self._consume_item_service( + service_cls=MoveItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + to_folder=to_folder, + ), + ) + )
@@ -2563,9 +2816,14 @@

Methods

if save_copy and not copy_to_folder: copy_to_folder = self.sent # 'Sent' is default EWS behaviour return list( - self._consume_item_service(service_cls=SendItem, items=ids, chunk_size=chunk_size, kwargs=dict( - saved_item_folder=copy_to_folder, - )) + self._consume_item_service( + service_cls=SendItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( + saved_item_folder=copy_to_folder, + ), + ) )
@@ -2589,9 +2847,15 @@

Methods

Expand source code -
def bulk_update(self, items, conflict_resolution=AUTO_RESOLVE, message_disposition=SAVE_ONLY,
-                send_meeting_invitations_or_cancellations=SEND_TO_NONE, suppress_read_receipts=True,
-                chunk_size=None):
+
def bulk_update(
+    self,
+    items,
+    conflict_resolution=AUTO_RESOLVE,
+    message_disposition=SAVE_ONLY,
+    send_meeting_invitations_or_cancellations=SEND_TO_NONE,
+    suppress_read_receipts=True,
+    chunk_size=None,
+):
     """Bulk update existing items.
 
     :param items: a list of (Item, fieldnames) tuples, where 'Item' is an Item object, and 'fieldnames' is a list
@@ -2611,20 +2875,27 @@ 

Methods

# fact, it could be dangerous if the queryset contains an '.only()'. This would wipe out certain fields # entirely. if isinstance(items, QuerySet): - raise ValueError('Cannot bulk update on a queryset') + raise ValueError("Cannot bulk update on a queryset") log.debug( - 'Updating items for %s (conflict_resolution %s, message_disposition: %s, send_meeting_invitations: %s)', + "Updating items for %s (conflict_resolution %s, message_disposition: %s, send_meeting_invitations: %s)", self, conflict_resolution, message_disposition, send_meeting_invitations_or_cancellations, ) - return list(self._consume_item_service(service_cls=UpdateItem, items=items, chunk_size=chunk_size, kwargs=dict( - conflict_resolution=conflict_resolution, - message_disposition=message_disposition, - send_meeting_invitations_or_cancellations=send_meeting_invitations_or_cancellations, - suppress_read_receipts=suppress_read_receipts, - )))
+ return list( + self._consume_item_service( + service_cls=UpdateItem, + items=items, + chunk_size=chunk_size, + kwargs=dict( + conflict_resolution=conflict_resolution, + message_disposition=message_disposition, + send_meeting_invitations_or_cancellations=send_meeting_invitations_or_cancellations, + suppress_read_receipts=suppress_read_receipts, + ), + ) + )
@@ -2647,9 +2918,7 @@

Methods

:return: A list of strings, the exported representation of the object """ - return list( - self._consume_item_service(service_cls=ExportItems, items=items, chunk_size=chunk_size, kwargs={}) - )
+ return list(self._consume_item_service(service_cls=ExportItems, items=items, chunk_size=chunk_size, kwargs={}))
@@ -2689,13 +2958,19 @@

Methods

for field in only_fields: validation_folder.validate_item_field(field=field, version=self.version) # Remove ItemId and ChangeKey. We get them unconditionally - additional_fields = {f for f in validation_folder.normalize_fields(fields=only_fields) - if not f.field.is_attribute} + additional_fields = { + f for f in validation_folder.normalize_fields(fields=only_fields) if not f.field.is_attribute + } # Always use IdOnly here, because AllProperties doesn't actually get *all* properties - yield from self._consume_item_service(service_cls=GetItem, items=ids, chunk_size=chunk_size, kwargs=dict( + yield from self._consume_item_service( + service_cls=GetItem, + items=ids, + chunk_size=chunk_size, + kwargs=dict( additional_fields=additional_fields, shape=ID_ONLY, - ))
+ ), + )
@@ -2768,9 +3043,7 @@

Methods

-> [("idA", "changekey"), ("idB", "changekey"), ("idC", "changekey")] """ items = ((f, (None, False, d) if isinstance(d, str) else d) for f, d in data) - return list( - self._consume_item_service(service_cls=UploadItems, items=items, chunk_size=chunk_size, kwargs={}) - )
+ return list(self._consume_item_service(service_cls=UploadItems, items=items, chunk_size=chunk_size, kwargs={})) diff --git a/docs/exchangelib/attachments.html b/docs/exchangelib/attachments.html index ee303c78..4f0fe14d 100644 --- a/docs/exchangelib/attachments.html +++ b/docs/exchangelib/attachments.html @@ -31,8 +31,18 @@

Module exchangelib.attachments

import mimetypes from .errors import InvalidTypeError -from .fields import BooleanField, TextField, IntegerField, URIField, DateTimeField, EWSElementField, Base64Field, \ - ItemField, IdField, FieldPath +from .fields import ( + Base64Field, + BooleanField, + DateTimeField, + EWSElementField, + FieldPath, + IdField, + IntegerField, + ItemField, + TextField, + URIField, +) from .properties import EWSElement, EWSMeta log = logging.getLogger(__name__) @@ -44,11 +54,11 @@

Module exchangelib.attachments

MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/attachmentid """ - ELEMENT_NAME = 'AttachmentId' + ELEMENT_NAME = "AttachmentId" - ID_ATTR = 'Id' - ROOT_ID_ATTR = 'RootItemId' - ROOT_CHANGEKEY_ATTR = 'RootItemChangeKey' + ID_ATTR = "Id" + ROOT_ID_ATTR = "RootItemId" + ROOT_CHANGEKEY_ATTR = "RootItemChangeKey" id = IdField(field_uri=ID_ATTR, is_required=True) root_id = IdField(field_uri=ROOT_ID_ATTR) @@ -65,35 +75,37 @@

Module exchangelib.attachments

"""Base class for FileAttachment and ItemAttachment.""" attachment_id = EWSElementField(value_cls=AttachmentId) - name = TextField(field_uri='Name') - content_type = TextField(field_uri='ContentType') - content_id = TextField(field_uri='ContentId') - content_location = URIField(field_uri='ContentLocation') - size = IntegerField(field_uri='Size', is_read_only=True) # Attachment size in bytes - last_modified_time = DateTimeField(field_uri='LastModifiedTime') - is_inline = BooleanField(field_uri='IsInline') + name = TextField(field_uri="Name") + content_type = TextField(field_uri="ContentType") + content_id = TextField(field_uri="ContentId") + content_location = URIField(field_uri="ContentLocation") + size = IntegerField(field_uri="Size", is_read_only=True) # Attachment size in bytes + last_modified_time = DateTimeField(field_uri="LastModifiedTime") + is_inline = BooleanField(field_uri="IsInline") - __slots__ = 'parent_item', + __slots__ = ("parent_item",) def __init__(self, **kwargs): - self.parent_item = kwargs.pop('parent_item', None) + self.parent_item = kwargs.pop("parent_item", None) super().__init__(**kwargs) def clean(self, version=None): from .items import Item + if self.parent_item is not None and not isinstance(self.parent_item, Item): - raise InvalidTypeError('parent_item', self.parent_item, Item) + raise InvalidTypeError("parent_item", self.parent_item, Item) if self.content_type is None and self.name is not None: - self.content_type = mimetypes.guess_type(self.name)[0] or 'application/octet-stream' + self.content_type = mimetypes.guess_type(self.name)[0] or "application/octet-stream" super().clean(version=version) def attach(self): from .services import CreateAttachment + # Adds this attachment to an item and updates the changekey of the parent item if self.attachment_id: - raise ValueError('This attachment has already been created') + raise ValueError("This attachment has already been created") if not self.parent_item or not self.parent_item.account: - raise ValueError(f'Parent item {self.parent_item} must have an account') + raise ValueError(f"Parent item {self.parent_item} must have an account") item = CreateAttachment(account=self.parent_item.account).get(parent_item=self.parent_item, items=[self]) attachment_id = item.attachment_id self.parent_item.changekey = attachment_id.root_changekey @@ -104,11 +116,12 @@

Module exchangelib.attachments

def detach(self): from .services import DeleteAttachment + # Deletes an attachment remotely and updates the changekey of the parent item if not self.attachment_id: - raise ValueError('This attachment has not been created') + raise ValueError("This attachment has not been created") if not self.parent_item or not self.parent_item.account: - raise ValueError(f'Parent item {self.parent_item} must have an account') + raise ValueError(f"Parent item {self.parent_item} must have an account") DeleteAttachment(account=self.parent_item.account).get(items=[self.attachment_id]) self.parent_item = None self.attachment_id = None @@ -117,27 +130,27 @@

Module exchangelib.attachments

if self.attachment_id: return hash(self.attachment_id) # Be careful to avoid recursion on the back-reference to the parent item - return hash(tuple(getattr(self, f) for f in self._slots_keys if f != 'parent_item')) + return hash(tuple(getattr(self, f) for f in self._slots_keys if f != "parent_item")) def __repr__(self): - args_str = ', '.join( - f'{f.name}={getattr(self, f.name)!r}' for f in self.FIELDS if f.name not in ('_item', '_content') + args_str = ", ".join( + f"{f.name}={getattr(self, f.name)!r}" for f in self.FIELDS if f.name not in ("_item", "_content") ) - return f'{self.__class__.__name__}({args_str})' + return f"{self.__class__.__name__}({args_str})" class FileAttachment(Attachment): """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/fileattachment""" - ELEMENT_NAME = 'FileAttachment' + ELEMENT_NAME = "FileAttachment" - is_contact_photo = BooleanField(field_uri='IsContactPhoto') - _content = Base64Field(field_uri='Content') + is_contact_photo = BooleanField(field_uri="IsContactPhoto") + _content = Base64Field(field_uri="Content") - __slots__ = '_fp', + __slots__ = ("_fp",) def __init__(self, **kwargs): - kwargs['_content'] = kwargs.pop('content', None) + kwargs["_content"] = kwargs.pop("content", None) super().__init__(**kwargs) self._fp = None @@ -152,7 +165,7 @@

Module exchangelib.attachments

# Create a file-like object for the attachment content. We try hard to reduce memory consumption so we never # store the full attachment content in-memory. if not self.parent_item or not self.parent_item.account: - raise ValueError(f'{self.__class__.__name__} must have an account') + raise ValueError(f"{self.__class__.__name__} must have an account") self._fp = FileAttachmentIO(attachment=self) @property @@ -173,13 +186,13 @@

Module exchangelib.attachments

def content(self, value): """Replace the attachment content.""" if not isinstance(value, bytes): - raise InvalidTypeError('value', value, bytes) + raise InvalidTypeError("value", value, bytes) self._content = value @classmethod def from_xml(cls, elem, account): kwargs = {f.name: f.from_xml(elem=elem, account=account) for f in cls.FIELDS} - kwargs['content'] = kwargs.pop('_content') + kwargs["content"] = kwargs.pop("_content") cls._clear(elem) return cls(**kwargs) @@ -190,7 +203,7 @@

Module exchangelib.attachments

def __getstate__(self): # The fp does not need to be pickled state = {k: getattr(self, k) for k in self._slots_keys} - del state['_fp'] + del state["_fp"] return state def __setstate__(self, state): @@ -203,30 +216,34 @@

Module exchangelib.attachments

class ItemAttachment(Attachment): """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/itemattachment""" - ELEMENT_NAME = 'ItemAttachment' + ELEMENT_NAME = "ItemAttachment" - _item = ItemField(field_uri='Item') + _item = ItemField(field_uri="Item") def __init__(self, **kwargs): - kwargs['_item'] = kwargs.pop('item', None) + kwargs["_item"] = kwargs.pop("item", None) super().__init__(**kwargs) @property def item(self): from .folders import BaseFolder from .services import GetAttachment + if self.attachment_id is None: return self._item if self._item is not None: return self._item # We have an ID to the data but still haven't called GetAttachment to get the actual data. Do that now. if not self.parent_item or not self.parent_item.account: - raise ValueError(f'{self.__class__.__name__} must have an account') + raise ValueError(f"{self.__class__.__name__} must have an account") additional_fields = { FieldPath(field=f) for f in BaseFolder.allowed_item_fields(version=self.parent_item.account.version) } attachment = GetAttachment(account=self.parent_item.account).get( - items=[self.attachment_id], include_mime_content=True, body_type=None, filter_html_content=None, + items=[self.attachment_id], + include_mime_content=True, + body_type=None, + filter_html_content=None, additional_fields=additional_fields, ) self._item = attachment.item @@ -235,14 +252,15 @@

Module exchangelib.attachments

@item.setter def item(self, value): from .items import Item + if not isinstance(value, Item): - raise InvalidTypeError('value', value, Item) + raise InvalidTypeError("value", value, Item) self._item = value @classmethod def from_xml(cls, elem, account): kwargs = {f.name: f.from_xml(elem=elem, account=account) for f in cls.FIELDS} - kwargs['item'] = kwargs.pop('_item') + kwargs["item"] = kwargs.pop("_item") cls._clear(elem) return cls(**kwargs) @@ -270,11 +288,12 @@

Module exchangelib.attachments

return 0 else: output, self._overflow = chunk[:buf_size], chunk[buf_size:] - b[:len(output)] = output + b[: len(output)] = output return len(output) def __enter__(self): from .services import GetAttachment + self._stream = GetAttachment(account=self._attachment.parent_item.account).stream_file_content( attachment_id=self._attachment.attachment_id ) @@ -309,35 +328,37 @@

Classes

"""Base class for FileAttachment and ItemAttachment.""" attachment_id = EWSElementField(value_cls=AttachmentId) - name = TextField(field_uri='Name') - content_type = TextField(field_uri='ContentType') - content_id = TextField(field_uri='ContentId') - content_location = URIField(field_uri='ContentLocation') - size = IntegerField(field_uri='Size', is_read_only=True) # Attachment size in bytes - last_modified_time = DateTimeField(field_uri='LastModifiedTime') - is_inline = BooleanField(field_uri='IsInline') + name = TextField(field_uri="Name") + content_type = TextField(field_uri="ContentType") + content_id = TextField(field_uri="ContentId") + content_location = URIField(field_uri="ContentLocation") + size = IntegerField(field_uri="Size", is_read_only=True) # Attachment size in bytes + last_modified_time = DateTimeField(field_uri="LastModifiedTime") + is_inline = BooleanField(field_uri="IsInline") - __slots__ = 'parent_item', + __slots__ = ("parent_item",) def __init__(self, **kwargs): - self.parent_item = kwargs.pop('parent_item', None) + self.parent_item = kwargs.pop("parent_item", None) super().__init__(**kwargs) def clean(self, version=None): from .items import Item + if self.parent_item is not None and not isinstance(self.parent_item, Item): - raise InvalidTypeError('parent_item', self.parent_item, Item) + raise InvalidTypeError("parent_item", self.parent_item, Item) if self.content_type is None and self.name is not None: - self.content_type = mimetypes.guess_type(self.name)[0] or 'application/octet-stream' + self.content_type = mimetypes.guess_type(self.name)[0] or "application/octet-stream" super().clean(version=version) def attach(self): from .services import CreateAttachment + # Adds this attachment to an item and updates the changekey of the parent item if self.attachment_id: - raise ValueError('This attachment has already been created') + raise ValueError("This attachment has already been created") if not self.parent_item or not self.parent_item.account: - raise ValueError(f'Parent item {self.parent_item} must have an account') + raise ValueError(f"Parent item {self.parent_item} must have an account") item = CreateAttachment(account=self.parent_item.account).get(parent_item=self.parent_item, items=[self]) attachment_id = item.attachment_id self.parent_item.changekey = attachment_id.root_changekey @@ -348,11 +369,12 @@

Classes

def detach(self): from .services import DeleteAttachment + # Deletes an attachment remotely and updates the changekey of the parent item if not self.attachment_id: - raise ValueError('This attachment has not been created') + raise ValueError("This attachment has not been created") if not self.parent_item or not self.parent_item.account: - raise ValueError(f'Parent item {self.parent_item} must have an account') + raise ValueError(f"Parent item {self.parent_item} must have an account") DeleteAttachment(account=self.parent_item.account).get(items=[self.attachment_id]) self.parent_item = None self.attachment_id = None @@ -361,13 +383,13 @@

Classes

if self.attachment_id: return hash(self.attachment_id) # Be careful to avoid recursion on the back-reference to the parent item - return hash(tuple(getattr(self, f) for f in self._slots_keys if f != 'parent_item')) + return hash(tuple(getattr(self, f) for f in self._slots_keys if f != "parent_item")) def __repr__(self): - args_str = ', '.join( - f'{f.name}={getattr(self, f.name)!r}' for f in self.FIELDS if f.name not in ('_item', '_content') + args_str = ", ".join( + f"{f.name}={getattr(self, f.name)!r}" for f in self.FIELDS if f.name not in ("_item", "_content") ) - return f'{self.__class__.__name__}({args_str})' + return f"{self.__class__.__name__}({args_str})"

Ancestors