1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
|
from itertools import chain
from ..attachments import FileAttachment, ItemAttachment
from ..errors import InvalidEnumValue
from ..util import (
MNS,
DummyResponse,
ElementNotFound,
StreamingBase64Parser,
StreamingContentHandler,
add_xml_child,
create_element,
set_xml_value,
)
from .common import EWSAccountService, attachment_ids_element
# https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/bodytype
BODY_TYPE_CHOICES = ("Best", "HTML", "Text")
class GetAttachment(EWSAccountService):
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/getattachment-operation"""
SERVICE_NAME = "GetAttachment"
element_container_name = f"{{{MNS}}}Attachments"
cls_map = {cls.response_tag(): cls for cls in (FileAttachment, ItemAttachment)}
def call(self, items, include_mime_content, body_type, filter_html_content, additional_fields):
if body_type and body_type not in BODY_TYPE_CHOICES:
raise InvalidEnumValue("body_type", body_type, BODY_TYPE_CHOICES)
return self._elems_to_objs(
self._chunked_get_elements(
self.get_payload,
items=items,
include_mime_content=include_mime_content,
body_type=body_type,
filter_html_content=filter_html_content,
additional_fields=additional_fields,
)
)
def _elem_to_obj(self, elem):
return self.cls_map[elem.tag].from_xml(elem=elem, account=self.account)
def get_payload(self, items, include_mime_content, body_type, filter_html_content, additional_fields):
payload = create_element(f"m:{self.SERVICE_NAME}")
shape_elem = create_element("m:AttachmentShape")
if include_mime_content:
add_xml_child(shape_elem, "t:IncludeMimeContent", "true")
if body_type:
add_xml_child(shape_elem, "t:BodyType", body_type)
if filter_html_content is not None:
add_xml_child(shape_elem, "t:FilterHtmlContent", "true" if filter_html_content else "false")
if additional_fields:
additional_properties = create_element("t:AdditionalProperties")
expanded_fields = chain(*(f.expand(version=self.account.version) for f in additional_fields))
set_xml_value(
additional_properties,
sorted(expanded_fields, key=lambda f: (getattr(f.field, "field_uri", ""), f.path)),
version=self.account.version,
)
shape_elem.append(additional_properties)
if len(shape_elem):
payload.append(shape_elem)
payload.append(attachment_ids_element(items=items, version=self.account.version))
return payload
def _update_api_version(self, api_version, header):
if not self.streaming:
super()._update_api_version(api_version, header)
# TODO: We're skipping this part in streaming mode because StreamingBase64Parser cannot parse the SOAP header
def _get_soap_parts(self, response):
if not self.streaming:
return super()._get_soap_parts(response)
# Pass the response unaltered. We want to use our custom streaming parser
return None, response
def _get_soap_messages(self, body):
if not self.streaming:
return super()._get_soap_messages(body)
# 'body' is actually the raw response passed on by '_get_soap_parts'
r = body
parser = StreamingBase64Parser()
field = FileAttachment.get_field_by_fieldname("_content")
handler = StreamingContentHandler(parser=parser, ns=field.namespace, element_name=field.field_uri)
parser.setContentHandler(handler)
return parser.parse(r)
def stream_file_content(self, attachment_id):
# The streaming XML parser can only stream content of one attachment
payload = self.get_payload(
items=[attachment_id],
include_mime_content=False,
body_type=None,
filter_html_content=None,
additional_fields=None,
)
self.streaming = True
try:
yield from self._get_response_xml(payload=payload)
except ElementNotFound as enf:
# When the returned XML does not contain a Content element, ElementNotFound is thrown by parser.parse().
# Let the non-streaming SOAP parser parse the response and hook into the normal exception handling.
# Wrap in DummyResponse because _get_soap_parts() expects an iter_content() method.
response = DummyResponse(content=enf.data)
_, body = super()._get_soap_parts(response=response)
# TODO: We're skipping ._update_api_version() here because we don't have access to the 'api_version' used.
res = super()._get_soap_messages(body=body)
for e in self._get_elements_in_response(response=res):
if isinstance(e, Exception):
raise e
# The returned content did not contain any EWS exceptions. Give up and re-raise the original exception.
raise enf
finally:
self.stop_streaming()
self.streaming = False
|