Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/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

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

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, 

154 response: _HttpResponseCommonAPI, 

155 error_map: Mapping[int, Type[HttpResponseError]], 

156) -> None: 

157 if not error_map: 

158 return 

159 error_type = error_map.get(status_code) 

160 if not error_type: 

161 return 

162 error = error_type(response=response) 

163 raise error 

164 

165 

166class ODataV4Format: 

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

168 

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

170 

171 Example of JSON: 

172 

173 .. code-block:: json 

174 

175 { 

176 "error": { 

177 "code": "ValidationError", 

178 "message": "One or more fields contain incorrect values: ", 

179 "details": [ 

180 { 

181 "code": "ValidationError", 

182 "target": "representation", 

183 "message": "Parsing error(s): String '' does not match regex pattern '^[^{}/ :]+(?: :\\\\d+)?$'. 

184 Path 'host', line 1, position 297." 

185 }, 

186 { 

187 "code": "ValidationError", 

188 "target": "representation", 

189 "message": "Parsing error(s): The input OpenAPI file is not valid for the OpenAPI specificate 

190 https: //github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md 

191 (schema https://github.com/OAI/OpenAPI-Specification/blob/master/schemas/v2.0/schema.json)." 

192 } 

193 ] 

194 } 

195 } 

196 

197 :param dict json_object: A Python dict representing a ODataV4 JSON 

198 :ivar str ~.code: Its value is a service-defined error code. 

199 This code serves as a sub-status for the HTTP error code specified in the response. 

200 :ivar str message: Human-readable, language-dependent representation of the error. 

201 :ivar str target: The target of the particular error (for example, the name of the property in error). 

202 This field is optional and may be None. 

203 :ivar list[ODataV4Format] details: Array of ODataV4Format instances that MUST contain name/value pairs 

204 for code and message, and MAY contain a name/value pair for target, as described above. 

205 :ivar dict innererror: An object. The contents of this object are service-defined. 

206 Usually this object contains information that will help debug the service. 

207 """ 

208 

209 CODE_LABEL = "code" 

210 MESSAGE_LABEL = "message" 

211 TARGET_LABEL = "target" 

212 DETAILS_LABEL = "details" 

213 INNERERROR_LABEL = "innererror" 

214 

215 def __init__(self, json_object: Mapping[str, Any]) -> None: 

216 if "error" in json_object: 

217 json_object = json_object["error"] 

218 cls: Type[ODataV4Format] = self.__class__ 

219 

220 # Required fields, but assume they could be missing still to be robust 

221 self.code: Optional[str] = json_object.get(cls.CODE_LABEL) 

222 self.message: Optional[str] = json_object.get(cls.MESSAGE_LABEL) 

223 

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

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

226 

227 # Optional fields 

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

229 

230 # details is recursive of this very format 

231 self.details: List[ODataV4Format] = [] 

232 for detail_node in json_object.get(cls.DETAILS_LABEL) or []: 

233 try: 

234 self.details.append(self.__class__(detail_node)) 

235 except Exception: # pylint: disable=broad-except 

236 pass 

237 

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

239 

240 @property 

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

242 import warnings 

243 

244 warnings.warn( 

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

246 DeprecationWarning, 

247 ) 

248 return self 

249 

250 def __str__(self) -> str: 

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

252 

253 def message_details(self) -> str: 

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

255 

256 :return: A string with the details of the error. 

257 :rtype: str 

258 """ 

259 error_str = "Code: {}".format(self.code) 

260 error_str += "\nMessage: {}".format(self.message) 

261 if self.target: 

262 error_str += "\nTarget: {}".format(self.target) 

263 

264 if self.details: 

265 error_str += "\nException Details:" 

266 for error_obj in self.details: 

267 # Indent for visibility 

268 error_str += "\n".join("\t" + s for s in str(error_obj).splitlines()) 

269 

270 if self.innererror: 

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

272 return error_str 

273 

274 

275class AzureError(Exception): 

276 """Base exception for all errors. 

277 

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

279 :keyword error: The original exception if any 

280 :paramtype error: Exception 

281 

282 :ivar inner_exception: The exception passed with the 'error' kwarg 

283 :vartype inner_exception: Exception 

284 :ivar exc_type: The exc_type from sys.exc_info() 

285 :ivar exc_value: The exc_value from sys.exc_info() 

286 :ivar exc_traceback: The exc_traceback from sys.exc_info() 

287 :ivar exc_msg: A string formatting of message parameter, exc_type and exc_value 

288 :ivar str message: A stringified version of the message parameter 

289 :ivar str continuation_token: A token reference to continue an incomplete operation. This value is optional 

290 and will be `None` where continuation is either unavailable or not applicable. 

291 """ 

292 

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

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

295 

296 exc_info = sys.exc_info() 

297 self.exc_type: Optional[Type[Any]] = exc_info[0] 

298 self.exc_value: Optional[BaseException] = exc_info[1] 

299 self.exc_traceback: Optional[TracebackType] = exc_info[2] 

300 

301 self.exc_type = self.exc_type if self.exc_type else type(self.inner_exception) 

302 self.exc_msg: str = "{}, {}: {}".format(message, self.exc_type.__name__, self.exc_value) 

303 self.message: str = str(message) 

304 self.continuation_token: Optional[str] = kwargs.get("continuation_token") 

305 super(AzureError, self).__init__(self.message, *args) 

306 

307 def raise_with_traceback(self) -> None: 

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

309 

310 .. deprecated:: 1.22.0 

311 This method is deprecated as we don't support Python 2 anymore. Use raise/from instead. 

312 """ 

313 try: 

314 raise super(AzureError, self).with_traceback(self.exc_traceback) 

315 except AttributeError: 

316 self.__traceback__: Optional[TracebackType] = self.exc_traceback 

317 raise self # pylint: disable=raise-missing-from 

318 

319 

320class ServiceRequestError(AzureError): 

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

322 No request was sent. 

323 """ 

324 

325 

326class ServiceResponseError(AzureError): 

327 """The request was sent, but the client failed to understand the response. 

328 The connection may have timed out. These errors can be retried for idempotent or 

329 safe operations""" 

330 

331 

332class ServiceRequestTimeoutError(ServiceRequestError): 

333 """Error raised when timeout happens""" 

334 

335 

336class ServiceResponseTimeoutError(ServiceResponseError): 

337 """Error raised when timeout happens""" 

338 

339 

340class HttpResponseError(AzureError): 

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

342 

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

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

345 :type response: ~azure.core.pipeline.transport.HttpResponse or ~azure.core.pipeline.transport.AsyncHttpResponse 

346 

347 :ivar reason: The HTTP response reason 

348 :vartype reason: str 

349 :ivar status_code: HttpResponse's status code 

350 :vartype status_code: int 

351 :ivar response: The response that triggered the exception. 

352 :vartype response: ~azure.core.pipeline.transport.HttpResponse or ~azure.core.pipeline.transport.AsyncHttpResponse 

353 :ivar model: The request body/response body model 

354 :vartype model: ~msrest.serialization.Model 

355 :ivar error: The formatted error 

356 :vartype error: ODataV4Format 

357 """ 

358 

359 def __init__( 

360 self, 

361 message: Optional[object] = None, 

362 response: Optional[_HttpResponseCommonAPI] = None, 

363 **kwargs: Any, 

364 ) -> None: 

365 # Don't want to document this one yet. 

366 error_format = kwargs.get("error_format", ODataV4Format) 

367 

368 self.reason: Optional[str] = None 

369 self.status_code: Optional[int] = None 

370 self.response: Optional[_HttpResponseCommonAPI] = response 

371 if response: 

372 self.reason = response.reason 

373 self.status_code = response.status_code 

374 

375 # old autorest are setting "error" before calling __init__, so it might be there already 

376 # transferring into self.model 

377 model: Optional[Any] = kwargs.pop("model", None) 

378 self.model: Optional[Any] 

379 if model is not None: # autorest v5 

380 self.model = model 

381 else: # autorest azure-core, for KV 1.0, Storage 12.0, etc. 

382 self.model = getattr(self, "error", None) 

383 self.error: Optional[ODataV4Format] = self._parse_odata_body(error_format, response) 

384 

385 # By priority, message is: 

386 # - odatav4 message, OR 

387 # - parameter "message", OR 

388 # - generic meassage using "reason" 

389 if self.error: 

390 message = str(self.error) 

391 else: 

392 message = message or "Operation returned an invalid status '{}'".format(self.reason) 

393 

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

395 

396 @staticmethod 

397 def _parse_odata_body( 

398 error_format: Type[ODataV4Format], response: Optional[_HttpResponseCommonAPI] 

399 ) -> Optional[ODataV4Format]: 

400 try: 

401 # https://github.com/python/mypy/issues/14743#issuecomment-1664725053 

402 odata_json = json.loads(response.text()) # type: ignore 

403 return error_format(odata_json) 

404 except Exception: # pylint: disable=broad-except 

405 # If the body is not JSON valid, just stop now 

406 pass 

407 return None 

408 

409 def __str__(self) -> str: 

410 retval = super(HttpResponseError, self).__str__() 

411 try: 

412 # https://github.com/python/mypy/issues/14743#issuecomment-1664725053 

413 body = self.response.text() # type: ignore 

414 if body and not self.error: 

415 return "{}\nContent: {}".format(retval, body)[:2048] 

416 except Exception: # pylint: disable=broad-except 

417 pass 

418 return retval 

419 

420 

421class DecodeError(HttpResponseError): 

422 """Error raised during response deserialization.""" 

423 

424 

425class IncompleteReadError(DecodeError): 

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

427 

428 

429class ResourceExistsError(HttpResponseError): 

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

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

432 

433 

434class ResourceNotFoundError(HttpResponseError): 

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

436 

437 

438class ClientAuthenticationError(HttpResponseError): 

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

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

441 

442 

443class ResourceModifiedError(HttpResponseError): 

444 """An error response with status code 4xx, typically 412 Conflict. 

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

446 

447 

448class ResourceNotModifiedError(HttpResponseError): 

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

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

451 

452 

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

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

455 

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

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

458 """ 

459 

460 def __init__( 

461 self, 

462 history: "List[RequestHistory[HTTPRequestType, HTTPResponseType]]", 

463 *args: Any, 

464 **kwargs: Any, 

465 ) -> None: 

466 self.history = history 

467 message = "Reached maximum redirect attempts." 

468 super(TooManyRedirectsError, self).__init__(message, *args, **kwargs) 

469 

470 

471class ODataV4Error(HttpResponseError): 

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

473 

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

475 

476 :param ~azure.core.rest.HttpResponse response: The response object. 

477 :ivar dict odata_json: The parsed JSON body as attribute for convenience. 

478 :ivar str ~.code: Its value is a service-defined error code. 

479 This code serves as a sub-status for the HTTP error code specified in the response. 

480 :ivar str message: Human-readable, language-dependent representation of the error. 

481 :ivar str target: The target of the particular error (for example, the name of the property in error). 

482 This field is optional and may be None. 

483 :ivar list[ODataV4Format] details: Array of ODataV4Format instances that MUST contain name/value pairs 

484 for code and message, and MAY contain a name/value pair for target, as described above. 

485 :ivar dict innererror: An object. The contents of this object are service-defined. 

486 Usually this object contains information that will help debug the service. 

487 """ 

488 

489 _ERROR_FORMAT = ODataV4Format 

490 

491 def __init__(self, response: _HttpResponseCommonAPI, **kwargs: Any) -> None: 

492 # Ensure field are declared, whatever can happen afterwards 

493 self.odata_json: Optional[Dict[str, Any]] = None 

494 try: 

495 self.odata_json = json.loads(response.text()) 

496 odata_message = self.odata_json.setdefault("error", {}).get("message") 

497 except Exception: # pylint: disable=broad-except 

498 # If the body is not JSON valid, just stop now 

499 odata_message = None 

500 

501 self.code: Optional[str] = None 

502 message: Optional[str] = kwargs.get("message", odata_message) 

503 self.target: Optional[str] = None 

504 self.details: Optional[List[Any]] = [] 

505 self.innererror: Optional[Mapping[str, Any]] = {} 

506 

507 if message and "message" not in kwargs: 

508 kwargs["message"] = message 

509 

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

511 

512 self._error_format: Optional[Union[str, ODataV4Format]] = None 

513 if self.odata_json: 

514 try: 

515 error_node = self.odata_json["error"] 

516 self._error_format = self._ERROR_FORMAT(error_node) 

517 self.__dict__.update({k: v for k, v in self._error_format.__dict__.items() if v is not None}) 

518 except Exception: # pylint: disable=broad-except 

519 _LOGGER.info("Received error message was not valid OdataV4 format.") 

520 self._error_format = "JSON was invalid for format " + str(self._ERROR_FORMAT) 

521 

522 def __str__(self) -> str: 

523 if self._error_format: 

524 return str(self._error_format) 

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

526 

527 

528class StreamConsumedError(AzureError): 

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

530 

531 It is thrown if you try to read / stream an ~azure.core.rest.HttpResponse or 

532 ~azure.core.rest.AsyncHttpResponse once the response's stream has been consumed. 

533 

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

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

536 """ 

537 

538 def __init__(self, response: _HttpResponseCommonAPI) -> None: 

539 message = ( 

540 "You are attempting to read or stream the content from request {}. " 

541 "You have likely already consumed this stream, so it can not be accessed anymore.".format(response.request) 

542 ) 

543 super(StreamConsumedError, self).__init__(message) 

544 

545 

546class StreamClosedError(AzureError): 

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

548 

549 It is thrown if you try to read / stream an ~azure.core.rest.HttpResponse or 

550 ~azure.core.rest.AsyncHttpResponse once the response's stream has been closed. 

551 

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

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

554 """ 

555 

556 def __init__(self, response: _HttpResponseCommonAPI) -> None: 

557 message = ( 

558 "The content for response from request {} can no longer be read or streamed, since the " 

559 "response has already been closed.".format(response.request) 

560 ) 

561 super(StreamClosedError, self).__init__(message) 

562 

563 

564class ResponseNotReadError(AzureError): 

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

566 

567 It is thrown if you try to access an ~azure.core.rest.HttpResponse or 

568 ~azure.core.rest.AsyncHttpResponse's content without first reading the response's bytes in first. 

569 

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

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

572 """ 

573 

574 def __init__(self, response: _HttpResponseCommonAPI) -> None: 

575 message = ( 

576 "You have not read in the bytes for the response from request {}. " 

577 "Call .read() on the response first.".format(response.request) 

578 ) 

579 super(ResponseNotReadError, self).__init__(message) 

580 

581 

582class SerializationError(ValueError): 

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

584 

585 

586class DeserializationError(ValueError): 

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