1# -------------------------------------------------------------------------- 2# 3# Copyright (c) Microsoft Corporation. All rights reserved. 4# 5# The MIT License (MIT) 6# 7# Permission is hereby granted, free of charge, to any person obtaining a copy 8# of this software and associated documentation files (the ""Software""), to 9# deal in the Software without restriction, including without limitation the 10# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11# sell copies of the Software, and to permit persons to whom the Software is 12# furnished to do so, subject to the following conditions: 13# 14# The above copyright notice and this permission notice shall be included in 15# all copies or substantial portions of the Software. 16# 17# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23# IN THE SOFTWARE. 24# 25# -------------------------------------------------------------------------- 26""" 27This module is the requests implementation of Pipeline ABC 28""" 29from __future__ import absolute_import # we have a "requests" module that conflicts with "requests" on Py2.7 30import json 31import inspect 32import logging 33import os 34import platform 35import xml.etree.ElementTree as ET 36import types 37import re 38import uuid 39from typing import (Mapping, IO, TypeVar, TYPE_CHECKING, Type, cast, List, Callable, Iterator, # pylint: disable=unused-import 40 Any, Union, Dict, Optional, AnyStr) 41from six.moves import urllib 42 43from azure.core import __version__ as azcore_version 44from azure.core.exceptions import ( 45 DecodeError, 46 raise_with_traceback 47) 48 49from azure.core.pipeline import PipelineRequest, PipelineResponse 50from ._base import SansIOHTTPPolicy 51 52if TYPE_CHECKING: 53 from azure.core.pipeline.transport import HttpResponse, AsyncHttpResponse 54 55_LOGGER = logging.getLogger(__name__) 56ContentDecodePolicyType = TypeVar('ContentDecodePolicyType', bound='ContentDecodePolicy') 57HTTPRequestType = TypeVar("HTTPRequestType") 58HTTPResponseType = TypeVar("HTTPResponseType") 59 60 61class HeadersPolicy(SansIOHTTPPolicy): 62 """A simple policy that sends the given headers with the request. 63 64 This will overwrite any headers already defined in the request. Headers can be 65 configured up front, where any custom headers will be applied to all outgoing 66 operations, and additional headers can also be added dynamically per operation. 67 68 :param dict base_headers: Headers to send with the request. 69 70 .. admonition:: Example: 71 72 .. literalinclude:: ../samples/test_example_sansio.py 73 :start-after: [START headers_policy] 74 :end-before: [END headers_policy] 75 :language: python 76 :dedent: 4 77 :caption: Configuring a headers policy. 78 """ 79 def __init__(self, base_headers=None, **kwargs): # pylint: disable=super-init-not-called 80 # type: (Dict[str, str], Any) -> None 81 self._headers = base_headers or {} 82 self._headers.update(kwargs.pop('headers', {})) 83 84 @property 85 def headers(self): 86 """The current headers collection.""" 87 return self._headers 88 89 def add_header(self, key, value): 90 """Add a header to the configuration to be applied to all requests. 91 92 :param str key: The header. 93 :param str value: The header's value. 94 """ 95 self._headers[key] = value 96 97 def on_request(self, request): 98 # type: (PipelineRequest) -> None 99 """Updates with the given headers before sending the request to the next policy. 100 101 :param request: The PipelineRequest object 102 :type request: ~azure.core.pipeline.PipelineRequest 103 """ 104 request.http_request.headers.update(self.headers) 105 additional_headers = request.context.options.pop('headers', {}) 106 if additional_headers: 107 request.http_request.headers.update(additional_headers) 108 109class _Unset(object): 110 pass 111 112class RequestIdPolicy(SansIOHTTPPolicy): 113 """A simple policy that sets the given request id in the header. 114 115 This will overwrite request id that is already defined in the request. Request id can be 116 configured up front, where the request id will be applied to all outgoing 117 operations, and additional request id can also be set dynamically per operation. 118 119 :keyword str request_id: The request id to be added into header. 120 :keyword bool auto_request_id: Auto generates a unique request ID per call if true which is by default. 121 122 .. admonition:: Example: 123 124 .. literalinclude:: ../samples/test_example_sansio.py 125 :start-after: [START request_id_policy] 126 :end-before: [END request_id_policy] 127 :language: python 128 :dedent: 4 129 :caption: Configuring a request id policy. 130 """ 131 def __init__(self, **kwargs): # pylint: disable=super-init-not-called 132 # type: (dict) -> None 133 self._request_id = kwargs.pop('request_id', _Unset) 134 self._auto_request_id = kwargs.pop('auto_request_id', True) 135 136 def set_request_id(self, value): 137 """Add the request id to the configuration to be applied to all requests. 138 139 :param str value: The request id value. 140 """ 141 self._request_id = value 142 143 def on_request(self, request): 144 # type: (PipelineRequest) -> None 145 """Updates with the given request id before sending the request to the next policy. 146 147 :param request: The PipelineRequest object 148 :type request: ~azure.core.pipeline.PipelineRequest 149 """ 150 request_id = unset = object() 151 if 'request_id' in request.context.options: 152 request_id = request.context.options.pop('request_id') 153 if request_id is None: 154 return 155 elif self._request_id is None: 156 return 157 elif self._request_id is not _Unset: 158 if "x-ms-client-request-id" in request.http_request.headers: 159 return 160 request_id = self._request_id 161 elif self._auto_request_id: 162 if "x-ms-client-request-id" in request.http_request.headers: 163 return 164 request_id = str(uuid.uuid1()) 165 if request_id is not unset: 166 header = {"x-ms-client-request-id": request_id} 167 request.http_request.headers.update(header) 168 169class UserAgentPolicy(SansIOHTTPPolicy): 170 """User-Agent Policy. Allows custom values to be added to the User-Agent header. 171 172 :param str base_user_agent: Sets the base user agent value. 173 174 :keyword bool user_agent_overwrite: Overwrites User-Agent when True. Defaults to False. 175 :keyword bool user_agent_use_env: Gets user-agent from environment. Defaults to True. 176 :keyword str user_agent: If specified, this will be added in front of the user agent string. 177 :keyword str sdk_moniker: If specified, the user agent string will be 178 azsdk-python-[sdk_moniker] Python/[python_version] ([platform_version]) 179 180 .. admonition:: Example: 181 182 .. literalinclude:: ../samples/test_example_sansio.py 183 :start-after: [START user_agent_policy] 184 :end-before: [END user_agent_policy] 185 :language: python 186 :dedent: 4 187 :caption: Configuring a user agent policy. 188 """ 189 _USERAGENT = "User-Agent" 190 _ENV_ADDITIONAL_USER_AGENT = 'AZURE_HTTP_USER_AGENT' 191 192 def __init__(self, base_user_agent=None, **kwargs): # pylint: disable=super-init-not-called 193 # type: (Optional[str], **Any) -> None 194 self.overwrite = kwargs.pop('user_agent_overwrite', False) 195 self.use_env = kwargs.pop('user_agent_use_env', True) 196 application_id = kwargs.pop('user_agent', None) 197 sdk_moniker = kwargs.pop('sdk_moniker', 'core/{}'.format(azcore_version)) 198 199 if base_user_agent: 200 self._user_agent = base_user_agent 201 else: 202 self._user_agent = "azsdk-python-{} Python/{} ({})".format( 203 sdk_moniker, 204 platform.python_version(), 205 platform.platform() 206 ) 207 208 if application_id: 209 self._user_agent = "{} {}".format(application_id, self._user_agent) 210 211 @property 212 def user_agent(self): 213 # type: () -> str 214 """The current user agent value.""" 215 if self.use_env: 216 add_user_agent_header = os.environ.get(self._ENV_ADDITIONAL_USER_AGENT, None) 217 if add_user_agent_header is not None: 218 return "{} {}".format(self._user_agent, add_user_agent_header) 219 return self._user_agent 220 221 def add_user_agent(self, value): 222 # type: (str) -> None 223 """Add value to current user agent with a space. 224 :param str value: value to add to user agent. 225 """ 226 self._user_agent = "{} {}".format(self._user_agent, value) 227 228 def on_request(self, request): 229 # type: (PipelineRequest) -> None 230 """Modifies the User-Agent header before the request is sent. 231 232 :param request: The PipelineRequest object 233 :type request: ~azure.core.pipeline.PipelineRequest 234 """ 235 http_request = request.http_request 236 options_dict = request.context.options 237 if 'user_agent' in options_dict: 238 user_agent = options_dict.pop('user_agent') 239 if options_dict.pop('user_agent_overwrite', self.overwrite): 240 http_request.headers[self._USERAGENT] = user_agent 241 else: 242 user_agent = "{} {}".format(user_agent, self.user_agent) 243 http_request.headers[self._USERAGENT] = user_agent 244 245 elif self.overwrite or self._USERAGENT not in http_request.headers: 246 http_request.headers[self._USERAGENT] = self.user_agent 247 248 249class NetworkTraceLoggingPolicy(SansIOHTTPPolicy): 250 251 """The logging policy in the pipeline is used to output HTTP network trace to the configured logger. 252 253 This accepts both global configuration, and per-request level with "enable_http_logger" 254 255 :param bool logging_enable: Use to enable per operation. Defaults to False. 256 257 .. admonition:: Example: 258 259 .. literalinclude:: ../samples/test_example_sansio.py 260 :start-after: [START network_trace_logging_policy] 261 :end-before: [END network_trace_logging_policy] 262 :language: python 263 :dedent: 4 264 :caption: Configuring a network trace logging policy. 265 """ 266 def __init__(self, logging_enable=False, **kwargs): # pylint: disable=unused-argument 267 self.enable_http_logger = logging_enable 268 269 def on_request(self, request): # pylint: disable=too-many-return-statements 270 # type: (PipelineRequest) -> None 271 """Logs HTTP request to the DEBUG logger. 272 273 :param request: The PipelineRequest object. 274 :type request: ~azure.core.pipeline.PipelineRequest 275 """ 276 http_request = request.http_request 277 options = request.context.options 278 logging_enable = options.pop("logging_enable", self.enable_http_logger) 279 request.context["logging_enable"] = logging_enable 280 if logging_enable: 281 if not _LOGGER.isEnabledFor(logging.DEBUG): 282 return 283 284 try: 285 log_string = "Request URL: '{}'".format(http_request.url) 286 log_string += "/nRequest method: '{}'".format(http_request.method) 287 log_string += "/nRequest headers:" 288 for header, value in http_request.headers.items(): 289 log_string += "/n '{}': '{}'".format(header, value) 290 log_string += "/nRequest body:" 291 292 # We don't want to log the binary data of a file upload. 293 if isinstance(http_request.body, types.GeneratorType): 294 log_string += "/nFile upload" 295 _LOGGER.debug(log_string) 296 return 297 try: 298 if isinstance(http_request.body, types.AsyncGeneratorType): 299 log_string += "/nFile upload" 300 _LOGGER.debug(log_string) 301 return 302 except AttributeError: 303 pass 304 if http_request.body: 305 log_string += "/n{}".format(str(http_request.body)) 306 _LOGGER.debug(log_string) 307 return 308 log_string += "/nThis request has no body" 309 _LOGGER.debug(log_string) 310 except Exception as err: # pylint: disable=broad-except 311 _LOGGER.debug("Failed to log request: %r", err) 312 313 def on_response(self, request, response): 314 # type: (PipelineRequest, PipelineResponse) -> None 315 """Logs HTTP response to the DEBUG logger. 316 317 :param request: The PipelineRequest object. 318 :type request: ~azure.core.pipeline.PipelineRequest 319 :param response: The PipelineResponse object. 320 :type response: ~azure.core.pipeline.PipelineResponse 321 """ 322 http_response = response.http_response 323 try: 324 logging_enable = response.context["logging_enable"] 325 if logging_enable: 326 if not _LOGGER.isEnabledFor(logging.DEBUG): 327 return 328 329 log_string = "Response status: '{}'".format(http_response.status_code) 330 log_string += "/nResponse headers:" 331 for res_header, value in http_response.headers.items(): 332 log_string += "/n '{}': '{}'".format(res_header, value) 333 334 # We don't want to log binary data if the response is a file. 335 log_string += "/nResponse content:" 336 pattern = re.compile(r'attachment; ?filename=["\w.]+', re.IGNORECASE) 337 header = http_response.headers.get('content-disposition') 338 339 if header and pattern.match(header): 340 filename = header.partition('=')[2] 341 log_string += "/nFile attachments: {}".format(filename) 342 elif http_response.headers.get("content-type", "").endswith("octet-stream"): 343 log_string += "/nBody contains binary data." 344 elif http_response.headers.get("content-type", "").startswith("image"): 345 log_string += "/nBody contains image data." 346 else: 347 if response.context.options.get('stream', False): 348 log_string += "/nBody is streamable." 349 else: 350 log_string += "/n{}".format(http_response.text()) 351 _LOGGER.debug(log_string) 352 except Exception as err: # pylint: disable=broad-except 353 _LOGGER.debug("Failed to log response: %s", repr(err)) 354 355 356class HttpLoggingPolicy(SansIOHTTPPolicy): 357 """The Pipeline policy that handles logging of HTTP requests and responses. 358 """ 359 360 DEFAULT_HEADERS_WHITELIST = set([ 361 "x-ms-request-id", 362 "x-ms-client-request-id", 363 "x-ms-return-client-request-id", 364 "traceparent", 365 "Accept", 366 "Cache-Control", 367 "Connection", 368 "Content-Length", 369 "Content-Type", 370 "Date", 371 "ETag", 372 "Expires", 373 "If-Match", 374 "If-Modified-Since", 375 "If-None-Match", 376 "If-Unmodified-Since", 377 "Last-Modified", 378 "Pragma", 379 "Request-Id", 380 "Retry-After", 381 "Server", 382 "Transfer-Encoding", 383 "User-Agent", 384 ]) 385 REDACTED_PLACEHOLDER = "REDACTED" 386 MULTI_RECORD_LOG = "AZURE_SDK_LOGGING_MULTIRECORD" 387 388 def __init__(self, logger=None, **kwargs): # pylint: disable=unused-argument 389 self.logger = logger or logging.getLogger( 390 "azure.core.pipeline.policies.http_logging_policy" 391 ) 392 self.allowed_query_params = set() 393 self.allowed_header_names = set(self.__class__.DEFAULT_HEADERS_WHITELIST) 394 395 def _redact_query_param(self, key, value): 396 lower_case_allowed_query_params = [ 397 param.lower() for param in self.allowed_query_params 398 ] 399 return value if key.lower() in lower_case_allowed_query_params else HttpLoggingPolicy.REDACTED_PLACEHOLDER 400 401 def _redact_header(self, key, value): 402 lower_case_allowed_header_names = [ 403 header.lower() for header in self.allowed_header_names 404 ] 405 return value if key.lower() in lower_case_allowed_header_names else HttpLoggingPolicy.REDACTED_PLACEHOLDER 406 407 def on_request(self, request): # pylint: disable=too-many-return-statements 408 # type: (PipelineRequest) -> None 409 """Logs HTTP method, url and headers. 410 :param request: The PipelineRequest object. 411 :type request: ~azure.core.pipeline.PipelineRequest 412 """ 413 http_request = request.http_request 414 options = request.context.options 415 # Get logger in my context first (request has been retried) 416 # then read from kwargs (pop if that's the case) 417 # then use my instance logger 418 logger = request.context.setdefault("logger", options.pop("logger", self.logger)) 419 420 if not logger.isEnabledFor(logging.INFO): 421 return 422 423 try: 424 parsed_url = list(urllib.parse.urlparse(http_request.url)) 425 parsed_qp = urllib.parse.parse_qsl(parsed_url[4], keep_blank_values=True) 426 filtered_qp = [(key, self._redact_query_param(key, value)) for key, value in parsed_qp] 427 # 4 is query 428 parsed_url[4] = "&".join(["=".join(part) for part in filtered_qp]) 429 redacted_url = urllib.parse.urlunparse(parsed_url) 430 431 multi_record = os.environ.get(HttpLoggingPolicy.MULTI_RECORD_LOG, False) 432 if multi_record: 433 logger.info("Request URL: %r", redacted_url) 434 logger.info("Request method: %r", http_request.method) 435 logger.info("Request headers:") 436 for header, value in http_request.headers.items(): 437 value = self._redact_header(header, value) 438 logger.info(" %r: %r", header, value) 439 if isinstance(http_request.body, types.GeneratorType): 440 logger.info("File upload") 441 return 442 try: 443 if isinstance(http_request.body, types.AsyncGeneratorType): 444 logger.info("File upload") 445 return 446 except AttributeError: 447 pass 448 if http_request.body: 449 logger.info("A body is sent with the request") 450 return 451 logger.info("No body was attached to the request") 452 return 453 log_string = "Request URL: '{}'".format(redacted_url) 454 log_string += "/nRequest method: '{}'".format(http_request.method) 455 log_string += "/nRequest headers:" 456 for header, value in http_request.headers.items(): 457 value = self._redact_header(header, value) 458 log_string += "/n '{}': '{}'".format(header, value) 459 if isinstance(http_request.body, types.GeneratorType): 460 log_string += "/nFile upload" 461 logger.info(log_string) 462 return 463 try: 464 if isinstance(http_request.body, types.AsyncGeneratorType): 465 log_string += "/nFile upload" 466 logger.info(log_string) 467 return 468 except AttributeError: 469 pass 470 if http_request.body: 471 log_string += "/nA body is sent with the request" 472 logger.info(log_string) 473 return 474 log_string += "/nNo body was attached to the request" 475 logger.info(log_string) 476 477 except Exception as err: # pylint: disable=broad-except 478 logger.warning("Failed to log request: %s", repr(err)) 479 480 def on_response(self, request, response): 481 # type: (PipelineRequest, PipelineResponse) -> None 482 http_response = response.http_response 483 484 try: 485 logger = response.context["logger"] 486 487 if not logger.isEnabledFor(logging.INFO): 488 return 489 490 multi_record = os.environ.get(HttpLoggingPolicy.MULTI_RECORD_LOG, False) 491 if multi_record: 492 logger.info("Response status: %r", http_response.status_code) 493 logger.info("Response headers:") 494 for res_header, value in http_response.headers.items(): 495 value = self._redact_header(res_header, value) 496 logger.info(" %r: %r", res_header, value) 497 return 498 log_string = "Response status: {}".format(http_response.status_code) 499 log_string += "/nResponse headers:" 500 for res_header, value in http_response.headers.items(): 501 value = self._redact_header(res_header, value) 502 log_string += "/n '{}': '{}'".format(res_header, value) 503 logger.info(log_string) 504 except Exception as err: # pylint: disable=broad-except 505 logger.warning("Failed to log response: %s", repr(err)) 506 507class ContentDecodePolicy(SansIOHTTPPolicy): 508 """Policy for decoding unstreamed response content. 509 510 :param response_encoding: The encoding to use if known for this service (will disable auto-detection) 511 :type response_encoding: str 512 """ 513 # Accept "text" because we're open minded people... 514 JSON_REGEXP = re.compile(r'^(application|text)/([0-9a-z+.]+\+)?json$') 515 516 # Name used in context 517 CONTEXT_NAME = "deserialized_data" 518 519 def __init__(self, response_encoding=None, **kwargs): # pylint: disable=unused-argument 520 # type: (Optional[str], Any) -> None 521 self._response_encoding = response_encoding 522 523 @classmethod 524 def deserialize_from_text( 525 cls, # type: Type[ContentDecodePolicyType] 526 data, # type: Optional[Union[AnyStr, IO]] 527 mime_type=None, # Optional[str] 528 response=None # Optional[Union[HttpResponse, AsyncHttpResponse]] 529 ): 530 """Decode response data according to content-type. 531 532 Accept a stream of data as well, but will be load at once in memory for now. 533 If no content-type, will return the string version (not bytes, not stream) 534 535 :param response: The HTTP response. 536 :type response: ~azure.core.pipeline.transport.HttpResponse 537 :param str mime_type: The mime type. As mime type, charset is not expected. 538 :param response: If passed, exception will be annotated with that response 539 :raises ~azure.core.exceptions.DecodeError: If deserialization fails 540 :returns: A dict or XML tree, depending of the mime_type 541 """ 542 if not data: 543 return None 544 545 if hasattr(data, 'read'): 546 # Assume a stream 547 data = cast(IO, data).read() 548 549 if isinstance(data, bytes): 550 data_as_str = data.decode(encoding='utf-8-sig') 551 else: 552 # Explain to mypy the correct type. 553 data_as_str = cast(str, data) 554 555 if mime_type is None: 556 return data_as_str 557 558 if cls.JSON_REGEXP.match(mime_type): 559 try: 560 return json.loads(data_as_str) 561 except ValueError as err: 562 raise DecodeError(message="JSON is invalid: {}".format(err), response=response, error=err) 563 elif "xml" in (mime_type or []): 564 try: 565 try: 566 if isinstance(data, unicode): # type: ignore 567 # If I'm Python 2.7 and unicode XML will scream if I try a "fromstring" on unicode string 568 data_as_str = cast(str, data_as_str.encode(encoding="utf-8")) 569 except NameError: 570 pass 571 return ET.fromstring(data_as_str) # nosec 572 except ET.ParseError: 573 # It might be because the server has an issue, and returned JSON with 574 # content-type XML.... 575 # So let's try a JSON load, and if it's still broken 576 # let's flow the initial exception 577 def _json_attemp(data): 578 try: 579 return True, json.loads(data) 580 except ValueError: 581 return False, None # Don't care about this one 582 success, json_result = _json_attemp(data) 583 if success: 584 return json_result 585 # If i'm here, it's not JSON, it's not XML, let's scream 586 # and raise the last context in this block (the XML exception) 587 # The function hack is because Py2.7 messes up with exception 588 # context otherwise. 589 _LOGGER.critical("Wasn't XML not JSON, failing") 590 raise_with_traceback(DecodeError, message="XML is invalid", response=response) 591 elif mime_type.startswith("text/"): 592 return data_as_str 593 raise DecodeError("Cannot deserialize content-type: {}".format(mime_type)) 594 595 @classmethod 596 def deserialize_from_http_generics( 597 cls, # type: Type[ContentDecodePolicyType] 598 response, # Union[HttpResponse, AsyncHttpResponse] 599 encoding=None, # Optional[str] 600 ): 601 """Deserialize from HTTP response. 602 603 Headers will tested for "content-type" 604 605 :param response: The HTTP response 606 :param encoding: The encoding to use if known for this service (will disable auto-detection) 607 :raises ~azure.core.exceptions.DecodeError: If deserialization fails 608 :returns: A dict or XML tree, depending of the mime-type 609 """ 610 # Try to use content-type from headers if available 611 if response.content_type: 612 mime_type = response.content_type.split(";")[0].strip().lower() 613 # Ouch, this server did not declare what it sent... 614 # Let's guess it's JSON... 615 # Also, since Autorest was considering that an empty body was a valid JSON, 616 # need that test as well.... 617 else: 618 mime_type = "application/json" 619 620 # Rely on transport implementation to give me "text()" decoded correctly 621 if hasattr(response, "read"): 622 try: 623 # since users can call deserialize_from_http_generics by themselves 624 # we want to make sure our new responses are read before we try to 625 # deserialize. Only read sync responses since we're in a sync function 626 if not inspect.iscoroutinefunction(response.read): 627 response.read() 628 except AttributeError: 629 # raises an AttributeError in 2.7 bc inspect.iscoroutinefunction was added in 3.5 630 # Entering here means it's 2.7 and that the response has a read method, so we read 631 # bc it will be sync. 632 response.read() 633 return cls.deserialize_from_text(response.text(encoding), mime_type, response=response) 634 635 def on_request(self, request): 636 # type: (PipelineRequest) -> None 637 options = request.context.options 638 response_encoding = options.pop("response_encoding", self._response_encoding) 639 if response_encoding: 640 request.context["response_encoding"] = response_encoding 641 642 def on_response(self, 643 request, # type: PipelineRequest[HTTPRequestType] 644 response # type: PipelineResponse[HTTPRequestType, Union[HttpResponse, AsyncHttpResponse]] 645 ): 646 # type: (...) -> None 647 """Extract data from the body of a REST response object. 648 This will load the entire payload in memory. 649 Will follow Content-Type to parse. 650 We assume everything is UTF8 (BOM acceptable). 651 652 :param request: The PipelineRequest object. 653 :type request: ~azure.core.pipeline.PipelineRequest 654 :param response: The PipelineResponse object. 655 :type response: ~azure.core.pipeline.PipelineResponse 656 :param raw_data: Data to be processed. 657 :param content_type: How to parse if raw_data is a string/bytes. 658 :raises JSONDecodeError: If JSON is requested and parsing is impossible. 659 :raises UnicodeDecodeError: If bytes is not UTF8 660 :raises xml.etree.ElementTree.ParseError: If bytes is not valid XML 661 :raises ~azure.core.exceptions.DecodeError: If deserialization fails 662 """ 663 # If response was asked as stream, do NOT read anything and quit now 664 if response.context.options.get("stream", True): 665 return 666 667 response_encoding = request.context.get('response_encoding') 668 669 response.context[self.CONTEXT_NAME] = self.deserialize_from_http_generics( 670 response.http_response, 671 response_encoding 672 ) 673 674 675class ProxyPolicy(SansIOHTTPPolicy): 676 """A proxy policy. 677 678 Dictionary mapping protocol or protocol and host to the URL of the proxy 679 to be used on each Request. 680 681 :param dict proxies: Maps protocol or protocol and hostname to the URL 682 of the proxy. 683 684 .. admonition:: Example: 685 686 .. literalinclude:: ../samples/test_example_sansio.py 687 :start-after: [START proxy_policy] 688 :end-before: [END proxy_policy] 689 :language: python 690 :dedent: 4 691 :caption: Configuring a proxy policy. 692 """ 693 def __init__(self, proxies=None, **kwargs): #pylint: disable=unused-argument,super-init-not-called 694 self.proxies = proxies 695 696 def on_request(self, request): 697 # type: (PipelineRequest) -> None 698 ctxt = request.context.options 699 if self.proxies and "proxies" not in ctxt: 700 ctxt["proxies"] = self.proxies 701