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

209 statements  

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 

30 

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 

47 

48_LOGGER = logging.getLogger(__name__) 

49 

50if TYPE_CHECKING: 

51 from azure.core.pipeline.policies import RequestHistory 

52 

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") 

59 

60 

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] 

81 

82 

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. 

86 

87 .. note:: This method is deprecated since we don't support Python 2 anymore. Use raise/from instead. 

88 

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 

104 

105 

106@runtime_checkable 

107class _HttpResponseCommonAPI(Protocol): 

108 """Protocol used by exceptions for HTTP response. 

109 

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 """ 

113 

114 @property 

115 def reason(self) -> Optional[str]: ... 

116 

117 @property 

118 def status_code(self) -> Optional[int]: ... 

119 

120 def text(self) -> str: ... 

121 

122 @property 

123 def request(self) -> object: # object as type, since all we need is str() on it 

124 ... 

125 

126 

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 

130 

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 """ 

134 

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 

144 

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 

150 

151 

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 

162 

163 

164class ODataV4Format: 

165 """Class to describe OData V4 error format. 

166 

167 http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793091 

168 

169 Example of JSON: 

170 

171 .. code-block:: json 

172 

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 } 

194 

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 """ 

206 

207 CODE_LABEL = "code" 

208 MESSAGE_LABEL = "message" 

209 TARGET_LABEL = "target" 

210 DETAILS_LABEL = "details" 

211 INNERERROR_LABEL = "innererror" 

212 

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__ 

217 

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) 

221 

222 if not (self.code or self.message): 

223 raise ValueError("Impossible to extract code/message from received JSON:\n" + json.dumps(json_object)) 

224 

225 # Optional fields 

226 self.target: Optional[str] = json_object.get(cls.TARGET_LABEL) 

227 

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 

235 

236 self.innererror: Mapping[str, Any] = json_object.get(cls.INNERERROR_LABEL, {}) 

237 

238 @property 

239 def error(self: SelfODataV4Format) -> SelfODataV4Format: 

240 import warnings 

241 

242 warnings.warn( 

243 "error.error from azure exceptions is deprecated, just simply use 'error' once", 

244 DeprecationWarning, 

245 ) 

246 return self 

247 

248 def __str__(self) -> str: 

249 return "({}) {}\n{}".format(self.code, self.message, self.message_details()) 

250 

251 def message_details(self) -> str: 

252 """Return a detailed string of the error. 

253 

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) 

261 

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()) 

267 

268 if self.innererror: 

269 error_str += "\nInner error: {}".format(json.dumps(self.innererror, indent=4)) 

270 return error_str 

271 

272 

273class AzureError(Exception): 

274 """Base exception for all errors. 

275 

276 :param object message: The message object stringified as 'message' attribute 

277 :keyword error: The original exception if any 

278 :paramtype error: Exception 

279 

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 """ 

290 

291 def __init__(self, message: Optional[object], *args: Any, **kwargs: Any) -> None: 

292 self.inner_exception: Optional[BaseException] = kwargs.get("error") 

293 

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] 

298 

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) 

304 

305 def raise_with_traceback(self) -> None: 

306 """Raise the exception with the existing traceback. 

307 

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 

316 

317 

318class ServiceRequestError(AzureError): 

319 """An error occurred while attempt to make a request to the service. 

320 No request was sent. 

321 """ 

322 

323 

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""" 

328 

329 

330class ServiceRequestTimeoutError(ServiceRequestError): 

331 """Error raised when timeout happens""" 

332 

333 

334class ServiceResponseTimeoutError(ServiceResponseError): 

335 """Error raised when timeout happens""" 

336 

337 

338class HttpResponseError(AzureError): 

339 """A request was made, and a non-success status code was received from the service. 

340 

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 

344 

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 """ 

356 

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) 

362 

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 

369 

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) 

379 

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) 

388 

389 super(HttpResponseError, self).__init__(message=message, **kwargs) 

390 

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 

403 

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 

414 

415 

416class DecodeError(HttpResponseError): 

417 """Error raised during response deserialization.""" 

418 

419 

420class IncompleteReadError(DecodeError): 

421 """Error raised if peer closes the connection before we have received the complete message body.""" 

422 

423 

424class ResourceExistsError(HttpResponseError): 

425 """An error response with status code 4xx. 

426 This will not be raised directly by the Azure core pipeline.""" 

427 

428 

429class ResourceNotFoundError(HttpResponseError): 

430 """An error response, typically triggered by a 412 response (for update) or 404 (for get/post)""" 

431 

432 

433class ClientAuthenticationError(HttpResponseError): 

434 """An error response with status code 4xx. 

435 This will not be raised directly by the Azure core pipeline.""" 

436 

437 

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.""" 

441 

442 

443class ResourceNotModifiedError(HttpResponseError): 

444 """An error response with status code 304. 

445 This will not be raised directly by the Azure core pipeline.""" 

446 

447 

448class TooManyRedirectsError(HttpResponseError, Generic[HTTPRequestType, HTTPResponseType]): 

449 """Reached the maximum number of redirect attempts. 

450 

451 :param history: The history of requests made while trying to fulfill the request. 

452 :type history: list[~azure.core.pipeline.policies.RequestHistory] 

453 """ 

454 

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) 

461 

462 

463class ODataV4Error(HttpResponseError): 

464 """An HTTP response error where the JSON is decoded as OData V4 error format. 

465 

466 http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793091 

467 

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 """ 

480 

481 _ERROR_FORMAT = ODataV4Format 

482 

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 

492 

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]] = {} 

498 

499 if message and "message" not in kwargs: 

500 kwargs["message"] = message 

501 

502 super(ODataV4Error, self).__init__(response=response, **kwargs) 

503 

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) 

513 

514 def __str__(self) -> str: 

515 if self._error_format: 

516 return str(self._error_format) 

517 return super(ODataV4Error, self).__str__() 

518 

519 

520class StreamConsumedError(AzureError): 

521 """Error thrown if you try to access the stream of a response once consumed. 

522 

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. 

525 

526 :param response: The response that triggered the exception. 

527 :type response: ~azure.core.rest.HttpResponse or ~azure.core.rest.AsyncHttpResponse 

528 """ 

529 

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) 

536 

537 

538class StreamClosedError(AzureError): 

539 """Error thrown if you try to access the stream of a response once closed. 

540 

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. 

543 

544 :param response: The response that triggered the exception. 

545 :type response: ~azure.core.rest.HttpResponse or ~azure.core.rest.AsyncHttpResponse 

546 """ 

547 

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) 

554 

555 

556class ResponseNotReadError(AzureError): 

557 """Error thrown if you try to access a response's content without reading first. 

558 

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. 

561 

562 :param response: The response that triggered the exception. 

563 :type response: ~azure.core.rest.HttpResponse or ~azure.core.rest.AsyncHttpResponse 

564 """ 

565 

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) 

572 

573 

574class SerializationError(ValueError): 

575 """Raised if an error is encountered during serialization.""" 

576 

577 

578class DeserializationError(ValueError): 

579 """Raised if an error is encountered during deserialization."""