exchangelib.items API documentation

# Module `exchangelib.items`

Expand source code

    from .base import RegisterMixIn, BulkCreateResult, MESSAGE_DISPOSITION_CHOICES, SAVE_ONLY, SEND_ONLY, \
        ID_ONLY, DEFAULT, ALL_PROPERTIES, SEND_MEETING_INVITATIONS_CHOICES, SEND_TO_NONE, SEND_ONLY_TO_ALL, \
        SEND_MEETING_INVITATIONS_AND_CANCELLATIONS_CHOICES, SEND_ONLY_TO_CHANGED, SEND_TO_CHANGED_AND_SAVE_COPY, \
        SEND_MEETING_CANCELLATIONS_CHOICES, AFFECTED_TASK_OCCURRENCES_CHOICES, ALL_OCCURRENCIES, \
        SPECIFIED_OCCURRENCE_ONLY, CONFLICT_RESOLUTION_CHOICES, NEVER_OVERWRITE, AUTO_RESOLVE, ALWAYS_OVERWRITE, \
        DELETE_TYPE_CHOICES, HARD_DELETE, SOFT_DELETE, MOVE_TO_DELETED_ITEMS, SEND_TO_ALL_AND_SAVE_COPY, \
        SEND_AND_SAVE_COPY, SHAPE_CHOICES
    from .calendar_item import CalendarItem, AcceptItem, TentativelyAcceptItem, DeclineItem, CancelCalendarItem, \
        MeetingMessage, MeetingRequest, MeetingResponse, MeetingCancellation, CONFERENCE_TYPES
    from .contact import Contact, Persona, DistributionList
    from .item import BaseItem, Item
    from .message import Message, ReplyToItem, ReplyAllToItem, ForwardItem
    from .post import PostItem, PostReplyItem
    from .task import Task

    # Traversal enums
    SHALLOW = 'Shallow'
    SOFT_DELETED = 'SoftDeleted'
    ASSOCIATED = 'Associated'
    ITEM_TRAVERSAL_CHOICES = (SHALLOW, SOFT_DELETED, ASSOCIATED)

    # Contacts search (ResolveNames) scope enums
    ACTIVE_DIRECTORY = 'ActiveDirectory'
    ACTIVE_DIRECTORY_CONTACTS = 'ActiveDirectoryContacts'
    CONTACTS = 'Contacts'
    CONTACTS_ACTIVE_DIRECTORY = 'ContactsActiveDirectory'
    SEARCH_SCOPE_CHOICES = (ACTIVE_DIRECTORY, ACTIVE_DIRECTORY_CONTACTS, CONTACTS, CONTACTS_ACTIVE_DIRECTORY)

    ITEM_CLASSES = (CalendarItem, Contact, DistributionList, Item, Message, MeetingMessage, MeetingRequest,
                    MeetingResponse, MeetingCancellation, PostItem, Task)

    __all__ = [
        'RegisterMixIn', 'MESSAGE_DISPOSITION_CHOICES', 'SAVE_ONLY', 'SEND_ONLY', 'SEND_AND_SAVE_COPY',
        'CalendarItem', 'AcceptItem', 'TentativelyAcceptItem', 'DeclineItem', 'CancelCalendarItem',
        'MeetingRequest', 'MeetingResponse', 'MeetingCancellation', 'CONFERENCE_TYPES',
        'Contact', 'Persona', 'DistributionList',
        'SEND_MEETING_INVITATIONS_CHOICES', 'SEND_TO_NONE', 'SEND_ONLY_TO_ALL', 'SEND_TO_ALL_AND_SAVE_COPY',
        'SEND_MEETING_INVITATIONS_AND_CANCELLATIONS_CHOICES', 'SEND_ONLY_TO_CHANGED', 'SEND_TO_CHANGED_AND_SAVE_COPY',
        'SEND_MEETING_CANCELLATIONS_CHOICES', 'AFFECTED_TASK_OCCURRENCES_CHOICES', 'ALL_OCCURRENCIES',
        'SPECIFIED_OCCURRENCE_ONLY', 'CONFLICT_RESOLUTION_CHOICES', 'NEVER_OVERWRITE', 'AUTO_RESOLVE', 'ALWAYS_OVERWRITE',
        'DELETE_TYPE_CHOICES', 'HARD_DELETE', 'SOFT_DELETE', 'MOVE_TO_DELETED_ITEMS', 'BaseItem', 'Item',
        'BulkCreateResult',
        'Message', 'ReplyToItem', 'ReplyAllToItem', 'ForwardItem',
        'PostItem', 'PostReplyItem',
        'Task',
        'ITEM_TRAVERSAL_CHOICES', 'SHALLOW', 'SOFT_DELETED', 'ASSOCIATED',
        'SHAPE_CHOICES', 'ID_ONLY', 'DEFAULT', 'ALL_PROPERTIES',
        'SEARCH_SCOPE_CHOICES', 'ACTIVE_DIRECTORY', 'ACTIVE_DIRECTORY_CONTACTS', 'CONTACTS', 'CONTACTS_ACTIVE_DIRECTORY',
        'ITEM_CLASSES',
    ]

## Sub-modules

`exchangelib.items.base`
`exchangelib.items.calendar_item`
`exchangelib.items.contact`
`exchangelib.items.item`
`exchangelib.items.message`
`exchangelib.items.post`
`exchangelib.items.task`

## Classes

`class AcceptItem (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/acceptitem

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class AcceptItem(BaseMeetingReplyItem):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/acceptitem"""

          ELEMENT_NAME = 'AcceptItem'

  ### Ancestors

  - BaseMeetingReplyItem
  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`

  ### Inherited members

  - `BaseMeetingReplyItem`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `deregister`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class BaseItem (**kwargs)`

  Base class for all other classes that implement EWS items.

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class BaseItem(RegisterMixIn, metaclass=EWSMeta):
          """Base class for all other classes that implement EWS items."""

          ID_ELEMENT_CLS = ItemId
          _id = IdElementField(field_uri='item:ItemId', value_cls=ID_ELEMENT_CLS)

          __slots__ = 'account', 'folder'

          def __init__(self, **kwargs):
              """Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

              :param kwargs:
                  'account' is optional but allows calling 'send()' and 'delete()'
                  'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set,
                      we use folder.account.
              """
              from ..folders import BaseFolder
              from ..account import Account
              self.account = kwargs.pop('account', None)
              if self.account is not None and not isinstance(self.account, Account):
                  raise ValueError("'account' %r must be an Account instance" % self.account)
              self.folder = kwargs.pop('folder', None)
              if self.folder is not None:
                  if not isinstance(self.folder, BaseFolder):
                      raise ValueError("'folder' %r must be a Folder instance" % self.folder)
                  if self.folder.account is not None:
                      if self.account is not None:
                          # Make sure the account from kwargs matches the folder account
                          if self.account != self.folder.account:
                              raise ValueError("'account' does not match 'folder.account'")
                      self.account = self.folder.account
              super().__init__(**kwargs)

          @classmethod
          def from_xml(cls, elem, account):
              item = super().from_xml(elem=elem, account=account)
              item.account = account
              return item

  ### Ancestors

  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement

  ### Subclasses

  - BulkCreateResult
  - BaseMeetingReplyItem
  - Item

  ### Class variables

  `var FIELDS`
  `var ID_ELEMENT_CLS`

    'id' and 'changekey' are UUIDs generated by Exchange.

    MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/itemid

  ### Static methods

  `def from_xml(elem, account)`
    Expand source code

        @classmethod
        def from_xml(cls, elem, account):
            item = super().from_xml(elem=elem, account=account)
            item.account = account
            return item

  ### Instance variables

  `var account`

    Return an attribute of instance, which is of type owner.

  `var folder`

    Return an attribute of instance, which is of type owner.

  ### Inherited members

  - `RegisterMixIn`:
    - `add_field`
    - `deregister`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class BulkCreateResult (**kwargs)`

  A dummy class to store return values from a CreateItem service call.

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class BulkCreateResult(BaseItem):
          """A dummy class to store return values from a CreateItem service call."""

          attachments = AttachmentField(field_uri='item:Attachments')  # ItemAttachment or FileAttachment

          def __init__(self, **kwargs):
              super().__init__(**kwargs)
              if self.attachments is None:
                  self.attachments = []

  ### Ancestors

  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement

  ### Class variables

  `var FIELDS`

  ### Instance variables

  `var attachments`

  ### Inherited members

  - `BaseItem`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `deregister`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class CalendarItem (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/calendaritem

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class CalendarItem(Item, AcceptDeclineMixIn):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/calendaritem"""

          ELEMENT_NAME = 'CalendarItem'

          uid = TextField(field_uri='calendar:UID', is_required_after_save=True, is_searchable=False)
          recurrence_id = DateTimeField(field_uri='calendar:RecurrenceId', is_read_only=True)
          start = DateOrDateTimeField(field_uri='calendar:Start', is_required=True)
          end = DateOrDateTimeField(field_uri='calendar:End', is_required=True)
          original_start = DateTimeField(field_uri='calendar:OriginalStart', is_read_only=True)
          is_all_day = BooleanField(field_uri='calendar:IsAllDayEvent', is_required=True, default=False)
          legacy_free_busy_status = FreeBusyStatusField(field_uri='calendar:LegacyFreeBusyStatus', is_required=True,
                                                        default='Busy')
          location = TextField(field_uri='calendar:Location')
          when = TextField(field_uri='calendar:When')
          is_meeting = BooleanField(field_uri='calendar:IsMeeting', is_read_only=True)
          is_cancelled = BooleanField(field_uri='calendar:IsCancelled', is_read_only=True)
          is_recurring = BooleanField(field_uri='calendar:IsRecurring', is_read_only=True)
          meeting_request_was_sent = BooleanField(field_uri='calendar:MeetingRequestWasSent', is_read_only=True)
          is_response_requested = BooleanField(field_uri='calendar:IsResponseRequested', default=None,
                                               is_required_after_save=True, is_searchable=False)
          type = ChoiceField(field_uri='calendar:CalendarItemType', choices={Choice(c) for c in CALENDAR_ITEM_CHOICES},
                             is_read_only=True)
          my_response_type = ChoiceField(field_uri='calendar:MyResponseType', choices={
                  Choice(c) for c in Attendee.RESPONSE_TYPES
          }, is_read_only=True)
          organizer = MailboxField(field_uri='calendar:Organizer', is_read_only=True)
          required_attendees = AttendeesField(field_uri='calendar:RequiredAttendees', is_searchable=False)
          optional_attendees = AttendeesField(field_uri='calendar:OptionalAttendees', is_searchable=False)
          resources = AttendeesField(field_uri='calendar:Resources', is_searchable=False)
          conflicting_meeting_count = IntegerField(field_uri='calendar:ConflictingMeetingCount', is_read_only=True)
          adjacent_meeting_count = IntegerField(field_uri='calendar:AdjacentMeetingCount', is_read_only=True)
          conflicting_meetings = EWSElementListField(field_uri='calendar:ConflictingMeetings', value_cls='CalendarItem',
                                                     namespace=Item.NAMESPACE, is_read_only=True)
          adjacent_meetings = EWSElementListField(field_uri='calendar:AdjacentMeetings', value_cls='CalendarItem',
                                                  namespace=Item.NAMESPACE, is_read_only=True)
          duration = CharField(field_uri='calendar:Duration', is_read_only=True)
          appointment_reply_time = DateTimeField(field_uri='calendar:AppointmentReplyTime', is_read_only=True)
          appointment_sequence_number = IntegerField(field_uri='calendar:AppointmentSequenceNumber', is_read_only=True)
          appointment_state = AppointmentStateField(field_uri='calendar:AppointmentState', is_read_only=True)
          recurrence = RecurrenceField(field_uri='calendar:Recurrence', is_searchable=False)
          first_occurrence = OccurrenceField(field_uri='calendar:FirstOccurrence', value_cls=FirstOccurrence,
                                             is_read_only=True)
          last_occurrence = OccurrenceField(field_uri='calendar:LastOccurrence', value_cls=LastOccurrence,
                                            is_read_only=True)
          modified_occurrences = OccurrenceListField(field_uri='calendar:ModifiedOccurrences', value_cls=Occurrence,
                                                     is_read_only=True)
          deleted_occurrences = OccurrenceListField(field_uri='calendar:DeletedOccurrences', value_cls=DeletedOccurrence,
                                                    is_read_only=True)
          _meeting_timezone = TimeZoneField(field_uri='calendar:MeetingTimeZone', deprecated_from=EXCHANGE_2010,
                                            is_searchable=False)
          _start_timezone = TimeZoneField(field_uri='calendar:StartTimeZone', supported_from=EXCHANGE_2010,
                                          is_searchable=False)
          _end_timezone = TimeZoneField(field_uri='calendar:EndTimeZone', supported_from=EXCHANGE_2010,
                                        is_searchable=False)
          conference_type = EnumAsIntField(field_uri='calendar:ConferenceType', enum=CONFERENCE_TYPES, min=0,
                                           default=None, is_required_after_save=True)
          allow_new_time_proposal = BooleanField(field_uri='calendar:AllowNewTimeProposal', default=None,
                                                 is_required_after_save=True, is_searchable=False)
          is_online_meeting = BooleanField(field_uri='calendar:IsOnlineMeeting', default=None,
                                           is_read_only=True)
          meeting_workspace_url = URIField(field_uri='calendar:MeetingWorkspaceUrl')
          net_show_url = URIField(field_uri='calendar:NetShowUrl')

          def occurrence(self, index):
              """Get an occurrence of a recurring master by index. No query is sent to the server to actually fetch the item.
              Call refresh() on the item do do so.

              Only call this method on a recurring master.

              :param index: The index, which is 1-based

              :return The occurrence
              """
              return self.__class__(
                  account=self.account,
                  folder=self.folder,
                  _id=OccurrenceItemId(id=self.id, changekey=self.changekey, instance_index=index),
              )

          def recurring_master(self):
              """Get the recurring master of an occurrence. No query is sent to the server to actually fetch the item.
              Call refresh() on the item do do so.

              Only call this method on an occurrence of a recurring master.

              :return: The master occurrence
              """
              return self.__class__(
                  account=self.account,
                  folder=self.folder,
                  _id=RecurringMasterItemId(id=self.id, changekey=self.changekey),
              )

          @classmethod
          def timezone_fields(cls):
              return [f for f in cls.FIELDS if isinstance(f, TimeZoneField)]

          def clean_timezone_fields(self, version):
              # Sets proper values on the timezone fields if they are not already set
              if self.start is None:
                  start_tz = None
              elif type(self.start) in (EWSDate, datetime.date):
                  start_tz = self.account.default_timezone
              else:
                  start_tz = self.start.tzinfo
              if self.end is None:
                  end_tz = None
              elif type(self.end) in (EWSDate, datetime.date):
                  end_tz = self.account.default_timezone
              else:
                  end_tz = self.end.tzinfo
              if version.build < EXCHANGE_2010:
                  if self._meeting_timezone is None:
                      self._meeting_timezone = start_tz
                  self._start_timezone = None
                  self._end_timezone = None
              else:
                  self._meeting_timezone = None
                  if self._start_timezone is None:
                      self._start_timezone = start_tz
                  if self._end_timezone is None:
                      self._end_timezone = end_tz

          def clean(self, version=None):
              super().clean(version=version)
              if self.start and self.end and self.end < self.start:
                  raise ValueError("'end' must be greater than 'start' (%s -> %s)" % (self.start, self.end))
              if version:
                  self.clean_timezone_fields(version=version)

          def cancel(self, **kwargs):
              return CancelCalendarItem(
                  account=self.account,
                  reference_item_id=ReferenceItemId(id=self.id, changekey=self.changekey),
                  **kwargs
              ).send()

          def _update_fieldnames(self):
              update_fields = super()._update_fieldnames()
              if self.type == OCCURRENCE:
                  # Some CalendarItem fields cannot be updated when the item is an occurrence. The values are empty when we
                  # receive them so would have been updated because they are set to None.
                  update_fields.remove('recurrence')
                  update_fields.remove('uid')
              return update_fields

          @classmethod
          def from_xml(cls, elem, account):
              item = super().from_xml(elem=elem, account=account)
              # EWS returns the start and end values as a datetime regardless of the is_all_day status. Convert to date if
              # applicable.
              if not item.is_all_day:
                  return item
              for field_name in ('start', 'end'):
                  val = getattr(item, field_name)
                  if val is None:
                      continue
                  # Return just the date part of the value. Subtract 1 day from the date if this is the end field. This is
                  # the inverse of what we do in .to_xml(). Convert to the local timezone before getting the date.
                  if field_name == 'end':
                      val -= datetime.timedelta(days=1)
                  tz = getattr(item, '_%s_timezone' % field_name)
                  setattr(item, field_name, val.astimezone(tz).date())
              return item

          def tz_field_for_field_name(self, field_name):
              meeting_tz_field, start_tz_field, end_tz_field = CalendarItem.timezone_fields()
              if self.account.version.build < EXCHANGE_2010:
                  return meeting_tz_field
              if field_name == 'start':
                  return start_tz_field
              if field_name == 'end':
                  return end_tz_field
              raise ValueError('Unsupported field_name')

          def date_to_datetime(self, field_name):
              # EWS always expects a datetime. If we have a date value, then convert it to datetime in the local
              # timezone. Additionally, if this the end field, add 1 day to the date. We could add 12 hours to both
              # start and end values and let EWS apply its logic, but that seems hacky.
              value = getattr(self, field_name)
              tz = getattr(self, self.tz_field_for_field_name(field_name).name)
              value = EWSDateTime.combine(value, datetime.time(0, 0)).replace(tzinfo=tz)
              if field_name == 'end':
                  value += datetime.timedelta(days=1)
              return value

          def to_xml(self, version):
              # EWS has some special logic related to all-day start and end values. Non-midnight start values are pushed to
              # the previous midnight. Non-midnight end values are pushed to the following midnight. Midnight in this context
              # refers to midnight in the local timezone. See
              #
              # https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-create-all-day-events-by-using-ews-in-exchange
              #
              elem = super().to_xml(version=version)
              if not self.is_all_day:
                  return elem
              for field_name in ('start', 'end'):
                  value = getattr(self, field_name)
                  if value is None:
                      continue
                  if type(value) in (EWSDate, datetime.date):
                      # EWS always expects a datetime
                      value = self.date_to_datetime(field_name=field_name)
                      # We already generated an XML element for this field, but it contains a plain date at this point, which
                      # is invalid. Replace the value.
                      field = self.get_field_by_fieldname(field_name)
                      set_xml_value(elem=elem.find(field.response_tag()), value=value, version=version)
              return elem

  ### Ancestors

  - Item
  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement
  - AcceptDeclineMixIn

  ### Class variables

  `var ELEMENT_NAME`
  `var FIELDS`

  ### Static methods

  `def from_xml(elem, account)`
    Expand source code

        @classmethod
        def from_xml(cls, elem, account):
            item = super().from_xml(elem=elem, account=account)
            # EWS returns the start and end values as a datetime regardless of the is_all_day status. Convert to date if
            # applicable.
            if not item.is_all_day:
                return item
            for field_name in ('start', 'end'):
                val = getattr(item, field_name)
                if val is None:
                    continue
                # Return just the date part of the value. Subtract 1 day from the date if this is the end field. This is
                # the inverse of what we do in .to_xml(). Convert to the local timezone before getting the date.
                if field_name == 'end':
                    val -= datetime.timedelta(days=1)
                tz = getattr(item, '_%s_timezone' % field_name)
                setattr(item, field_name, val.astimezone(tz).date())
            return item

  `def timezone_fields()`
    Expand source code

        @classmethod
        def timezone_fields(cls):
            return [f for f in cls.FIELDS if isinstance(f, TimeZoneField)]

  ### Instance variables

  `var adjacent_meeting_count`
  `var adjacent_meetings`
  `var allow_new_time_proposal`
  `var appointment_reply_time`
  `var appointment_sequence_number`
  `var appointment_state`
  `var conference_type`
  `var conflicting_meeting_count`
  `var conflicting_meetings`
  `var deleted_occurrences`
  `var duration`
  `var end`
  `var first_occurrence`
  `var is_all_day`
  `var is_cancelled`
  `var is_meeting`
  `var is_online_meeting`
  `var is_recurring`
  `var is_response_requested`
  `var last_occurrence`
  `var legacy_free_busy_status`
  `var location`
  `var meeting_request_was_sent`
  `var meeting_workspace_url`
  `var modified_occurrences`
  `var my_response_type`
  `var net_show_url`
  `var optional_attendees`
  `var organizer`
  `var original_start`
  `var recurrence`
  `var recurrence_id`
  `var required_attendees`
  `var resources`
  `var start`
  `var type`
  `var uid`
  `var when`

  ### Methods

  `def cancel(self, **kwargs)`
    Expand source code

        def cancel(self, **kwargs):
            return CancelCalendarItem(
                account=self.account,
                reference_item_id=ReferenceItemId(id=self.id, changekey=self.changekey),
                **kwargs
            ).send()

  `def clean(self, version=None)`
    Expand source code

        def clean(self, version=None):
            super().clean(version=version)
            if self.start and self.end and self.end < self.start:
                raise ValueError("'end' must be greater than 'start' (%s -> %s)" % (self.start, self.end))
            if version:
                self.clean_timezone_fields(version=version)

  `def clean_timezone_fields(self, version)`
    Expand source code

        def clean_timezone_fields(self, version):
            # Sets proper values on the timezone fields if they are not already set
            if self.start is None:
                start_tz = None
            elif type(self.start) in (EWSDate, datetime.date):
                start_tz = self.account.default_timezone
            else:
                start_tz = self.start.tzinfo
            if self.end is None:
                end_tz = None
            elif type(self.end) in (EWSDate, datetime.date):
                end_tz = self.account.default_timezone
            else:
                end_tz = self.end.tzinfo
            if version.build < EXCHANGE_2010:
                if self._meeting_timezone is None:
                    self._meeting_timezone = start_tz
                self._start_timezone = None
                self._end_timezone = None
            else:
                self._meeting_timezone = None
                if self._start_timezone is None:
                    self._start_timezone = start_tz
                if self._end_timezone is None:
                    self._end_timezone = end_tz

  `def date_to_datetime(self, field_name)`
    Expand source code

        def date_to_datetime(self, field_name):
            # EWS always expects a datetime. If we have a date value, then convert it to datetime in the local
            # timezone. Additionally, if this the end field, add 1 day to the date. We could add 12 hours to both
            # start and end values and let EWS apply its logic, but that seems hacky.
            value = getattr(self, field_name)
            tz = getattr(self, self.tz_field_for_field_name(field_name).name)
            value = EWSDateTime.combine(value, datetime.time(0, 0)).replace(tzinfo=tz)
            if field_name == 'end':
                value += datetime.timedelta(days=1)
            return value

  `def occurrence(self, index)`

    Get an occurrence of a recurring master by index. No query is sent to the server to actually fetch the item. Call refresh() on the item do do so.

    Only call this method on a recurring master.

    :param index: The index, which is 1-based

    :return The occurrence

    Expand source code

        def occurrence(self, index):
            """Get an occurrence of a recurring master by index. No query is sent to the server to actually fetch the item.
            Call refresh() on the item do do so.

            Only call this method on a recurring master.

            :param index: The index, which is 1-based

            :return The occurrence
            """
            return self.__class__(
                account=self.account,
                folder=self.folder,
                _id=OccurrenceItemId(id=self.id, changekey=self.changekey, instance_index=index),
            )

  `def recurring_master(self)`

    Get the recurring master of an occurrence. No query is sent to the server to actually fetch the item. Call refresh() on the item do do so.

    Only call this method on an occurrence of a recurring master.

    :return: The master occurrence

    Expand source code

        def recurring_master(self):
            """Get the recurring master of an occurrence. No query is sent to the server to actually fetch the item.
            Call refresh() on the item do do so.

            Only call this method on an occurrence of a recurring master.

            :return: The master occurrence
            """
            return self.__class__(
                account=self.account,
                folder=self.folder,
                _id=RecurringMasterItemId(id=self.id, changekey=self.changekey),
            )

  `def to_xml(self, version)`
    Expand source code

        def to_xml(self, version):
            # EWS has some special logic related to all-day start and end values. Non-midnight start values are pushed to
            # the previous midnight. Non-midnight end values are pushed to the following midnight. Midnight in this context
            # refers to midnight in the local timezone. See
            #
            # https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-create-all-day-events-by-using-ews-in-exchange
            #
            elem = super().to_xml(version=version)
            if not self.is_all_day:
                return elem
            for field_name in ('start', 'end'):
                value = getattr(self, field_name)
                if value is None:
                    continue
                if type(value) in (EWSDate, datetime.date):
                    # EWS always expects a datetime
                    value = self.date_to_datetime(field_name=field_name)
                    # We already generated an XML element for this field, but it contains a plain date at this point, which
                    # is invalid. Replace the value.
                    field = self.get_field_by_fieldname(field_name)
                    set_xml_value(elem=elem.find(field.response_tag()), value=value, version=version)
            return elem

  `def tz_field_for_field_name(self, field_name)`
    Expand source code

        def tz_field_for_field_name(self, field_name):
            meeting_tz_field, start_tz_field, end_tz_field = CalendarItem.timezone_fields()
            if self.account.version.build < EXCHANGE_2010:
                return meeting_tz_field
            if field_name == 'start':
                return start_tz_field
            if field_name == 'end':
                return end_tz_field
            raise ValueError('Unsupported field_name')

  ### Inherited members

  - `Item`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `attach`
    - `deregister`
    - `detach`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class CancelCalendarItem (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/cancelcalendaritem

  Expand source code

      class CancelCalendarItem(BaseReplyItem):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/cancelcalendaritem"""

          ELEMENT_NAME = 'CancelCalendarItem'
          author_idx = BaseReplyItem.FIELDS.index_by_name('author')
          FIELDS = BaseReplyItem.FIELDS[:author_idx] + BaseReplyItem.FIELDS[author_idx + 1:]

  ### Ancestors

  - BaseReplyItem
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`
  `var FIELDS`
  `var author_idx`

  ### Inherited members

  - `BaseReplyItem`:
    - `account`
    - `add_field`
    - `remove_field`
    - `save`
    - `supported_fields`
    - `validate_field`
`class Contact (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/contact

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class Contact(Item):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/contact"""

          ELEMENT_NAME = 'Contact'

          file_as = TextField(field_uri='contacts:FileAs')
          file_as_mapping = ChoiceField(field_uri='contacts:FileAsMapping', choices={
              Choice('None'), Choice('LastCommaFirst'), Choice('FirstSpaceLast'), Choice('Company'),
              Choice('LastCommaFirstCompany'), Choice('CompanyLastFirst'), Choice('LastFirst'),
              Choice('LastFirstCompany'), Choice('CompanyLastCommaFirst'), Choice('LastFirstSuffix'),
              Choice('LastSpaceFirstCompany'), Choice('CompanyLastSpaceFirst'), Choice('LastSpaceFirst'),
              Choice('DisplayName'), Choice('FirstName'), Choice('LastFirstMiddleSuffix'), Choice('LastName'),
              Choice('Empty'),
          })
          display_name = TextField(field_uri='contacts:DisplayName', is_required=True)
          given_name = CharField(field_uri='contacts:GivenName')
          initials = TextField(field_uri='contacts:Initials')
          middle_name = CharField(field_uri='contacts:MiddleName')
          nickname = TextField(field_uri='contacts:Nickname')
          complete_name = EWSElementField(field_uri='contacts:CompleteName', value_cls=CompleteName, is_read_only=True)
          company_name = TextField(field_uri='contacts:CompanyName')
          email_addresses = EmailAddressesField(field_uri='contacts:EmailAddress')
          physical_addresses = PhysicalAddressField(field_uri='contacts:PhysicalAddress')
          phone_numbers = PhoneNumberField(field_uri='contacts:PhoneNumber')
          assistant_name = TextField(field_uri='contacts:AssistantName')
          birthday = DateTimeBackedDateField(field_uri='contacts:Birthday', default_time=datetime.time(11, 59))
          business_homepage = URIField(field_uri='contacts:BusinessHomePage')
          children = TextListField(field_uri='contacts:Children')
          companies = TextListField(field_uri='contacts:Companies', is_searchable=False)
          contact_source = ChoiceField(field_uri='contacts:ContactSource', choices={
              Choice('Store'), Choice('ActiveDirectory')
          }, is_read_only=True)
          department = TextField(field_uri='contacts:Department')
          generation = TextField(field_uri='contacts:Generation')
          im_addresses = CharField(field_uri='contacts:ImAddresses', is_read_only=True)
          job_title = TextField(field_uri='contacts:JobTitle')
          manager = TextField(field_uri='contacts:Manager')
          mileage = TextField(field_uri='contacts:Mileage')
          office = TextField(field_uri='contacts:OfficeLocation')
          postal_address_index = ChoiceField(field_uri='contacts:PostalAddressIndex', choices={
              Choice('Business'), Choice('Home'), Choice('Other'), Choice('None')
          }, default='None', is_required_after_save=True)
          profession = TextField(field_uri='contacts:Profession')
          spouse_name = TextField(field_uri='contacts:SpouseName')
          surname = CharField(field_uri='contacts:Surname')
          wedding_anniversary = DateTimeBackedDateField(field_uri='contacts:WeddingAnniversary',
                                                        default_time=datetime.time(11, 59))
          has_picture = BooleanField(field_uri='contacts:HasPicture', supported_from=EXCHANGE_2010, is_read_only=True)
          phonetic_full_name = TextField(field_uri='contacts:PhoneticFullName', supported_from=EXCHANGE_2010_SP2,
                                         is_read_only=True)
          phonetic_first_name = TextField(field_uri='contacts:PhoneticFirstName', supported_from=EXCHANGE_2010_SP2,
                                          is_read_only=True)
          phonetic_last_name = TextField(field_uri='contacts:PhoneticLastName', supported_from=EXCHANGE_2010_SP2,
                                         is_read_only=True)
          email_alias = EmailAddressField(field_uri='contacts:Alias', is_read_only=True,
                                          supported_from=EXCHANGE_2010_SP2)
          # 'notes' is documented in MSDN but apparently unused. Writing to it raises ErrorInvalidPropertyRequest. OWA
          # put entries into the 'notes' form field into the 'body' field.
          notes = CharField(field_uri='contacts:Notes', supported_from=EXCHANGE_2010_SP2, is_read_only=True)
          # 'photo' is documented in MSDN but apparently unused. Writing to it raises ErrorInvalidPropertyRequest. OWA
          # adds photos as FileAttachments on the contact item (with 'is_contact_photo=True'), which automatically flips
          # the 'has_picture' field.
          photo = Base64Field(field_uri='contacts:Photo', supported_from=EXCHANGE_2010_SP2, is_read_only=True)
          user_smime_certificate = Base64Field(field_uri='contacts:UserSMIMECertificate', supported_from=EXCHANGE_2010_SP2,
                                               is_read_only=True)
          ms_exchange_certificate = Base64Field(field_uri='contacts:MSExchangeCertificate', supported_from=EXCHANGE_2010_SP2,
                                                is_read_only=True)
          directory_id = TextField(field_uri='contacts:DirectoryId', supported_from=EXCHANGE_2010_SP2, is_read_only=True)
          manager_mailbox = MailboxField(field_uri='contacts:ManagerMailbox', supported_from=EXCHANGE_2010_SP2,
                                         is_read_only=True)
          direct_reports = MailboxListField(field_uri='contacts:DirectReports', supported_from=EXCHANGE_2010_SP2,
                                            is_read_only=True)

  ### Ancestors

  - Item
  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`
  `var FIELDS`

  ### Instance variables

  `var assistant_name`
  `var birthday`
  `var business_homepage`
  `var children`
  `var companies`
  `var company_name`
  `var complete_name`
  `var contact_source`
  `var department`
  `var direct_reports`
  `var directory_id`
  `var display_name`
  `var email_addresses`
  `var email_alias`
  `var file_as`
  `var file_as_mapping`
  `var generation`
  `var given_name`
  `var has_picture`
  `var im_addresses`
  `var initials`
  `var job_title`
  `var manager`
  `var manager_mailbox`
  `var middle_name`
  `var mileage`
  `var ms_exchange_certificate`
  `var nickname`
  `var notes`
  `var office`
  `var phone_numbers`
  `var phonetic_first_name`
  `var phonetic_full_name`
  `var phonetic_last_name`
  `var photo`
  `var physical_addresses`
  `var postal_address_index`
  `var profession`
  `var spouse_name`
  `var surname`
  `var user_smime_certificate`
  `var wedding_anniversary`

  ### Inherited members

  - `Item`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `attach`
    - `deregister`
    - `detach`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class DeclineItem (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/declineitem

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class DeclineItem(BaseMeetingReplyItem):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/declineitem"""

          ELEMENT_NAME = 'DeclineItem'

  ### Ancestors

  - BaseMeetingReplyItem
  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`

  ### Inherited members

  - `BaseMeetingReplyItem`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `deregister`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class DistributionList (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/distributionlist

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class DistributionList(Item):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/distributionlist"""

          ELEMENT_NAME = 'DistributionList'

          display_name = CharField(field_uri='contacts:DisplayName', is_required=True)
          file_as = CharField(field_uri='contacts:FileAs', is_read_only=True)
          contact_source = ChoiceField(field_uri='contacts:ContactSource', choices={
              Choice('Store'), Choice('ActiveDirectory')
          }, is_read_only=True)
          members = MemberListField(field_uri='distributionlist:Members')

  ### Ancestors

  - Item
  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`
  `var FIELDS`

  ### Instance variables

  `var contact_source`
  `var display_name`
  `var file_as`
  `var members`

  ### Inherited members

  - `Item`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `attach`
    - `deregister`
    - `detach`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class ForwardItem (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/forwarditem

  Expand source code

      class ForwardItem(BaseReplyItem):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/forwarditem"""

          ELEMENT_NAME = 'ForwardItem'

  ### Ancestors

  - BaseReplyItem
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`

  ### Inherited members

  - `BaseReplyItem`:
    - `account`
    - `add_field`
    - `remove_field`
    - `save`
    - `supported_fields`
    - `validate_field`
`class Item (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/item

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class Item(BaseItem):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/item"""

          ELEMENT_NAME = 'Item'

          mime_content = MimeContentField(field_uri='item:MimeContent', is_read_only_after_send=True)
          _id = BaseItem.FIELDS['_id']
          parent_folder_id = EWSElementField(field_uri='item:ParentFolderId', value_cls=ParentFolderId, is_read_only=True)
          item_class = CharField(field_uri='item:ItemClass', is_read_only=True)
          subject = CharField(field_uri='item:Subject')
          sensitivity = ChoiceField(field_uri='item:Sensitivity', choices={
              Choice('Normal'), Choice('Personal'), Choice('Private'), Choice('Confidential')
          }, is_required=True, default='Normal')
          text_body = TextField(field_uri='item:TextBody', is_read_only=True, supported_from=EXCHANGE_2013)
          body = BodyField(field_uri='item:Body')  # Accepts and returns Body or HTMLBody instances
          attachments = AttachmentField(field_uri='item:Attachments')  # ItemAttachment or FileAttachment
          datetime_received = DateTimeField(field_uri='item:DateTimeReceived', is_read_only=True)
          size = IntegerField(field_uri='item:Size', is_read_only=True)  # Item size in bytes
          categories = CharListField(field_uri='item:Categories')
          importance = ChoiceField(field_uri='item:Importance', choices={
              Choice('Low'), Choice('Normal'), Choice('High')
          }, is_required=True, default='Normal')
          in_reply_to = TextField(field_uri='item:InReplyTo')
          is_submitted = BooleanField(field_uri='item:IsSubmitted', is_read_only=True)
          is_draft = BooleanField(field_uri='item:IsDraft', is_read_only=True)
          is_from_me = BooleanField(field_uri='item:IsFromMe', is_read_only=True)
          is_resend = BooleanField(field_uri='item:IsResend', is_read_only=True)
          is_unmodified = BooleanField(field_uri='item:IsUnmodified', is_read_only=True)
          headers = MessageHeaderField(field_uri='item:InternetMessageHeaders', is_read_only=True)
          datetime_sent = DateTimeField(field_uri='item:DateTimeSent', is_read_only=True)
          datetime_created = DateTimeField(field_uri='item:DateTimeCreated', is_read_only=True)
          response_objects = EWSElementField(field_uri='item:ResponseObjects', value_cls=ResponseObjects,
                                             is_read_only=True,)
          # Placeholder for ResponseObjects
          reminder_due_by = DateTimeField(field_uri='item:ReminderDueBy', is_required_after_save=True, is_searchable=False)
          reminder_is_set = BooleanField(field_uri='item:ReminderIsSet', is_required=True, default=False)
          reminder_minutes_before_start = IntegerField(field_uri='item:ReminderMinutesBeforeStart',
                                                       is_required_after_save=True, min=0, default=0)
          display_cc = TextField(field_uri='item:DisplayCc', is_read_only=True)
          display_to = TextField(field_uri='item:DisplayTo', is_read_only=True)
          has_attachments = BooleanField(field_uri='item:HasAttachments', is_read_only=True)
          # ExtendedProperty fields go here
          culture = CultureField(field_uri='item:Culture', is_required_after_save=True, is_searchable=False)
          effective_rights = EffectiveRightsField(field_uri='item:EffectiveRights', is_read_only=True)
          last_modified_name = CharField(field_uri='item:LastModifiedName', is_read_only=True)
          last_modified_time = DateTimeField(field_uri='item:LastModifiedTime', is_read_only=True)
          is_associated = BooleanField(field_uri='item:IsAssociated', is_read_only=True, supported_from=EXCHANGE_2010)
          web_client_read_form_query_string = URIField(field_uri='item:WebClientReadFormQueryString', is_read_only=True,
                                                       supported_from=EXCHANGE_2010)
          web_client_edit_form_query_string = URIField(field_uri='item:WebClientEditFormQueryString', is_read_only=True,
                                                       supported_from=EXCHANGE_2010)
          conversation_id = EWSElementField(field_uri='item:ConversationId', value_cls=ConversationId,
                                            is_read_only=True, supported_from=EXCHANGE_2010)
          unique_body = BodyField(field_uri='item:UniqueBody', is_read_only=True, supported_from=EXCHANGE_2010)

          FIELDS = Fields()

          # Used to register extended properties
          INSERT_AFTER_FIELD = 'has_attachments'

          def __init__(self, **kwargs):
              super().__init__(**kwargs)
              if self.attachments:
                  for a in self.attachments:
                      if a.parent_item:
                          if a.parent_item is not self:
                              raise ValueError("'parent_item' of attachment %s must point to this item" % a)
                      else:
                          a.parent_item = self
                      self.attach(self.attachments)
              else:
                  self.attachments = []

          def save(self, update_fields=None, conflict_resolution=AUTO_RESOLVE, send_meeting_invitations=SEND_TO_NONE):
              from .task import Task
              if self.id:
                  item_id, changekey = self._update(
                      update_fieldnames=update_fields,
                      message_disposition=SAVE_ONLY,
                      conflict_resolution=conflict_resolution,
                      send_meeting_invitations=send_meeting_invitations
                  )
                  if self.id != item_id \
                          and not isinstance(self._id, (OccurrenceItemId, RecurringMasterItemId)) \
                          and not isinstance(self, Task):
                      # When we update an item with an OccurrenceItemId as ID, EWS returns the ID of the occurrence, so
                      # the ID of this item changes.
                      #
                      # When we update certain fields on a task, the ID may change. A full description is available at
                      # https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/updateitem-operation-task
                      raise ValueError("'id' mismatch in returned update response")
                  # Don't check that changekeys are different. No-op saves will sometimes leave the changekey intact
                  self._id = self.ID_ELEMENT_CLS(item_id, changekey)
              else:
                  if update_fields:
                      raise ValueError("'update_fields' is only valid for updates")
                  tmp_attachments = None
                  if self.account and self.account.version.build < EXCHANGE_2013 and self.attachments:
                      # At least some versions prior to Exchange 2013 can't save attachments immediately. You need to first
                      # save, then attach. Store the attachment of this item temporarily and attach later.
                      tmp_attachments, self.attachments = self.attachments, []
                  item = self._create(message_disposition=SAVE_ONLY, send_meeting_invitations=send_meeting_invitations)
                  self._id = self.ID_ELEMENT_CLS(item.id, item.changekey)
                  for old_att, new_att in zip(self.attachments, item.attachments):
                      if old_att.attachment_id is not None:
                          raise ValueError("Old 'attachment_id' is not empty")
                      if new_att.attachment_id is None:
                          raise ValueError("New 'attachment_id' is empty")
                      old_att.attachment_id = new_att.attachment_id
                  if tmp_attachments:
                      # Exchange 2007 workaround. See above
                      self.attach(tmp_attachments)
              return self

          @require_account
          def _create(self, message_disposition, send_meeting_invitations):
              # Return a BulkCreateResult because we want to return the ID of both the main item *and* attachments. In send
              # and send-and-save-copy mode, the server does not return an ID, so we just return True.
              return CreateItem(account=self.account).get(
                  items=[self],
                  folder=self.folder,
                  message_disposition=message_disposition,
                  send_meeting_invitations=send_meeting_invitations,
              )

          def _update_fieldnames(self):
              from .contact import Contact, DistributionList
              # Return the list of fields we are allowed to update
              update_fieldnames = []
              for f in self.supported_fields(version=self.account.version):
                  if f.name == 'attachments':
                      # Attachments are handled separately after item creation
                      continue
                  if f.is_read_only:
                      # These cannot be changed
                      continue
                  if (f.is_required or f.is_required_after_save) and (
                          getattr(self, f.name) is None or (f.is_list and not getattr(self, f.name))
                  ):
                      # These are required and cannot be deleted
                      continue
                  if not self.is_draft and f.is_read_only_after_send:
                      # These cannot be changed when the item is no longer a draft
                      continue
                  if f.name == 'message_id' and f.is_read_only_after_send:
                      # 'message_id' doesn't support updating, no matter the draft status
                      continue
                  if f.name == 'mime_content' and isinstance(self, (Contact, DistributionList)):
                      # Contact and DistributionList don't support updating mime_content, no matter the draft status
                      continue
                  update_fieldnames.append(f.name)
              return update_fieldnames

          @require_account
          def _update(self, update_fieldnames, message_disposition, conflict_resolution, send_meeting_invitations):
              if not self.changekey:
                  raise ValueError('%s must have changekey' % self.__class__.__name__)
              if not update_fieldnames:
                  # The fields to update was not specified explicitly. Update all fields where update is possible
                  update_fieldnames = self._update_fieldnames()
              return UpdateItem(account=self.account).get(
                  items=[(self, update_fieldnames)],
                  message_disposition=message_disposition,
                  conflict_resolution=conflict_resolution,
                  send_meeting_invitations_or_cancellations=send_meeting_invitations,
                  suppress_read_receipts=True,
                  expect_result=message_disposition != SEND_AND_SAVE_COPY,
              )

          @require_id
          def refresh(self):
              # Updates the item based on fresh data from EWS
              from ..folders import Folder
              additional_fields = {
                  FieldPath(field=f) for f in Folder(root=self.account.root).allowed_item_fields(version=self.account.version)
              }
              res = GetItem(account=self.account).get(items=[self], additional_fields=additional_fields, shape=ID_ONLY)
              if self.id != res.id and not isinstance(self._id, (OccurrenceItemId, RecurringMasterItemId)):
                  # When we refresh an item with an OccurrenceItemId as ID, EWS returns the ID of the occurrence, so
                  # the ID of this item changes.
                  raise ValueError("'id' mismatch in returned update response")
              for f in self.FIELDS:
                  setattr(self, f.name, getattr(res, f.name))
              # 'parent_item' should point to 'self', not 'fresh_item'. That way, 'fresh_item' can be garbage collected.
              for a in self.attachments:
                  a.parent_item = self
              return self

          @require_id
          def copy(self, to_folder):
              # If 'to_folder' is a public folder or a folder in a different mailbox then None is returned
              return CopyItem(account=self.account).get(
                  items=[self],
                  to_folder=to_folder,
                  expect_result=None,
              )

          @require_id
          def move(self, to_folder):
              res = MoveItem(account=self.account).get(
                  items=[self],
                  to_folder=to_folder,
                  expect_result=None,
              )
              if res is None:
                  # Assume 'to_folder' is a public folder or a folder in a different mailbox
                  self._id = None
                  return
              self._id = self.ID_ELEMENT_CLS(*res)
              self.folder = to_folder

          def move_to_trash(self, send_meeting_cancellations=SEND_TO_NONE, affected_task_occurrences=ALL_OCCURRENCIES,
                            suppress_read_receipts=True):
              # Delete and move to the trash folder.
              self._delete(delete_type=MOVE_TO_DELETED_ITEMS, send_meeting_cancellations=send_meeting_cancellations,
                           affected_task_occurrences=affected_task_occurrences, suppress_read_receipts=suppress_read_receipts)
              self._id = None
              self.folder = self.account.trash

          def soft_delete(self, send_meeting_cancellations=SEND_TO_NONE, affected_task_occurrences=ALL_OCCURRENCIES,
                          suppress_read_receipts=True):
              # Delete and move to the dumpster, if it is enabled.
              self._delete(delete_type=SOFT_DELETE, send_meeting_cancellations=send_meeting_cancellations,
                           affected_task_occurrences=affected_task_occurrences, suppress_read_receipts=suppress_read_receipts)
              self._id = None
              self.folder = self.account.recoverable_items_deletions

          def delete(self, send_meeting_cancellations=SEND_TO_NONE, affected_task_occurrences=ALL_OCCURRENCIES,
                     suppress_read_receipts=True):
              # Remove the item permanently. No copies are stored anywhere.
              self._delete(delete_type=HARD_DELETE, send_meeting_cancellations=send_meeting_cancellations,
                           affected_task_occurrences=affected_task_occurrences, suppress_read_receipts=suppress_read_receipts)
              self._id, self.folder = None, None

          @require_id
          def _delete(self, delete_type, send_meeting_cancellations, affected_task_occurrences, suppress_read_receipts):
              DeleteItem(account=self.account).get(
                  items=[self],
                  delete_type=delete_type,
                  send_meeting_cancellations=send_meeting_cancellations,
                  affected_task_occurrences=affected_task_occurrences,
                  suppress_read_receipts=suppress_read_receipts,
              )

          @require_id
          def archive(self, to_folder):
              return ArchiveItem(account=self.account).get(items=[self], to_folder=to_folder, expect_result=True)

          def attach(self, attachments):
              """Add an attachment, or a list of attachments, to this item. If the item has already been saved, the
              attachments will be created on the server immediately. If the item has not yet been saved, the attachments will
              be created on the server when the item is saved.

              Adding attachments to an existing item will update the changekey of the item.

              :param attachments:
              """
              if not is_iterable(attachments, generators_allowed=True):
                  attachments = [attachments]
              for a in attachments:
                  if not a.parent_item:
                      a.parent_item = self
                  if self.id and not a.attachment_id:
                      # Already saved object. Attach the attachment server-side now
                      a.attach()
                  if a not in self.attachments:
                      self.attachments.append(a)

          def detach(self, attachments):
              """Remove an attachment, or a list of attachments, from this item. If the item has already been saved, the
              attachments will be deleted on the server immediately. If the item has not yet been saved, the attachments will
              simply not be created on the server the item is saved.

              Removing attachments from an existing item will update the changekey of the item.

              :param attachments:
              """
              if not is_iterable(attachments, generators_allowed=True):
                  attachments = [attachments]
              if attachments is self.attachments:
                  # Don't remove from the same list we are iterating
                  attachments = list(attachments)
              for a in attachments:
                  if a.parent_item is not self:
                      raise ValueError('Attachment does not belong to this item')
                  if self.id:
                      # Item is already created. Detach  the attachment server-side now
                      a.detach()
                  if a in self.attachments:
                      self.attachments.remove(a)

          @require_id
          def create_forward(self, subject, body, to_recipients, cc_recipients=None, bcc_recipients=None):
              from .message import ForwardItem
              return ForwardItem(
                  account=self.account,
                  reference_item_id=ReferenceItemId(id=self.id, changekey=self.changekey),
                  subject=subject,
                  new_body=body,
                  to_recipients=to_recipients,
                  cc_recipients=cc_recipients,
                  bcc_recipients=bcc_recipients,
              )

          def forward(self, subject, body, to_recipients, cc_recipients=None, bcc_recipients=None):
              self.create_forward(
                  subject,
                  body,
                  to_recipients,
                  cc_recipients,
                  bcc_recipients,
              ).send()

  ### Ancestors

  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement

  ### Subclasses

  - BaseMeetingItem
  - CalendarItem
  - Contact
  - DistributionList
  - Message
  - PostItem
  - PostReplyItem
  - Task

  ### Class variables

  `var ELEMENT_NAME`
  `var FIELDS`
  `var INSERT_AFTER_FIELD`

  ### Instance variables

  `var attachments`
  `var body`
  `var categories`
  `var conversation_id`
  `var culture`
  `var datetime_created`
  `var datetime_received`
  `var datetime_sent`
  `var display_cc`
  `var display_to`
  `var effective_rights`
  `var has_attachments`
  `var headers`
  `var importance`
  `var in_reply_to`
  `var is_associated`
  `var is_draft`
  `var is_from_me`
  `var is_resend`
  `var is_submitted`
  `var is_unmodified`
  `var item_class`
  `var last_modified_name`
  `var last_modified_time`
  `var mime_content`
  `var parent_folder_id`
  `var reminder_due_by`
  `var reminder_is_set`
  `var reminder_minutes_before_start`
  `var response_objects`
  `var sensitivity`
  `var size`
  `var subject`
  `var text_body`
  `var unique_body`
  `var web_client_edit_form_query_string`
  `var web_client_read_form_query_string`

  ### Methods

  `def archive(self, to_folder)`
    Expand source code

        @require_id
        def archive(self, to_folder):
            return ArchiveItem(account=self.account).get(items=[self], to_folder=to_folder, expect_result=True)

  `def attach(self, attachments)`

    Add an attachment, or a list of attachments, to this item. If the item has already been saved, the attachments will be created on the server immediately. If the item has not yet been saved, the attachments will be created on the server when the item is saved.

    Adding attachments to an existing item will update the changekey of the item.

    :param attachments:

    Expand source code

        def attach(self, attachments):
            """Add an attachment, or a list of attachments, to this item. If the item has already been saved, the
            attachments will be created on the server immediately. If the item has not yet been saved, the attachments will
            be created on the server when the item is saved.

            Adding attachments to an existing item will update the changekey of the item.

            :param attachments:
            """
            if not is_iterable(attachments, generators_allowed=True):
                attachments = [attachments]
            for a in attachments:
                if not a.parent_item:
                    a.parent_item = self
                if self.id and not a.attachment_id:
                    # Already saved object. Attach the attachment server-side now
                    a.attach()
                if a not in self.attachments:
                    self.attachments.append(a)

  `def copy(self, to_folder)`
    Expand source code

        @require_id
        def copy(self, to_folder):
            # If 'to_folder' is a public folder or a folder in a different mailbox then None is returned
            return CopyItem(account=self.account).get(
                items=[self],
                to_folder=to_folder,
                expect_result=None,
            )

  `def create_forward(self, subject, body, to_recipients, cc_recipients=None, bcc_recipients=None)`
    Expand source code

        @require_id
        def create_forward(self, subject, body, to_recipients, cc_recipients=None, bcc_recipients=None):
            from .message import ForwardItem
            return ForwardItem(
                account=self.account,
                reference_item_id=ReferenceItemId(id=self.id, changekey=self.changekey),
                subject=subject,
                new_body=body,
                to_recipients=to_recipients,
                cc_recipients=cc_recipients,
                bcc_recipients=bcc_recipients,
            )

  `def delete(self, send_meeting_cancellations='SendToNone', affected_task_occurrences='AllOccurrences', suppress_read_receipts=True)`
    Expand source code

        def delete(self, send_meeting_cancellations=SEND_TO_NONE, affected_task_occurrences=ALL_OCCURRENCIES,
                   suppress_read_receipts=True):
            # Remove the item permanently. No copies are stored anywhere.
            self._delete(delete_type=HARD_DELETE, send_meeting_cancellations=send_meeting_cancellations,
                         affected_task_occurrences=affected_task_occurrences, suppress_read_receipts=suppress_read_receipts)
            self._id, self.folder = None, None

  `def detach(self, attachments)`

    Remove an attachment, or a list of attachments, from this item. If the item has already been saved, the attachments will be deleted on the server immediately. If the item has not yet been saved, the attachments will simply not be created on the server the item is saved.

    Removing attachments from an existing item will update the changekey of the item.

    :param attachments:

    Expand source code

        def detach(self, attachments):
            """Remove an attachment, or a list of attachments, from this item. If the item has already been saved, the
            attachments will be deleted on the server immediately. If the item has not yet been saved, the attachments will
            simply not be created on the server the item is saved.

            Removing attachments from an existing item will update the changekey of the item.

            :param attachments:
            """
            if not is_iterable(attachments, generators_allowed=True):
                attachments = [attachments]
            if attachments is self.attachments:
                # Don't remove from the same list we are iterating
                attachments = list(attachments)
            for a in attachments:
                if a.parent_item is not self:
                    raise ValueError('Attachment does not belong to this item')
                if self.id:
                    # Item is already created. Detach  the attachment server-side now
                    a.detach()
                if a in self.attachments:
                    self.attachments.remove(a)

  `def forward(self, subject, body, to_recipients, cc_recipients=None, bcc_recipients=None)`
    Expand source code

        def forward(self, subject, body, to_recipients, cc_recipients=None, bcc_recipients=None):
            self.create_forward(
                subject,
                body,
                to_recipients,
                cc_recipients,
                bcc_recipients,
            ).send()

  `def move(self, to_folder)`
    Expand source code

        @require_id
        def move(self, to_folder):
            res = MoveItem(account=self.account).get(
                items=[self],
                to_folder=to_folder,
                expect_result=None,
            )
            if res is None:
                # Assume 'to_folder' is a public folder or a folder in a different mailbox
                self._id = None
                return
            self._id = self.ID_ELEMENT_CLS(*res)
            self.folder = to_folder

  `def move_to_trash(self, send_meeting_cancellations='SendToNone', affected_task_occurrences='AllOccurrences', suppress_read_receipts=True)`
    Expand source code

        def move_to_trash(self, send_meeting_cancellations=SEND_TO_NONE, affected_task_occurrences=ALL_OCCURRENCIES,
                          suppress_read_receipts=True):
            # Delete and move to the trash folder.
            self._delete(delete_type=MOVE_TO_DELETED_ITEMS, send_meeting_cancellations=send_meeting_cancellations,
                         affected_task_occurrences=affected_task_occurrences, suppress_read_receipts=suppress_read_receipts)
            self._id = None
            self.folder = self.account.trash

  `def refresh(self)`
    Expand source code

        @require_id
        def refresh(self):
            # Updates the item based on fresh data from EWS
            from ..folders import Folder
            additional_fields = {
                FieldPath(field=f) for f in Folder(root=self.account.root).allowed_item_fields(version=self.account.version)
            }
            res = GetItem(account=self.account).get(items=[self], additional_fields=additional_fields, shape=ID_ONLY)
            if self.id != res.id and not isinstance(self._id, (OccurrenceItemId, RecurringMasterItemId)):
                # When we refresh an item with an OccurrenceItemId as ID, EWS returns the ID of the occurrence, so
                # the ID of this item changes.
                raise ValueError("'id' mismatch in returned update response")
            for f in self.FIELDS:
                setattr(self, f.name, getattr(res, f.name))
            # 'parent_item' should point to 'self', not 'fresh_item'. That way, 'fresh_item' can be garbage collected.
            for a in self.attachments:
                a.parent_item = self
            return self

  `def save(self, update_fields=None, conflict_resolution='AutoResolve', send_meeting_invitations='SendToNone')`
    Expand source code

        def save(self, update_fields=None, conflict_resolution=AUTO_RESOLVE, send_meeting_invitations=SEND_TO_NONE):
            from .task import Task
            if self.id:
                item_id, changekey = self._update(
                    update_fieldnames=update_fields,
                    message_disposition=SAVE_ONLY,
                    conflict_resolution=conflict_resolution,
                    send_meeting_invitations=send_meeting_invitations
                )
                if self.id != item_id \
                        and not isinstance(self._id, (OccurrenceItemId, RecurringMasterItemId)) \
                        and not isinstance(self, Task):
                    # When we update an item with an OccurrenceItemId as ID, EWS returns the ID of the occurrence, so
                    # the ID of this item changes.
                    #
                    # When we update certain fields on a task, the ID may change. A full description is available at
                    # https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/updateitem-operation-task
                    raise ValueError("'id' mismatch in returned update response")
                # Don't check that changekeys are different. No-op saves will sometimes leave the changekey intact
                self._id = self.ID_ELEMENT_CLS(item_id, changekey)
            else:
                if update_fields:
                    raise ValueError("'update_fields' is only valid for updates")
                tmp_attachments = None
                if self.account and self.account.version.build < EXCHANGE_2013 and self.attachments:
                    # At least some versions prior to Exchange 2013 can't save attachments immediately. You need to first
                    # save, then attach. Store the attachment of this item temporarily and attach later.
                    tmp_attachments, self.attachments = self.attachments, []
                item = self._create(message_disposition=SAVE_ONLY, send_meeting_invitations=send_meeting_invitations)
                self._id = self.ID_ELEMENT_CLS(item.id, item.changekey)
                for old_att, new_att in zip(self.attachments, item.attachments):
                    if old_att.attachment_id is not None:
                        raise ValueError("Old 'attachment_id' is not empty")
                    if new_att.attachment_id is None:
                        raise ValueError("New 'attachment_id' is empty")
                    old_att.attachment_id = new_att.attachment_id
                if tmp_attachments:
                    # Exchange 2007 workaround. See above
                    self.attach(tmp_attachments)
            return self

  `def soft_delete(self, send_meeting_cancellations='SendToNone', affected_task_occurrences='AllOccurrences', suppress_read_receipts=True)`
    Expand source code

        def soft_delete(self, send_meeting_cancellations=SEND_TO_NONE, affected_task_occurrences=ALL_OCCURRENCIES,
                        suppress_read_receipts=True):
            # Delete and move to the dumpster, if it is enabled.
            self._delete(delete_type=SOFT_DELETE, send_meeting_cancellations=send_meeting_cancellations,
                         affected_task_occurrences=affected_task_occurrences, suppress_read_receipts=suppress_read_receipts)
            self._id = None
            self.folder = self.account.recoverable_items_deletions

  ### Inherited members

  - `BaseItem`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `deregister`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class MeetingCancellation (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/meetingcancellation

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class MeetingCancellation(BaseMeetingItem):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/meetingcancellation"""

          ELEMENT_NAME = 'MeetingCancellation'

  ### Ancestors

  - BaseMeetingItem
  - Item
  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`

  ### Inherited members

  - `BaseMeetingItem`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `attach`
    - `deregister`
    - `detach`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class MeetingRequest (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/meetingrequest

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class MeetingRequest(BaseMeetingItem, AcceptDeclineMixIn):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/meetingrequest"""

          ELEMENT_NAME = 'MeetingRequest'

          meeting_request_type = ChoiceField(field_uri='meetingRequest:MeetingRequestType', choices={
              Choice('FullUpdate'), Choice('InformationalUpdate'), Choice('NewMeetingRequest'), Choice('None'),
              Choice('Outdated'), Choice('PrincipalWantsCopy'), Choice('SilentUpdate')
          }, default='None')
          intended_free_busy_status = ChoiceField(field_uri='meetingRequest:IntendedFreeBusyStatus', choices={
                  Choice('Free'), Choice('Tentative'), Choice('Busy'), Choice('OOF'), Choice('NoData')
          }, is_required=True, default='Busy')

          # This element also has some fields from CalendarItem
          start_idx = CalendarItem.FIELDS.index_by_name('start')
          is_response_requested_idx = CalendarItem.FIELDS.index_by_name('is_response_requested')
          FIELDS = BaseMeetingItem.FIELDS \
              + CalendarItem.FIELDS[start_idx:is_response_requested_idx]\
              + CalendarItem.FIELDS[is_response_requested_idx + 1:]

  ### Ancestors

  - BaseMeetingItem
  - Item
  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement
  - AcceptDeclineMixIn

  ### Class variables

  `var ELEMENT_NAME`
  `var FIELDS`
  `var is_response_requested_idx`
  `var start_idx`

  ### Instance variables

  `var adjacent_meeting_count`
  `var adjacent_meetings`
  `var allow_new_time_proposal`
  `var appointment_reply_time`
  `var appointment_sequence_number`
  `var appointment_state`
  `var conference_type`
  `var conflicting_meeting_count`
  `var conflicting_meetings`
  `var deleted_occurrences`
  `var duration`
  `var end`
  `var first_occurrence`
  `var intended_free_busy_status`
  `var is_all_day`
  `var is_cancelled`
  `var is_meeting`
  `var is_online_meeting`
  `var is_recurring`
  `var last_occurrence`
  `var legacy_free_busy_status`
  `var location`
  `var meeting_request_type`
  `var meeting_request_was_sent`
  `var meeting_workspace_url`
  `var modified_occurrences`
  `var my_response_type`
  `var net_show_url`
  `var optional_attendees`
  `var organizer`
  `var original_start`
  `var recurrence`
  `var required_attendees`
  `var resources`
  `var start`
  `var type`
  `var when`

  ### Inherited members

  - `BaseMeetingItem`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `attach`
    - `deregister`
    - `detach`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class MeetingResponse (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/meetingresponse

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class MeetingResponse(BaseMeetingItem):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/meetingresponse"""

          ELEMENT_NAME = 'MeetingResponse'

          received_by = MailboxField(field_uri='message:ReceivedBy', is_read_only=True)
          received_representing = MailboxField(field_uri='message:ReceivedRepresenting', is_read_only=True)
          proposed_start = DateTimeField(field_uri='meeting:ProposedStart', supported_from=EXCHANGE_2013)
          proposed_end = DateTimeField(field_uri='meeting:ProposedEnd', supported_from=EXCHANGE_2013)

  ### Ancestors

  - BaseMeetingItem
  - Item
  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`
  `var FIELDS`

  ### Instance variables

  `var proposed_end`
  `var proposed_start`
  `var received_by`
  `var received_representing`

  ### Inherited members

  - `BaseMeetingItem`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `attach`
    - `deregister`
    - `detach`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class Message (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/message-ex15websvcsotherref

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class Message(Item):
          """MSDN:
          https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/message-ex15websvcsotherref
          """

          ELEMENT_NAME = 'Message'

          sender = MailboxField(field_uri='message:Sender', is_read_only=True, is_read_only_after_send=True)
          to_recipients = MailboxListField(field_uri='message:ToRecipients', is_read_only_after_send=True,
                                           is_searchable=False)
          cc_recipients = MailboxListField(field_uri='message:CcRecipients', is_read_only_after_send=True,
                                           is_searchable=False)
          bcc_recipients = MailboxListField(field_uri='message:BccRecipients', is_read_only_after_send=True,
                                            is_searchable=False)
          is_read_receipt_requested = BooleanField(field_uri='message:IsReadReceiptRequested',
                                                   is_required=True, default=False, is_read_only_after_send=True)
          is_delivery_receipt_requested = BooleanField(field_uri='message:IsDeliveryReceiptRequested', is_required=True,
                                                       default=False, is_read_only_after_send=True)
          conversation_index = Base64Field(field_uri='message:ConversationIndex', is_read_only=True)
          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)
          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')
          reply_to = MailboxListField(field_uri='message:ReplyTo', is_read_only_after_send=True, is_searchable=False)
          received_by = MailboxField(field_uri='message:ReceivedBy', is_read_only=True)
          received_representing = MailboxField(field_uri='message:ReceivedRepresenting', is_read_only=True)
          reminder_message_data = EWSElementField(field_uri='message:ReminderMessageData', value_cls=ReminderMessageData,
                                                  supported_from=EXCHANGE_2013_SP1, is_read_only=True)

          @require_account
          def send(self, save_copy=True, copy_to_folder=None, conflict_resolution=AUTO_RESOLVE,
                   send_meeting_invitations=SEND_TO_NONE):
              # Only sends a message. The message can either be an existing draft stored in EWS or a new message that does
              # not yet exist in EWS.
              if copy_to_folder and not save_copy:
                  raise AttributeError("'save_copy' must be True when 'copy_to_folder' is set")
              if save_copy and not copy_to_folder:
                  copy_to_folder = self.account.sent  # 'Sent' is default EWS behaviour
              if self.id:
                  SendItem(account=self.account).get(items=[self], saved_item_folder=copy_to_folder)
                  # The item will be deleted from the original folder
                  self._id = None
                  self.folder = copy_to_folder
                  return None

              # New message
              if copy_to_folder:
                  # This would better be done via send_and_save() but lets just support it here
                  self.folder = copy_to_folder
                  return self.send_and_save(conflict_resolution=conflict_resolution,
                                            send_meeting_invitations=send_meeting_invitations)

              if self.account.version.build < EXCHANGE_2013 and self.attachments:
                  # At least some versions prior to Exchange 2013 can't send attachments immediately. You need to first save,
                  # then attach, then send. This is done in send_and_save(). send() will delete the item again.
                  self.send_and_save(conflict_resolution=conflict_resolution,
                                     send_meeting_invitations=send_meeting_invitations)
                  return None

              self._create(message_disposition=SEND_ONLY, send_meeting_invitations=send_meeting_invitations)
              return None

          def send_and_save(self, update_fields=None, conflict_resolution=AUTO_RESOLVE,
                            send_meeting_invitations=SEND_TO_NONE):
              # Sends Message and saves a copy in the parent folder. Does not return an ItemId.
              if self.id:
                  self._update(
                      update_fieldnames=update_fields,
                      message_disposition=SEND_AND_SAVE_COPY,
                      conflict_resolution=conflict_resolution,
                      send_meeting_invitations=send_meeting_invitations
                  )
              else:
                  if self.account.version.build < EXCHANGE_2013 and self.attachments:
                      # At least some versions prior to Exchange 2013 can't send-and-save attachments immediately. You need
                      # to first save, then attach, then send. This is done in save().
                      self.save(update_fields=update_fields, conflict_resolution=conflict_resolution,
                                send_meeting_invitations=send_meeting_invitations)
                      self.send(save_copy=False, conflict_resolution=conflict_resolution,
                                send_meeting_invitations=send_meeting_invitations)
                  else:
                      res = self._create(
                          message_disposition=SEND_AND_SAVE_COPY,
                          send_meeting_invitations=send_meeting_invitations
                      )
                      if res is not True:
                          raise ValueError('Unexpected response in send-only mode')

          @require_id
          def create_reply(self, subject, body, to_recipients=None, cc_recipients=None, bcc_recipients=None):
              if to_recipients is None:
                  if not self.author:
                      raise ValueError("'to_recipients' must be set when message has no 'author'")
                  to_recipients = [self.author]
              return ReplyToItem(
                  account=self.account,
                  reference_item_id=ReferenceItemId(id=self.id, changekey=self.changekey),
                  subject=subject,
                  new_body=body,
                  to_recipients=to_recipients,
                  cc_recipients=cc_recipients,
                  bcc_recipients=bcc_recipients,
              )

          def reply(self, subject, body, to_recipients=None, cc_recipients=None, bcc_recipients=None):
              self.create_reply(
                  subject,
                  body,
                  to_recipients,
                  cc_recipients,
                  bcc_recipients
              ).send()

          @require_id
          def create_reply_all(self, subject, body):
              to_recipients = list(self.to_recipients) if self.to_recipients else []
              if self.author:
                  to_recipients.append(self.author)
              return ReplyAllToItem(
                  account=self.account,
                  reference_item_id=ReferenceItemId(id=self.id, changekey=self.changekey),
                  subject=subject,
                  new_body=body,
                  to_recipients=to_recipients,
                  cc_recipients=self.cc_recipients,
                  bcc_recipients=self.bcc_recipients,
              )

          def reply_all(self, subject, body):
              self.create_reply_all(subject, body).send()

          def mark_as_junk(self, is_junk=True, move_item=True):
              """Mark or un-marks items as junk email.

              :param is_junk: If True, the sender will be added from the blocked sender list. Otherwise, the sender will be
              removed.
              :param move_item: If true, the item will be moved to the junk folder.
              :return:
              """
              res = MarkAsJunk(account=self.account).get(
                  items=[self], is_junk=is_junk, move_item=move_item, expect_result=move_item
              )
              if res is None:
                  return
              self.folder = self.account.junk if is_junk else self.account.inbox
              self.id, self.changekey = res

  ### Ancestors

  - Item
  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`
  `var FIELDS`

  ### Instance variables

  `var author`
  `var bcc_recipients`
  `var cc_recipients`
  `var conversation_index`
  `var conversation_topic`
  `var is_delivery_receipt_requested`
  `var is_read`
  `var is_read_receipt_requested`
  `var is_response_requested`
  `var message_id`
  `var received_by`
  `var received_representing`
  `var references`
  `var reminder_message_data`
  `var reply_to`
  `var sender`
  `var to_recipients`

  ### Methods

  `def create_reply(self, subject, body, to_recipients=None, cc_recipients=None, bcc_recipients=None)`
    Expand source code

        @require_id
        def create_reply(self, subject, body, to_recipients=None, cc_recipients=None, bcc_recipients=None):
            if to_recipients is None:
                if not self.author:
                    raise ValueError("'to_recipients' must be set when message has no 'author'")
                to_recipients = [self.author]
            return ReplyToItem(
                account=self.account,
                reference_item_id=ReferenceItemId(id=self.id, changekey=self.changekey),
                subject=subject,
                new_body=body,
                to_recipients=to_recipients,
                cc_recipients=cc_recipients,
                bcc_recipients=bcc_recipients,
            )

  `def create_reply_all(self, subject, body)`
    Expand source code

        @require_id
        def create_reply_all(self, subject, body):
            to_recipients = list(self.to_recipients) if self.to_recipients else []
            if self.author:
                to_recipients.append(self.author)
            return ReplyAllToItem(
                account=self.account,
                reference_item_id=ReferenceItemId(id=self.id, changekey=self.changekey),
                subject=subject,
                new_body=body,
                to_recipients=to_recipients,
                cc_recipients=self.cc_recipients,
                bcc_recipients=self.bcc_recipients,
            )

  `def mark_as_junk(self, is_junk=True, move_item=True)`

    Mark or un-marks items as junk email.

    :param is_junk: If True, the sender will be added from the blocked sender list. Otherwise, the sender will be removed. :param move_item: If true, the item will be moved to the junk folder. :return:

    Expand source code

        def mark_as_junk(self, is_junk=True, move_item=True):
            """Mark or un-marks items as junk email.

            :param is_junk: If True, the sender will be added from the blocked sender list. Otherwise, the sender will be
            removed.
            :param move_item: If true, the item will be moved to the junk folder.
            :return:
            """
            res = MarkAsJunk(account=self.account).get(
                items=[self], is_junk=is_junk, move_item=move_item, expect_result=move_item
            )
            if res is None:
                return
            self.folder = self.account.junk if is_junk else self.account.inbox
            self.id, self.changekey = res

  `def reply(self, subject, body, to_recipients=None, cc_recipients=None, bcc_recipients=None)`
    Expand source code

        def reply(self, subject, body, to_recipients=None, cc_recipients=None, bcc_recipients=None):
            self.create_reply(
                subject,
                body,
                to_recipients,
                cc_recipients,
                bcc_recipients
            ).send()

  `def reply_all(self, subject, body)`
    Expand source code

        def reply_all(self, subject, body):
            self.create_reply_all(subject, body).send()

  `def send(self, save_copy=True, copy_to_folder=None, conflict_resolution='AutoResolve', send_meeting_invitations='SendToNone')`
    Expand source code

        @require_account
        def send(self, save_copy=True, copy_to_folder=None, conflict_resolution=AUTO_RESOLVE,
                 send_meeting_invitations=SEND_TO_NONE):
            # Only sends a message. The message can either be an existing draft stored in EWS or a new message that does
            # not yet exist in EWS.
            if copy_to_folder and not save_copy:
                raise AttributeError("'save_copy' must be True when 'copy_to_folder' is set")
            if save_copy and not copy_to_folder:
                copy_to_folder = self.account.sent  # 'Sent' is default EWS behaviour
            if self.id:
                SendItem(account=self.account).get(items=[self], saved_item_folder=copy_to_folder)
                # The item will be deleted from the original folder
                self._id = None
                self.folder = copy_to_folder
                return None

            # New message
            if copy_to_folder:
                # This would better be done via send_and_save() but lets just support it here
                self.folder = copy_to_folder
                return self.send_and_save(conflict_resolution=conflict_resolution,
                                          send_meeting_invitations=send_meeting_invitations)

            if self.account.version.build < EXCHANGE_2013 and self.attachments:
                # At least some versions prior to Exchange 2013 can't send attachments immediately. You need to first save,
                # then attach, then send. This is done in send_and_save(). send() will delete the item again.
                self.send_and_save(conflict_resolution=conflict_resolution,
                                   send_meeting_invitations=send_meeting_invitations)
                return None

            self._create(message_disposition=SEND_ONLY, send_meeting_invitations=send_meeting_invitations)
            return None

  `def send_and_save(self, update_fields=None, conflict_resolution='AutoResolve', send_meeting_invitations='SendToNone')`
    Expand source code

        def send_and_save(self, update_fields=None, conflict_resolution=AUTO_RESOLVE,
                          send_meeting_invitations=SEND_TO_NONE):
            # Sends Message and saves a copy in the parent folder. Does not return an ItemId.
            if self.id:
                self._update(
                    update_fieldnames=update_fields,
                    message_disposition=SEND_AND_SAVE_COPY,
                    conflict_resolution=conflict_resolution,
                    send_meeting_invitations=send_meeting_invitations
                )
            else:
                if self.account.version.build < EXCHANGE_2013 and self.attachments:
                    # At least some versions prior to Exchange 2013 can't send-and-save attachments immediately. You need
                    # to first save, then attach, then send. This is done in save().
                    self.save(update_fields=update_fields, conflict_resolution=conflict_resolution,
                              send_meeting_invitations=send_meeting_invitations)
                    self.send(save_copy=False, conflict_resolution=conflict_resolution,
                              send_meeting_invitations=send_meeting_invitations)
                else:
                    res = self._create(
                        message_disposition=SEND_AND_SAVE_COPY,
                        send_meeting_invitations=send_meeting_invitations
                    )
                    if res is not True:
                        raise ValueError('Unexpected response in send-only mode')

  ### Inherited members

  - `Item`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `attach`
    - `deregister`
    - `detach`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class Persona (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/persona

  Expand source code

      class Persona(IdChangeKeyMixIn):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/persona"""

          ELEMENT_NAME = 'Persona'
          ID_ELEMENT_CLS = PersonaId

          _id = IdElementField(field_uri='persona:PersonaId', value_cls=ID_ELEMENT_CLS, namespace=TNS)
          persona_type = CharField(field_uri='persona:PersonaType')
          persona_object_type = TextField(field_uri='persona:PersonaObjectStatus')
          creation_time = DateTimeField(field_uri='persona:CreationTime')
          bodies = BodyContentAttributedValueField(field_uri='persona:Bodies')
          display_name_first_last_sort_key = TextField(field_uri='persona:DisplayNameFirstLastSortKey')
          display_name_last_first_sort_key = TextField(field_uri='persona:DisplayNameLastFirstSortKey')
          company_sort_key = TextField(field_uri='persona:CompanyNameSortKey')
          home_sort_key = TextField(field_uri='persona:HomeCitySortKey')
          work_city_sort_key = TextField(field_uri='persona:WorkCitySortKey')
          display_name_first_last_header = CharField(field_uri='persona:DisplayNameFirstLastHeader')
          display_name_last_first_header = CharField(field_uri='persona:DisplayNameLastFirstHeader')
          file_as_header = TextField(field_uri='persona:FileAsHeader')
          display_name = CharField(field_uri='persona:DisplayName')
          display_name_first_last = CharField(field_uri='persona:DisplayNameFirstLast')
          display_name_last_first = CharField(field_uri='persona:DisplayNameLastFirst')
          file_as = CharField(field_uri='persona:FileAs')
          file_as_id = TextField(field_uri='persona:FileAsId')
          display_name_prefix = CharField(field_uri='persona:DisplayNamePrefix')
          given_name = CharField(field_uri='persona:GivenName')
          middle_name = CharField(field_uri='persona:MiddleName')
          surname = CharField(field_uri='persona:Surname')
          generation = CharField(field_uri='persona:Generation')
          nickname = TextField(field_uri='persona:Nickname')
          yomi_company_name = TextField(field_uri='persona:YomiCompanyName')
          yomi_first_name = TextField(field_uri='persona:YomiFirstName')
          yomi_last_name = TextField(field_uri='persona:YomiLastName')
          title = CharField(field_uri='persona:Title')
          department = TextField(field_uri='persona:Department')
          company_name = CharField(field_uri='persona:CompanyName')
          email_address = EWSElementField(field_uri='persona:EmailAddress', value_cls=EmailAddress)
          email_addresses = EWSElementListField(field_uri='persona:EmailAddresses', value_cls=Address)
          PhoneNumber = PersonaPhoneNumberField(field_uri='persona:PhoneNumber')
          im_address = CharField(field_uri='persona:ImAddress')
          home_city = CharField(field_uri='persona:HomeCity')
          work_city = CharField(field_uri='persona:WorkCity')
          relevance_score = CharField(field_uri='persona:RelevanceScore')
          folder_ids = EWSElementListField(field_uri='persona:FolderIds', value_cls=FolderId)
          attributions = EWSElementListField(field_uri='persona:Attributions', value_cls=Attribution)
          display_names = StringAttributedValueField(field_uri='persona:DisplayNames')
          file_ases = StringAttributedValueField(field_uri='persona:FileAses')
          file_as_ids = StringAttributedValueField(field_uri='persona:FileAsIds')
          display_name_prefixes = StringAttributedValueField(field_uri='persona:DisplayNamePrefixes')
          given_names = StringAttributedValueField(field_uri='persona:GivenNames')
          middle_names = StringAttributedValueField(field_uri='persona:MiddleNames')
          surnames = StringAttributedValueField(field_uri='persona:Surnames')
          generations = StringAttributedValueField(field_uri='persona:Generations')
          nicknames = StringAttributedValueField(field_uri='persona:Nicknames')
          initials = StringAttributedValueField(field_uri='persona:Initials')
          yomi_company_names = StringAttributedValueField(field_uri='persona:YomiCompanyNames')
          yomi_first_names = StringAttributedValueField(field_uri='persona:YomiFirstNames')
          yomi_last_names = StringAttributedValueField(field_uri='persona:YomiLastNames')
          business_phone_numbers = PhoneNumberAttributedValueField(field_uri='persona:BusinessPhoneNumbers')
          business_phone_numbers2 = PhoneNumberAttributedValueField(field_uri='persona:BusinessPhoneNumbers2')
          home_phones = PhoneNumberAttributedValueField(field_uri='persona:HomePhones')
          home_phones2 = PhoneNumberAttributedValueField(field_uri='persona:HomePhones2')
          mobile_phones = PhoneNumberAttributedValueField(field_uri='persona:MobilePhones')
          mobile_phones2 = PhoneNumberAttributedValueField(field_uri='persona:MobilePhones2')
          assistant_phone_numbers = PhoneNumberAttributedValueField(field_uri='persona:AssistantPhoneNumbers')
          callback_phones = PhoneNumberAttributedValueField(field_uri='persona:CallbackPhones')
          car_phones = PhoneNumberAttributedValueField(field_uri='persona:CarPhones')
          home_faxes = PhoneNumberAttributedValueField(field_uri='persona:HomeFaxes')
          orgnaization_main_phones = PhoneNumberAttributedValueField(field_uri='persona:OrganizationMainPhones')
          other_faxes = PhoneNumberAttributedValueField(field_uri='persona:OtherFaxes')
          other_telephones = PhoneNumberAttributedValueField(field_uri='persona:OtherTelephones')
          other_phones2 = PhoneNumberAttributedValueField(field_uri='persona:OtherPhones2')
          pagers = PhoneNumberAttributedValueField(field_uri='persona:Pagers')
          radio_phones = PhoneNumberAttributedValueField(field_uri='persona:RadioPhones')
          telex_numbers = PhoneNumberAttributedValueField(field_uri='persona:TelexNumbers')
          tty_tdd_phone_numbers = PhoneNumberAttributedValueField(field_uri='persona:TTYTDDPhoneNumbers')
          work_faxes = PhoneNumberAttributedValueField(field_uri='persona:WorkFaxes')
          emails1 = EmailAddressAttributedValueField(field_uri='persona:Emails1')
          emails2 = EmailAddressAttributedValueField(field_uri='persona:Emails2')
          emails3 = EmailAddressAttributedValueField(field_uri='persona:Emails3')
          business_home_pages = StringAttributedValueField(field_uri='persona:BusinessHomePages')
          personal_home_pages = StringAttributedValueField(field_uri='persona:PersonalHomePages')
          office_locations = StringAttributedValueField(field_uri='persona:OfficeLocations')
          im_addresses = StringAttributedValueField(field_uri='persona:ImAddresses')
          im_addresses2 = StringAttributedValueField(field_uri='persona:ImAddresses2')
          im_addresses3 = StringAttributedValueField(field_uri='persona:ImAddresses3')
          business_addresses = PostalAddressAttributedValueField(field_uri='persona:BusinessAddresses')
          home_addresses = PostalAddressAttributedValueField(field_uri='persona:HomeAddresses')
          other_addresses = PostalAddressAttributedValueField(field_uri='persona:OtherAddresses')
          titles = StringAttributedValueField(field_uri='persona:Titles')
          departments = StringAttributedValueField(field_uri='persona:Departments')
          company_names = StringAttributedValueField(field_uri='persona:CompanyNames')
          managers = StringAttributedValueField(field_uri='persona:Managers')
          assistant_names = StringAttributedValueField(field_uri='persona:AssistantNames')
          professions = StringAttributedValueField(field_uri='persona:Professions')
          spouse_names = StringAttributedValueField(field_uri='persona:SpouseNames')
          children = StringAttributedValueField(field_uri='persona:Children')
          schools = StringAttributedValueField(field_uri='persona:Schools')
          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')

  ### Ancestors

  - IdChangeKeyMixIn
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`
  `var FIELDS`
  `var ID_ELEMENT_CLS`

    MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/personaid

  ### Instance variables

  `var PhoneNumber`
  `var assistant_names`
  `var assistant_phone_numbers`
  `var attributions`
  `var birthdays`
  `var bodies`
  `var business_addresses`
  `var business_home_pages`
  `var business_phone_numbers`
  `var business_phone_numbers2`
  `var callback_phones`
  `var car_phones`
  `var children`
  `var company_name`
  `var company_names`
  `var company_sort_key`
  `var creation_time`
  `var department`
  `var departments`
  `var display_name`
  `var display_name_first_last`
  `var display_name_first_last_header`
  `var display_name_first_last_sort_key`
  `var display_name_last_first`
  `var display_name_last_first_header`
  `var display_name_last_first_sort_key`
  `var display_name_prefix`
  `var display_name_prefixes`
  `var display_names`
  `var email_address`
  `var email_addresses`
  `var emails1`
  `var emails2`
  `var emails3`
  `var file_as`
  `var file_as_header`
  `var file_as_id`
  `var file_as_ids`
  `var file_ases`
  `var folder_ids`
  `var generation`
  `var generations`
  `var given_name`
  `var given_names`
  `var hobbies`
  `var home_addresses`
  `var home_city`
  `var home_faxes`
  `var home_phones`
  `var home_phones2`
  `var home_sort_key`
  `var im_address`
  `var im_addresses`
  `var im_addresses2`
  `var im_addresses3`
  `var initials`
  `var locations`
  `var managers`
  `var middle_name`
  `var middle_names`
  `var mobile_phones`
  `var mobile_phones2`
  `var nickname`
  `var nicknames`
  `var office_locations`
  `var orgnaization_main_phones`
  `var other_addresses`
  `var other_faxes`
  `var other_phones2`
  `var other_telephones`
  `var pagers`
  `var persona_object_type`
  `var persona_type`
  `var personal_home_pages`
  `var professions`
  `var radio_phones`
  `var relevance_score`
  `var schools`
  `var spouse_names`
  `var surname`
  `var surnames`
  `var telex_numbers`
  `var title`
  `var titles`
  `var tty_tdd_phone_numbers`
  `var wedding_anniversaries`
  `var work_city`
  `var work_city_sort_key`
  `var work_faxes`
  `var yomi_company_name`
  `var yomi_company_names`
  `var yomi_first_name`
  `var yomi_first_names`
  `var yomi_last_name`
  `var yomi_last_names`

  ### Inherited members

  - `IdChangeKeyMixIn`:
    - `add_field`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class PostItem (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/postitem

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class PostItem(Item):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/postitem"""

          ELEMENT_NAME = 'PostItem'

          conversation_index = Message.FIELDS['conversation_index']
          conversation_topic = Message.FIELDS['conversation_topic']

          author = Message.FIELDS['author']
          message_id = Message.FIELDS['message_id']
          is_read = Message.FIELDS['is_read']

          posted_time = DateTimeField(field_uri='postitem:PostedTime', is_read_only=True)
          references = TextField(field_uri='message:References')
          sender = MailboxField(field_uri='message:Sender', is_read_only=True, is_read_only_after_send=True)

  ### Ancestors

  - Item
  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`
  `var FIELDS`

  ### Instance variables

  `var author`
  `var conversation_index`
  `var conversation_topic`
  `var is_read`
  `var message_id`
  `var posted_time`
  `var references`
  `var sender`

  ### Inherited members

  - `Item`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `attach`
    - `deregister`
    - `detach`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class PostReplyItem (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/postreplyitem

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class PostReplyItem(Item):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/postreplyitem"""

          ELEMENT_NAME = 'PostReplyItem'

          # This element only has Item fields up to, and including, 'culture'
          # TDO: Plus all message fields
          new_body = BodyField(field_uri='NewBodyContent')  # Accepts and returns Body or HTMLBody instances

          culture_idx = Item.FIELDS.index_by_name('culture')
          sender_idx = Message.FIELDS.index_by_name('sender')
          FIELDS = Item.FIELDS[:culture_idx + 1] + Message.FIELDS[sender_idx:]

  ### Ancestors

  - Item
  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`
  `var FIELDS`
  `var culture_idx`
  `var sender_idx`

  ### Instance variables

  `var author`
  `var bcc_recipients`
  `var cc_recipients`
  `var conversation_index`
  `var conversation_topic`
  `var is_delivery_receipt_requested`
  `var is_read`
  `var is_read_receipt_requested`
  `var is_response_requested`
  `var message_id`
  `var new_body`
  `var received_by`
  `var received_representing`
  `var references`
  `var reminder_message_data`
  `var reply_to`
  `var sender`
  `var to_recipients`

  ### Inherited members

  - `Item`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `attach`
    - `deregister`
    - `detach`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class RegisterMixIn (**kwargs)`

  Base class for classes that can change their list of supported fields dynamically.

  Expand source code

      class RegisterMixIn(IdChangeKeyMixIn, metaclass=EWSMeta):
          """Base class for classes that can change their list of supported fields dynamically."""

          # This class implements dynamic fields on an element class, so we need to include __dict__ in __slots__
          __slots__ = '__dict__',

          INSERT_AFTER_FIELD = None

          @classmethod
          def register(cls, attr_name, attr_cls):
              """Register a custom extended property in this item class so they can be accessed just like any other attribute

              :param attr_name:
              :param attr_cls:
              :return:
              """
              if not cls.INSERT_AFTER_FIELD:
                  raise ValueError('Class %s is missing INSERT_AFTER_FIELD value' % cls)
              try:
                  cls.get_field_by_fieldname(attr_name)
              except InvalidField:
                  pass
              else:
                  raise ValueError("'%s' is already registered" % attr_name)
              if not issubclass(attr_cls, ExtendedProperty):
                  raise ValueError("%r must be a subclass of ExtendedProperty" % attr_cls)
              # Check if class attributes are properly defined
              attr_cls.validate_cls()
              # ExtendedProperty is not a real field, but a placeholder in the fields list. See
              #   https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/item
              #
              # Find the correct index for the new extended property, and insert.
              field = ExtendedPropertyField(attr_name, value_cls=attr_cls)
              cls.add_field(field, insert_after=cls.INSERT_AFTER_FIELD)

          @classmethod
          def deregister(cls, attr_name):
              """De-register an extended property that has been registered with register().

              :param attr_name:
              :return:
              """
              try:
                  field = cls.get_field_by_fieldname(attr_name)
              except InvalidField:
                  raise ValueError("'%s' is not registered" % attr_name)
              if not isinstance(field, ExtendedPropertyField):
                  raise ValueError("'%s' is not registered as an ExtendedProperty" % attr_name)
              cls.remove_field(field)

  ### Ancestors

  - IdChangeKeyMixIn
  - EWSElement

  ### Subclasses

  - BaseFolder
  - BaseItem

  ### Class variables

  `var INSERT_AFTER_FIELD`

  ### Static methods

  `def deregister(attr_name)`

    De-register an extended property that has been registered with register().

    :param attr_name: :return:

    Expand source code

        @classmethod
        def deregister(cls, attr_name):
            """De-register an extended property that has been registered with register().

            :param attr_name:
            :return:
            """
            try:
                field = cls.get_field_by_fieldname(attr_name)
            except InvalidField:
                raise ValueError("'%s' is not registered" % attr_name)
            if not isinstance(field, ExtendedPropertyField):
                raise ValueError("'%s' is not registered as an ExtendedProperty" % attr_name)
            cls.remove_field(field)

  `def register(attr_name, attr_cls)`

    Register a custom extended property in this item class so they can be accessed just like any other attribute

    :param attr_name: :param attr_cls: :return:

    Expand source code

        @classmethod
        def register(cls, attr_name, attr_cls):
            """Register a custom extended property in this item class so they can be accessed just like any other attribute

            :param attr_name:
            :param attr_cls:
            :return:
            """
            if not cls.INSERT_AFTER_FIELD:
                raise ValueError('Class %s is missing INSERT_AFTER_FIELD value' % cls)
            try:
                cls.get_field_by_fieldname(attr_name)
            except InvalidField:
                pass
            else:
                raise ValueError("'%s' is already registered" % attr_name)
            if not issubclass(attr_cls, ExtendedProperty):
                raise ValueError("%r must be a subclass of ExtendedProperty" % attr_cls)
            # Check if class attributes are properly defined
            attr_cls.validate_cls()
            # ExtendedProperty is not a real field, but a placeholder in the fields list. See
            #   https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/item
            #
            # Find the correct index for the new extended property, and insert.
            field = ExtendedPropertyField(attr_name, value_cls=attr_cls)
            cls.add_field(field, insert_after=cls.INSERT_AFTER_FIELD)

  ### Inherited members

  - `IdChangeKeyMixIn`:
    - `add_field`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class ReplyAllToItem (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/replyalltoitem

  Expand source code

      class ReplyAllToItem(BaseReplyItem):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/replyalltoitem"""

          ELEMENT_NAME = 'ReplyAllToItem'

  ### Ancestors

  - BaseReplyItem
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`

  ### Inherited members

  - `BaseReplyItem`:
    - `account`
    - `add_field`
    - `remove_field`
    - `save`
    - `supported_fields`
    - `validate_field`
`class ReplyToItem (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/replytoitem

  Expand source code

      class ReplyToItem(BaseReplyItem):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/replytoitem"""

          ELEMENT_NAME = 'ReplyToItem'

  ### Ancestors

  - BaseReplyItem
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`

  ### Inherited members

  - `BaseReplyItem`:
    - `account`
    - `add_field`
    - `remove_field`
    - `save`
    - `supported_fields`
    - `validate_field`
`class Task (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/task

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class Task(Item):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/task"""

          ELEMENT_NAME = 'Task'
          NOT_STARTED = 'NotStarted'
          COMPLETED = 'Completed'

          actual_work = IntegerField(field_uri='task:ActualWork', min=0)
          assigned_time = DateTimeField(field_uri='task:AssignedTime', is_read_only=True)
          billing_information = TextField(field_uri='task:BillingInformation')
          change_count = IntegerField(field_uri='task:ChangeCount', is_read_only=True, min=0)
          companies = TextListField(field_uri='task:Companies')
          # 'complete_date' can be set, but is ignored by the server, which sets it to now()
          complete_date = DateTimeField(field_uri='task:CompleteDate', is_read_only=True)
          contacts = TextListField(field_uri='task:Contacts')
          delegation_state = ChoiceField(field_uri='task:DelegationState', choices={
              Choice('NoMatch'), Choice('OwnNew'), Choice('Owned'), Choice('Accepted'), Choice('Declined'), Choice('Max')
          }, is_read_only=True)
          delegator = CharField(field_uri='task:Delegator', is_read_only=True)
          due_date = DateTimeBackedDateField(field_uri='task:DueDate')
          is_editable = BooleanField(field_uri='task:IsAssignmentEditable', is_read_only=True)
          is_complete = BooleanField(field_uri='task:IsComplete', is_read_only=True)
          is_recurring = BooleanField(field_uri='task:IsRecurring', is_read_only=True)
          is_team_task = BooleanField(field_uri='task:IsTeamTask', is_read_only=True)
          mileage = TextField(field_uri='task:Mileage')
          owner = CharField(field_uri='task:Owner', is_read_only=True)
          percent_complete = DecimalField(field_uri='task:PercentComplete', is_required=True, default=Decimal(0.0),
                                          min=Decimal(0), max=Decimal(100), is_searchable=False)
          recurrence = TaskRecurrenceField(field_uri='task:Recurrence', is_searchable=False)
          start_date = DateTimeBackedDateField(field_uri='task:StartDate')
          status = ChoiceField(field_uri='task:Status', choices={
              Choice(NOT_STARTED), Choice('InProgress'), Choice(COMPLETED), Choice('WaitingOnOthers'), Choice('Deferred')
          }, is_required=True, is_searchable=False, default=NOT_STARTED)
          status_description = CharField(field_uri='task:StatusDescription', is_read_only=True)
          total_work = IntegerField(field_uri='task:TotalWork', min=0)

          def clean(self, version=None):
              super().clean(version=version)
              if self.due_date and self.start_date and self.due_date < self.start_date:
                  log.warning("'due_date' must be greater than 'start_date' (%s vs %s). Resetting 'due_date'",
                              self.due_date, self.start_date)
                  self.due_date = self.start_date
              if self.complete_date:
                  if self.status != self.COMPLETED:
                      log.warning("'status' must be '%s' when 'complete_date' is set (%s). Resetting",
                                  self.COMPLETED, self.status)
                      self.status = self.COMPLETED
                  now = datetime.datetime.now(tz=UTC)
                  if (self.complete_date - now).total_seconds() > 120:
                      # Reset complete_date values that are in the future
                      # 'complete_date' can be set automatically by the server. Allow some grace between local and server time
                      log.warning("'complete_date' must be in the past (%s vs %s). Resetting", self.complete_date, now)
                      self.complete_date = now
                  if self.start_date and self.complete_date.date() < self.start_date:
                      log.warning("'complete_date' must be greater than 'start_date' (%s vs %s). Resetting",
                                  self.complete_date, self.start_date)
                      self.complete_date = EWSDateTime.combine(self.start_date, datetime.time(0, 0)).replace(tzinfo=UTC)
              if self.percent_complete is not None:
                  if self.status == self.COMPLETED and self.percent_complete != Decimal(100):
                      # percent_complete must be 100% if task is complete
                      log.warning("'percent_complete' must be 100 when 'status' is '%s' (%s). Resetting",
                                  self.COMPLETED, self.percent_complete)
                      self.percent_complete = Decimal(100)
                  elif self.status == self.NOT_STARTED and self.percent_complete != Decimal(0):
                      # percent_complete must be 0% if task is not started
                      log.warning("'percent_complete' must be 0 when 'status' is '%s' (%s). Resetting",
                                  self.NOT_STARTED, self.percent_complete)
                      self.percent_complete = Decimal(0)

          def complete(self):
              # A helper method to mark a task as complete on the server
              self.status = Task.COMPLETED
              self.percent_complete = Decimal(100)
              self.save()

  ### Ancestors

  - Item
  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement

  ### Class variables

  `var COMPLETED`
  `var ELEMENT_NAME`
  `var FIELDS`
  `var NOT_STARTED`

  ### Instance variables

  `var actual_work`
  `var assigned_time`
  `var billing_information`
  `var change_count`
  `var companies`
  `var complete_date`
  `var contacts`
  `var delegation_state`
  `var delegator`
  `var due_date`
  `var is_complete`
  `var is_editable`
  `var is_recurring`
  `var is_team_task`
  `var mileage`
  `var owner`
  `var percent_complete`
  `var recurrence`
  `var start_date`
  `var status`
  `var status_description`
  `var total_work`

  ### Methods

  `def clean(self, version=None)`
    Expand source code

        def clean(self, version=None):
            super().clean(version=version)
            if self.due_date and self.start_date and self.due_date < self.start_date:
                log.warning("'due_date' must be greater than 'start_date' (%s vs %s). Resetting 'due_date'",
                            self.due_date, self.start_date)
                self.due_date = self.start_date
            if self.complete_date:
                if self.status != self.COMPLETED:
                    log.warning("'status' must be '%s' when 'complete_date' is set (%s). Resetting",
                                self.COMPLETED, self.status)
                    self.status = self.COMPLETED
                now = datetime.datetime.now(tz=UTC)
                if (self.complete_date - now).total_seconds() > 120:
                    # Reset complete_date values that are in the future
                    # 'complete_date' can be set automatically by the server. Allow some grace between local and server time
                    log.warning("'complete_date' must be in the past (%s vs %s). Resetting", self.complete_date, now)
                    self.complete_date = now
                if self.start_date and self.complete_date.date() < self.start_date:
                    log.warning("'complete_date' must be greater than 'start_date' (%s vs %s). Resetting",
                                self.complete_date, self.start_date)
                    self.complete_date = EWSDateTime.combine(self.start_date, datetime.time(0, 0)).replace(tzinfo=UTC)
            if self.percent_complete is not None:
                if self.status == self.COMPLETED and self.percent_complete != Decimal(100):
                    # percent_complete must be 100% if task is complete
                    log.warning("'percent_complete' must be 100 when 'status' is '%s' (%s). Resetting",
                                self.COMPLETED, self.percent_complete)
                    self.percent_complete = Decimal(100)
                elif self.status == self.NOT_STARTED and self.percent_complete != Decimal(0):
                    # percent_complete must be 0% if task is not started
                    log.warning("'percent_complete' must be 0 when 'status' is '%s' (%s). Resetting",
                                self.NOT_STARTED, self.percent_complete)
                    self.percent_complete = Decimal(0)

  `def complete(self)`
    Expand source code

        def complete(self):
            # A helper method to mark a task as complete on the server
            self.status = Task.COMPLETED
            self.percent_complete = Decimal(100)
            self.save()

  ### Inherited members

  - `Item`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `attach`
    - `deregister`
    - `detach`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`
`class TentativelyAcceptItem (**kwargs)`

  MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/tentativelyacceptitem

  Pick out optional 'account' and 'folder' kwargs, and pass the rest to the parent class.

  :param kwargs: 'account' is optional but allows calling 'send()' and 'delete()' 'folder' is optional but allows calling 'save()'. If 'folder' has an account, and 'account' is not set, we use folder.account.

  Expand source code

      class TentativelyAcceptItem(BaseMeetingReplyItem):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/tentativelyacceptitem"""

          ELEMENT_NAME = 'TentativelyAcceptItem'

  ### Ancestors

  - BaseMeetingReplyItem
  - BaseItem
  - RegisterMixIn
  - IdChangeKeyMixIn
  - EWSElement

  ### Class variables

  `var ELEMENT_NAME`

  ### Inherited members

  - `BaseMeetingReplyItem`:
    - `ID_ELEMENT_CLS`
    - `account`
    - `add_field`
    - `deregister`
    - `folder`
    - `register`
    - `remove_field`
    - `supported_fields`
    - `validate_field`