Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/azure/core/exceptions.py: 38%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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# --------------------------------------------------------------------------
26from __future__ import annotations
27import json
28import logging
29import sys
31from types import TracebackType
32from typing import (
33 Callable,
34 Any,
35 Optional,
36 Union,
37 Type,
38 List,
39 Mapping,
40 TypeVar,
41 Generic,
42 Dict,
43 NoReturn,
44 TYPE_CHECKING,
45)
46from typing_extensions import Protocol, runtime_checkable
48_LOGGER = logging.getLogger(__name__)
50if TYPE_CHECKING:
51 from azure.core.pipeline.policies import RequestHistory
53HTTPResponseType = TypeVar("HTTPResponseType")
54HTTPRequestType = TypeVar("HTTPRequestType")
55KeyType = TypeVar("KeyType")
56ValueType = TypeVar("ValueType")
57# To replace when typing.Self is available in our baseline
58SelfODataV4Format = TypeVar("SelfODataV4Format", bound="ODataV4Format")
61__all__ = [
62 "AzureError",
63 "ServiceRequestError",
64 "ServiceResponseError",
65 "HttpResponseError",
66 "DecodeError",
67 "ResourceExistsError",
68 "ResourceNotFoundError",
69 "ClientAuthenticationError",
70 "ResourceModifiedError",
71 "ResourceNotModifiedError",
72 "TooManyRedirectsError",
73 "ODataV4Format",
74 "ODataV4Error",
75 "StreamConsumedError",
76 "StreamClosedError",
77 "ResponseNotReadError",
78 "SerializationError",
79 "DeserializationError",
80]
83def raise_with_traceback(exception: Callable, *args: Any, message: str = "", **kwargs: Any) -> NoReturn:
84 """Raise exception with a specified traceback.
85 This MUST be called inside a "except" clause.
87 .. note:: This method is deprecated since we don't support Python 2 anymore. Use raise/from instead.
89 :param Exception exception: Error type to be raised.
90 :param any args: Any additional args to be included with exception.
91 :keyword str message: Message to be associated with the exception. If omitted, defaults to an empty string.
92 """
93 exc_type, exc_value, exc_traceback = sys.exc_info()
94 # If not called inside an "except", exc_type will be None. Assume it will not happen
95 if exc_type is None:
96 raise ValueError("raise_with_traceback can only be used in except clauses")
97 exc_msg = "{}, {}: {}".format(message, exc_type.__name__, exc_value)
98 error = exception(exc_msg, *args, **kwargs)
99 try:
100 raise error.with_traceback(exc_traceback) # pylint: disable=raise-missing-from
101 except AttributeError: # Python 2
102 error.__traceback__ = exc_traceback
103 raise error # pylint: disable=raise-missing-from
106@runtime_checkable
107class _HttpResponseCommonAPI(Protocol):
108 """Protocol used by exceptions for HTTP response.
110 As HttpResponseError uses very few properties of HttpResponse, a protocol
111 is faster and simpler than import all the possible types (at least 6).
112 """
114 @property
115 def reason(self) -> Optional[str]: ...
117 @property
118 def status_code(self) -> Optional[int]: ...
120 def text(self) -> str: ...
122 @property
123 def request(self) -> object: # object as type, since all we need is str() on it
124 ...
127class ErrorMap(Generic[KeyType, ValueType]):
128 """Error Map class. To be used in map_error method, behaves like a dictionary.
129 It returns the error type if it is found in custom_error_map. Or return default_error
131 :param dict custom_error_map: User-defined error map, it is used to map status codes to error types.
132 :keyword error default_error: Default error type. It is returned if the status code is not found in custom_error_map
133 """
135 def __init__(
136 self, # pylint: disable=unused-argument
137 custom_error_map: Optional[Mapping[KeyType, ValueType]] = None,
138 *,
139 default_error: Optional[ValueType] = None,
140 **kwargs: Any,
141 ) -> None:
142 self._custom_error_map = custom_error_map or {}
143 self._default_error = default_error
145 def get(self, key: KeyType) -> Optional[ValueType]:
146 ret = self._custom_error_map.get(key)
147 if ret:
148 return ret
149 return self._default_error
152def map_error(
153 status_code: int, response: _HttpResponseCommonAPI, error_map: Mapping[int, Type[HttpResponseError]]
154) -> None:
155 if not error_map:
156 return
157 error_type = error_map.get(status_code)
158 if not error_type:
159 return
160 error = error_type(response=response)
161 raise error
164class ODataV4Format:
165 """Class to describe OData V4 error format.
167 http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793091
169 Example of JSON:
171 .. code-block:: json
173 {
174 "error": {
175 "code": "ValidationError",
176 "message": "One or more fields contain incorrect values: ",
177 "details": [
178 {
179 "code": "ValidationError",
180 "target": "representation",
181 "message": "Parsing error(s): String '' does not match regex pattern '^[^{}/ :]+(?: :\\\\d+)?$'.
182 Path 'host', line 1, position 297."
183 },
184 {
185 "code": "ValidationError",
186 "target": "representation",
187 "message": "Parsing error(s): The input OpenAPI file is not valid for the OpenAPI specificate
188 https: //github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
189 (schema https://github.com/OAI/OpenAPI-Specification/blob/master/schemas/v2.0/schema.json)."
190 }
191 ]
192 }
193 }
195 :param dict json_object: A Python dict representing a ODataV4 JSON
196 :ivar str ~.code: Its value is a service-defined error code.
197 This code serves as a sub-status for the HTTP error code specified in the response.
198 :ivar str message: Human-readable, language-dependent representation of the error.
199 :ivar str target: The target of the particular error (for example, the name of the property in error).
200 This field is optional and may be None.
201 :ivar list[ODataV4Format] details: Array of ODataV4Format instances that MUST contain name/value pairs
202 for code and message, and MAY contain a name/value pair for target, as described above.
203 :ivar dict innererror: An object. The contents of this object are service-defined.
204 Usually this object contains information that will help debug the service.
205 """
207 CODE_LABEL = "code"
208 MESSAGE_LABEL = "message"
209 TARGET_LABEL = "target"
210 DETAILS_LABEL = "details"
211 INNERERROR_LABEL = "innererror"
213 def __init__(self, json_object: Mapping[str, Any]) -> None:
214 if "error" in json_object:
215 json_object = json_object["error"]
216 cls: Type[ODataV4Format] = self.__class__
218 # Required fields, but assume they could be missing still to be robust
219 self.code: Optional[str] = json_object.get(cls.CODE_LABEL)
220 self.message: Optional[str] = json_object.get(cls.MESSAGE_LABEL)
222 if not (self.code or self.message):
223 raise ValueError("Impossible to extract code/message from received JSON:\n" + json.dumps(json_object))
225 # Optional fields
226 self.target: Optional[str] = json_object.get(cls.TARGET_LABEL)
228 # details is recursive of this very format
229 self.details: List[ODataV4Format] = []
230 for detail_node in json_object.get(cls.DETAILS_LABEL) or []:
231 try:
232 self.details.append(self.__class__(detail_node))
233 except Exception: # pylint: disable=broad-except
234 pass
236 self.innererror: Mapping[str, Any] = json_object.get(cls.INNERERROR_LABEL, {})
238 @property
239 def error(self: SelfODataV4Format) -> SelfODataV4Format:
240 import warnings
242 warnings.warn(
243 "error.error from azure exceptions is deprecated, just simply use 'error' once",
244 DeprecationWarning,
245 )
246 return self
248 def __str__(self) -> str:
249 return "({}) {}\n{}".format(self.code, self.message, self.message_details())
251 def message_details(self) -> str:
252 """Return a detailed string of the error.
254 :return: A string with the details of the error.
255 :rtype: str
256 """
257 error_str = "Code: {}".format(self.code)
258 error_str += "\nMessage: {}".format(self.message)
259 if self.target:
260 error_str += "\nTarget: {}".format(self.target)
262 if self.details:
263 error_str += "\nException Details:"
264 for error_obj in self.details:
265 # Indent for visibility
266 error_str += "\n".join("\t" + s for s in str(error_obj).splitlines())
268 if self.innererror:
269 error_str += "\nInner error: {}".format(json.dumps(self.innererror, indent=4))
270 return error_str
273class AzureError(Exception):
274 """Base exception for all errors.
276 :param object message: The message object stringified as 'message' attribute
277 :keyword error: The original exception if any
278 :paramtype error: Exception
280 :ivar inner_exception: The exception passed with the 'error' kwarg
281 :vartype inner_exception: Exception
282 :ivar exc_type: The exc_type from sys.exc_info()
283 :ivar exc_value: The exc_value from sys.exc_info()
284 :ivar exc_traceback: The exc_traceback from sys.exc_info()
285 :ivar exc_msg: A string formatting of message parameter, exc_type and exc_value
286 :ivar str message: A stringified version of the message parameter
287 :ivar str continuation_token: A token reference to continue an incomplete operation. This value is optional
288 and will be `None` where continuation is either unavailable or not applicable.
289 """
291 def __init__(self, message: Optional[object], *args: Any, **kwargs: Any) -> None:
292 self.inner_exception: Optional[BaseException] = kwargs.get("error")
294 exc_info = sys.exc_info()
295 self.exc_type: Optional[Type[Any]] = exc_info[0]
296 self.exc_value: Optional[BaseException] = exc_info[1]
297 self.exc_traceback: Optional[TracebackType] = exc_info[2]
299 self.exc_type = self.exc_type if self.exc_type else type(self.inner_exception)
300 self.exc_msg: str = "{}, {}: {}".format(message, self.exc_type.__name__, self.exc_value)
301 self.message: str = str(message)
302 self.continuation_token: Optional[str] = kwargs.get("continuation_token")
303 super(AzureError, self).__init__(self.message, *args)
305 def raise_with_traceback(self) -> None:
306 """Raise the exception with the existing traceback.
308 .. deprecated:: 1.22.0
309 This method is deprecated as we don't support Python 2 anymore. Use raise/from instead.
310 """
311 try:
312 raise super(AzureError, self).with_traceback(self.exc_traceback) # pylint: disable=raise-missing-from
313 except AttributeError:
314 self.__traceback__: Optional[TracebackType] = self.exc_traceback
315 raise self # pylint: disable=raise-missing-from
318class ServiceRequestError(AzureError):
319 """An error occurred while attempt to make a request to the service.
320 No request was sent.
321 """
324class ServiceResponseError(AzureError):
325 """The request was sent, but the client failed to understand the response.
326 The connection may have timed out. These errors can be retried for idempotent or
327 safe operations"""
330class ServiceRequestTimeoutError(ServiceRequestError):
331 """Error raised when timeout happens"""
334class ServiceResponseTimeoutError(ServiceResponseError):
335 """Error raised when timeout happens"""
338class HttpResponseError(AzureError):
339 """A request was made, and a non-success status code was received from the service.
341 :param object message: The message object stringified as 'message' attribute
342 :param response: The response that triggered the exception.
343 :type response: ~azure.core.pipeline.transport.HttpResponse or ~azure.core.pipeline.transport.AsyncHttpResponse
345 :ivar reason: The HTTP response reason
346 :vartype reason: str
347 :ivar status_code: HttpResponse's status code
348 :vartype status_code: int
349 :ivar response: The response that triggered the exception.
350 :vartype response: ~azure.core.pipeline.transport.HttpResponse or ~azure.core.pipeline.transport.AsyncHttpResponse
351 :ivar model: The request body/response body model
352 :vartype model: ~msrest.serialization.Model
353 :ivar error: The formatted error
354 :vartype error: ODataV4Format
355 """
357 def __init__(
358 self, message: Optional[object] = None, response: Optional[_HttpResponseCommonAPI] = None, **kwargs: Any
359 ) -> None:
360 # Don't want to document this one yet.
361 error_format = kwargs.get("error_format", ODataV4Format)
363 self.reason: Optional[str] = None
364 self.status_code: Optional[int] = None
365 self.response: Optional[_HttpResponseCommonAPI] = response
366 if response:
367 self.reason = response.reason
368 self.status_code = response.status_code
370 # old autorest are setting "error" before calling __init__, so it might be there already
371 # transferring into self.model
372 model: Optional[Any] = kwargs.pop("model", None)
373 self.model: Optional[Any]
374 if model is not None: # autorest v5
375 self.model = model
376 else: # autorest azure-core, for KV 1.0, Storage 12.0, etc.
377 self.model = getattr(self, "error", None)
378 self.error: Optional[ODataV4Format] = self._parse_odata_body(error_format, response)
380 # By priority, message is:
381 # - odatav4 message, OR
382 # - parameter "message", OR
383 # - generic meassage using "reason"
384 if self.error:
385 message = str(self.error)
386 else:
387 message = message or "Operation returned an invalid status '{}'".format(self.reason)
389 super(HttpResponseError, self).__init__(message=message, **kwargs)
391 @staticmethod
392 def _parse_odata_body(
393 error_format: Type[ODataV4Format], response: Optional[_HttpResponseCommonAPI]
394 ) -> Optional[ODataV4Format]:
395 try:
396 # https://github.com/python/mypy/issues/14743#issuecomment-1664725053
397 odata_json = json.loads(response.text()) # type: ignore
398 return error_format(odata_json)
399 except Exception: # pylint: disable=broad-except
400 # If the body is not JSON valid, just stop now
401 pass
402 return None
404 def __str__(self) -> str:
405 retval = super(HttpResponseError, self).__str__()
406 try:
407 # https://github.com/python/mypy/issues/14743#issuecomment-1664725053
408 body = self.response.text() # type: ignore
409 if body and not self.error:
410 return "{}\nContent: {}".format(retval, body)[:2048]
411 except Exception: # pylint: disable=broad-except
412 pass
413 return retval
416class DecodeError(HttpResponseError):
417 """Error raised during response deserialization."""
420class IncompleteReadError(DecodeError):
421 """Error raised if peer closes the connection before we have received the complete message body."""
424class ResourceExistsError(HttpResponseError):
425 """An error response with status code 4xx.
426 This will not be raised directly by the Azure core pipeline."""
429class ResourceNotFoundError(HttpResponseError):
430 """An error response, typically triggered by a 412 response (for update) or 404 (for get/post)"""
433class ClientAuthenticationError(HttpResponseError):
434 """An error response with status code 4xx.
435 This will not be raised directly by the Azure core pipeline."""
438class ResourceModifiedError(HttpResponseError):
439 """An error response with status code 4xx, typically 412 Conflict.
440 This will not be raised directly by the Azure core pipeline."""
443class ResourceNotModifiedError(HttpResponseError):
444 """An error response with status code 304.
445 This will not be raised directly by the Azure core pipeline."""
448class TooManyRedirectsError(HttpResponseError, Generic[HTTPRequestType, HTTPResponseType]):
449 """Reached the maximum number of redirect attempts.
451 :param history: The history of requests made while trying to fulfill the request.
452 :type history: list[~azure.core.pipeline.policies.RequestHistory]
453 """
455 def __init__(
456 self, history: "List[RequestHistory[HTTPRequestType, HTTPResponseType]]", *args: Any, **kwargs: Any
457 ) -> None:
458 self.history = history
459 message = "Reached maximum redirect attempts."
460 super(TooManyRedirectsError, self).__init__(message, *args, **kwargs)
463class ODataV4Error(HttpResponseError):
464 """An HTTP response error where the JSON is decoded as OData V4 error format.
466 http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793091
468 :param ~azure.core.rest.HttpResponse response: The response object.
469 :ivar dict odata_json: The parsed JSON body as attribute for convenience.
470 :ivar str ~.code: Its value is a service-defined error code.
471 This code serves as a sub-status for the HTTP error code specified in the response.
472 :ivar str message: Human-readable, language-dependent representation of the error.
473 :ivar str target: The target of the particular error (for example, the name of the property in error).
474 This field is optional and may be None.
475 :ivar list[ODataV4Format] details: Array of ODataV4Format instances that MUST contain name/value pairs
476 for code and message, and MAY contain a name/value pair for target, as described above.
477 :ivar dict innererror: An object. The contents of this object are service-defined.
478 Usually this object contains information that will help debug the service.
479 """
481 _ERROR_FORMAT = ODataV4Format
483 def __init__(self, response: _HttpResponseCommonAPI, **kwargs: Any) -> None:
484 # Ensure field are declared, whatever can happen afterwards
485 self.odata_json: Optional[Dict[str, Any]] = None
486 try:
487 self.odata_json = json.loads(response.text())
488 odata_message = self.odata_json.setdefault("error", {}).get("message")
489 except Exception: # pylint: disable=broad-except
490 # If the body is not JSON valid, just stop now
491 odata_message = None
493 self.code: Optional[str] = None
494 message: Optional[str] = kwargs.get("message", odata_message)
495 self.target: Optional[str] = None
496 self.details: Optional[List[Any]] = []
497 self.innererror: Optional[Mapping[str, Any]] = {}
499 if message and "message" not in kwargs:
500 kwargs["message"] = message
502 super(ODataV4Error, self).__init__(response=response, **kwargs)
504 self._error_format: Optional[Union[str, ODataV4Format]] = None
505 if self.odata_json:
506 try:
507 error_node = self.odata_json["error"]
508 self._error_format = self._ERROR_FORMAT(error_node)
509 self.__dict__.update({k: v for k, v in self._error_format.__dict__.items() if v is not None})
510 except Exception: # pylint: disable=broad-except
511 _LOGGER.info("Received error message was not valid OdataV4 format.")
512 self._error_format = "JSON was invalid for format " + str(self._ERROR_FORMAT)
514 def __str__(self) -> str:
515 if self._error_format:
516 return str(self._error_format)
517 return super(ODataV4Error, self).__str__()
520class StreamConsumedError(AzureError):
521 """Error thrown if you try to access the stream of a response once consumed.
523 It is thrown if you try to read / stream an ~azure.core.rest.HttpResponse or
524 ~azure.core.rest.AsyncHttpResponse once the response's stream has been consumed.
526 :param response: The response that triggered the exception.
527 :type response: ~azure.core.rest.HttpResponse or ~azure.core.rest.AsyncHttpResponse
528 """
530 def __init__(self, response: _HttpResponseCommonAPI) -> None:
531 message = (
532 "You are attempting to read or stream the content from request {}. "
533 "You have likely already consumed this stream, so it can not be accessed anymore.".format(response.request)
534 )
535 super(StreamConsumedError, self).__init__(message)
538class StreamClosedError(AzureError):
539 """Error thrown if you try to access the stream of a response once closed.
541 It is thrown if you try to read / stream an ~azure.core.rest.HttpResponse or
542 ~azure.core.rest.AsyncHttpResponse once the response's stream has been closed.
544 :param response: The response that triggered the exception.
545 :type response: ~azure.core.rest.HttpResponse or ~azure.core.rest.AsyncHttpResponse
546 """
548 def __init__(self, response: _HttpResponseCommonAPI) -> None:
549 message = (
550 "The content for response from request {} can no longer be read or streamed, since the "
551 "response has already been closed.".format(response.request)
552 )
553 super(StreamClosedError, self).__init__(message)
556class ResponseNotReadError(AzureError):
557 """Error thrown if you try to access a response's content without reading first.
559 It is thrown if you try to access an ~azure.core.rest.HttpResponse or
560 ~azure.core.rest.AsyncHttpResponse's content without first reading the response's bytes in first.
562 :param response: The response that triggered the exception.
563 :type response: ~azure.core.rest.HttpResponse or ~azure.core.rest.AsyncHttpResponse
564 """
566 def __init__(self, response: _HttpResponseCommonAPI) -> None:
567 message = (
568 "You have not read in the bytes for the response from request {}. "
569 "Call .read() on the response first.".format(response.request)
570 )
571 super(ResponseNotReadError, self).__init__(message)
574class SerializationError(ValueError):
575 """Raised if an error is encountered during serialization."""
578class DeserializationError(ValueError):
579 """Raised if an error is encountered during deserialization."""