You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

external_api.py 4.2KB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import re
  2. import sys
  3. from typing import Any
  4. from flask import current_app, got_request_exception
  5. from flask_restx import Api
  6. from werkzeug.datastructures import Headers
  7. from werkzeug.exceptions import HTTPException
  8. from werkzeug.http import HTTP_STATUS_CODES
  9. from core.errors.error import AppInvokeQuotaExceededError
  10. def http_status_message(code):
  11. """Maps an HTTP status code to the textual status"""
  12. return HTTP_STATUS_CODES.get(code, "")
  13. class ExternalApi(Api):
  14. def handle_error(self, e):
  15. """Error handler for the API transforms a raised exception into a Flask
  16. response, with the appropriate HTTP status code and body.
  17. :param e: the raised Exception object
  18. :type e: Exception
  19. """
  20. got_request_exception.send(current_app, exception=e)
  21. headers = Headers()
  22. if isinstance(e, HTTPException):
  23. if e.response is not None:
  24. resp = e.get_response()
  25. return resp
  26. status_code = e.code
  27. default_data = {
  28. "code": re.sub(r"(?<!^)(?=[A-Z])", "_", type(e).__name__).lower(),
  29. "message": getattr(e, "description", http_status_message(status_code)),
  30. "status": status_code,
  31. }
  32. if (
  33. default_data["message"]
  34. and default_data["message"] == "Failed to decode JSON object: Expecting value: line 1 column 1 (char 0)"
  35. ):
  36. default_data["message"] = "Invalid JSON payload received or JSON payload is empty."
  37. headers = e.get_response().headers
  38. elif isinstance(e, ValueError):
  39. status_code = 400
  40. default_data = {
  41. "code": "invalid_param",
  42. "message": str(e),
  43. "status": status_code,
  44. }
  45. elif isinstance(e, AppInvokeQuotaExceededError):
  46. status_code = 429
  47. default_data = {
  48. "code": "too_many_requests",
  49. "message": str(e),
  50. "status": status_code,
  51. }
  52. else:
  53. status_code = 500
  54. default_data = {
  55. "message": http_status_message(status_code),
  56. }
  57. # Werkzeug exceptions generate a content-length header which is added
  58. # to the response in addition to the actual content-length header
  59. # https://github.com/flask-restful/flask-restful/issues/534
  60. remove_headers = ("Content-Length",)
  61. for header in remove_headers:
  62. headers.pop(header, None)
  63. data = getattr(e, "data", default_data)
  64. # record the exception in the logs when we have a server error of status code: 500
  65. if status_code and status_code >= 500:
  66. exc_info: Any = sys.exc_info()
  67. if exc_info[1] is None:
  68. exc_info = None
  69. current_app.log_exception(exc_info)
  70. if status_code == 406 and self.default_mediatype is None:
  71. # if we are handling NotAcceptable (406), make sure that
  72. # make_response uses a representation we support as the
  73. # default mediatype (so that make_response doesn't throw
  74. # another NotAcceptable error).
  75. supported_mediatypes = list(self.representations.keys()) # only supported application/json
  76. fallback_mediatype = supported_mediatypes[0] if supported_mediatypes else "text/plain"
  77. data = {"code": "not_acceptable", "message": data.get("message")}
  78. resp = self.make_response(data, status_code, headers, fallback_mediatype=fallback_mediatype)
  79. elif status_code == 400:
  80. if isinstance(data.get("message"), dict):
  81. param_key, param_value = list(data.get("message", {}).items())[0]
  82. data = {"code": "invalid_param", "message": param_value, "params": param_key}
  83. else:
  84. if "code" not in data:
  85. data["code"] = "unknown"
  86. resp = self.make_response(data, status_code, headers)
  87. else:
  88. if "code" not in data:
  89. data["code"] = "unknown"
  90. resp = self.make_response(data, status_code, headers)
  91. if status_code == 401:
  92. resp = self.unauthorized(resp)
  93. return resp