exchangelib.services.update_folder API documentation

# Module `exchangelib.services.update_folder`

Expand source code

    from .common import EWSAccountService, parse_folder_elem, to_item_id
    from ..fields import FieldPath
    from ..util import create_element, set_xml_value, MNS

    class UpdateFolder(EWSAccountService):
        """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/updatefolder-operation"""

        SERVICE_NAME = 'UpdateFolder'
        element_container_name = '{%s}Folders' % MNS

        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.folders = []  # A hack to communicate parsing args to _elems_to_objs()

        def call(self, folders):
            # We can't easily find the correct folder class from the returned XML. Instead, return objects with the same
            # class as the folder instance it was requested with.
            self.folders = list(folders)  # Convert to a list, in case 'folders' is a generator. We're iterating twice.
            return self._elems_to_objs(self._chunked_get_elements(self.get_payload, items=self.folders))

        def _elems_to_objs(self, elems):
            for (folder, _), elem in zip(self.folders, elems):
                if isinstance(elem, Exception):
                    yield elem
                    continue
                yield parse_folder_elem(elem=elem, folder=folder, account=self.account)

        @staticmethod
        def _sort_fieldnames(folder_model, fieldnames):
            # Take a list of fieldnames and return the fields in the order they are mentioned in folder_model.FIELDS.
            # Loop over FIELDS and not supported_fields(). Upstream should make sure not to update a non-supported field.
            for f in folder_model.FIELDS:
                if f.name in fieldnames:
                    yield f.name

        def _set_folder_elem(self, folder_model, field_path, value):
            setfolderfield = create_element('t:SetFolderField')
            set_xml_value(setfolderfield, field_path, version=self.account.version)
            folder = create_element(folder_model.request_tag())
            field_elem = field_path.field.to_xml(value, version=self.account.version)
            set_xml_value(folder, field_elem, version=self.account.version)
            setfolderfield.append(folder)
            return setfolderfield

        def _delete_folder_elem(self, field_path):
            deletefolderfield = create_element('t:DeleteFolderField')
            return set_xml_value(deletefolderfield, field_path, version=self.account.version)

        def _get_folder_update_elems(self, folder, fieldnames):
            folder_model = folder.__class__
            fieldnames_set = set(fieldnames)

            for fieldname in self._sort_fieldnames(folder_model=folder_model, fieldnames=fieldnames_set):
                field = folder_model.get_field_by_fieldname(fieldname)
                if field.is_read_only:
                    raise ValueError('%s is a read-only field' % field.name)
                value = field.clean(getattr(folder, field.name), version=self.account.version)  # Make sure the value is OK

                if value is None or (field.is_list and not value):
                    # A value of None or [] means we want to remove this field from the item
                    if field.is_required or field.is_required_after_save:
                        raise ValueError('%s is a required field and may not be deleted' % field.name)
                    for field_path in FieldPath(field=field).expand(version=self.account.version):
                        yield self._delete_folder_elem(field_path=field_path)
                    continue

                yield self._set_folder_elem(folder_model=folder_model, field_path=FieldPath(field=field), value=value)

        def get_payload(self, folders):
            from ..folders import BaseFolder, FolderId
            updatefolder = create_element('m:%s' % self.SERVICE_NAME)
            folderchanges = create_element('m:FolderChanges')
            version = self.account.version
            for folder, fieldnames in folders:
                folderchange = create_element('t:FolderChange')
                if not isinstance(folder, (BaseFolder, FolderId)):
                    folder = to_item_id(folder, FolderId, version=version)
                set_xml_value(folderchange, folder, version=version)
                updates = create_element('t:Updates')
                for elem in self._get_folder_update_elems(folder=folder, fieldnames=fieldnames):
                    updates.append(elem)
                folderchange.append(updates)
                folderchanges.append(folderchange)
            if not len(folderchanges):
                raise ValueError('"folders" must not be empty')
            updatefolder.append(folderchanges)
            return updatefolder

## Classes

`class UpdateFolder (*args, **kwargs)`

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

  Expand source code

      class UpdateFolder(EWSAccountService):
          """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/updatefolder-operation"""

          SERVICE_NAME = 'UpdateFolder'
          element_container_name = '{%s}Folders' % MNS

          def __init__(self, *args, **kwargs):
              super().__init__(*args, **kwargs)
              self.folders = []  # A hack to communicate parsing args to _elems_to_objs()

          def call(self, folders):
              # We can't easily find the correct folder class from the returned XML. Instead, return objects with the same
              # class as the folder instance it was requested with.
              self.folders = list(folders)  # Convert to a list, in case 'folders' is a generator. We're iterating twice.
              return self._elems_to_objs(self._chunked_get_elements(self.get_payload, items=self.folders))

          def _elems_to_objs(self, elems):
              for (folder, _), elem in zip(self.folders, elems):
                  if isinstance(elem, Exception):
                      yield elem
                      continue
                  yield parse_folder_elem(elem=elem, folder=folder, account=self.account)

          @staticmethod
          def _sort_fieldnames(folder_model, fieldnames):
              # Take a list of fieldnames and return the fields in the order they are mentioned in folder_model.FIELDS.
              # Loop over FIELDS and not supported_fields(). Upstream should make sure not to update a non-supported field.
              for f in folder_model.FIELDS:
                  if f.name in fieldnames:
                      yield f.name

          def _set_folder_elem(self, folder_model, field_path, value):
              setfolderfield = create_element('t:SetFolderField')
              set_xml_value(setfolderfield, field_path, version=self.account.version)
              folder = create_element(folder_model.request_tag())
              field_elem = field_path.field.to_xml(value, version=self.account.version)
              set_xml_value(folder, field_elem, version=self.account.version)
              setfolderfield.append(folder)
              return setfolderfield

          def _delete_folder_elem(self, field_path):
              deletefolderfield = create_element('t:DeleteFolderField')
              return set_xml_value(deletefolderfield, field_path, version=self.account.version)

          def _get_folder_update_elems(self, folder, fieldnames):
              folder_model = folder.__class__
              fieldnames_set = set(fieldnames)

              for fieldname in self._sort_fieldnames(folder_model=folder_model, fieldnames=fieldnames_set):
                  field = folder_model.get_field_by_fieldname(fieldname)
                  if field.is_read_only:
                      raise ValueError('%s is a read-only field' % field.name)
                  value = field.clean(getattr(folder, field.name), version=self.account.version)  # Make sure the value is OK

                  if value is None or (field.is_list and not value):
                      # A value of None or [] means we want to remove this field from the item
                      if field.is_required or field.is_required_after_save:
                          raise ValueError('%s is a required field and may not be deleted' % field.name)
                      for field_path in FieldPath(field=field).expand(version=self.account.version):
                          yield self._delete_folder_elem(field_path=field_path)
                      continue

                  yield self._set_folder_elem(folder_model=folder_model, field_path=FieldPath(field=field), value=value)

          def get_payload(self, folders):
              from ..folders import BaseFolder, FolderId
              updatefolder = create_element('m:%s' % self.SERVICE_NAME)
              folderchanges = create_element('m:FolderChanges')
              version = self.account.version
              for folder, fieldnames in folders:
                  folderchange = create_element('t:FolderChange')
                  if not isinstance(folder, (BaseFolder, FolderId)):
                      folder = to_item_id(folder, FolderId, version=version)
                  set_xml_value(folderchange, folder, version=version)
                  updates = create_element('t:Updates')
                  for elem in self._get_folder_update_elems(folder=folder, fieldnames=fieldnames):
                      updates.append(elem)
                  folderchange.append(updates)
                  folderchanges.append(folderchange)
              if not len(folderchanges):
                  raise ValueError('"folders" must not be empty')
              updatefolder.append(folderchanges)
              return updatefolder

  ### Ancestors

  - EWSAccountService
  - EWSService

  ### Class variables

  `var SERVICE_NAME`
  `var element_container_name`

  ### Methods

  `def call(self, folders)`
    Expand source code

        def call(self, folders):
            # We can't easily find the correct folder class from the returned XML. Instead, return objects with the same
            # class as the folder instance it was requested with.
            self.folders = list(folders)  # Convert to a list, in case 'folders' is a generator. We're iterating twice.
            return self._elems_to_objs(self._chunked_get_elements(self.get_payload, items=self.folders))

  `def get_payload(self, folders)`
    Expand source code

        def get_payload(self, folders):
            from ..folders import BaseFolder, FolderId
            updatefolder = create_element('m:%s' % self.SERVICE_NAME)
            folderchanges = create_element('m:FolderChanges')
            version = self.account.version
            for folder, fieldnames in folders:
                folderchange = create_element('t:FolderChange')
                if not isinstance(folder, (BaseFolder, FolderId)):
                    folder = to_item_id(folder, FolderId, version=version)
                set_xml_value(folderchange, folder, version=version)
                updates = create_element('t:Updates')
                for elem in self._get_folder_update_elems(folder=folder, fieldnames=fieldnames):
                    updates.append(elem)
                folderchange.append(updates)
                folderchanges.append(folderchange)
            if not len(folderchanges):
                raise ValueError('"folders" must not be empty')
            updatefolder.append(folderchanges)
            return updatefolder

  ### Inherited members

  - `EWSAccountService`:
    - `NO_VALID_SERVER_VERSIONS`
    - `WARNINGS_TO_CATCH_IN_RESPONSE`
    - `get`
    - `parse`