external_api.py 4.5 KB

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