openapi_call.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import logging
  2. import os
  3. import urllib.parse
  4. from typing import Dict
  5. import requests
  6. from app.schemas.tool.authentication import Authentication, AuthenticationType
  7. from app.schemas.tool.action import ActionMethod, ActionBodyType, ActionParam
  8. # This function code from the Open Source Project TaskingAI.
  9. # The original code can be found at: https://github.com/TaskingAI/TaskingAI
  10. def _prepare_headers(authentication: Authentication, extra_headers: Dict) -> Dict:
  11. """
  12. Prepares the headers for an HTTP request including authentication and additional headers.
  13. :param authentication: An Authentication object containing the authentication details.
  14. :param extra_headers: A dictionary of additional headers to include in the request.
  15. :return: A dictionary of headers for the HTTP request.
  16. """
  17. headers = {}
  18. if extra_headers:
  19. headers.update(extra_headers)
  20. if authentication:
  21. if authentication.type == AuthenticationType.basic:
  22. # Basic Authentication: Assume the secret is a base64 encoded string
  23. headers["Authorization"] = f"Basic {authentication.secret}"
  24. elif authentication.type == AuthenticationType.bearer:
  25. # Bearer Authentication: Add the secret as a bearer token
  26. headers["Authorization"] = f"Bearer {authentication.secret}"
  27. elif authentication.type == AuthenticationType.custom:
  28. # Custom Authentication: Return the custom content as headers
  29. headers.update(authentication.content)
  30. return headers
  31. # This function code from the Open Source Project TaskingAI.
  32. # The original code can be found at: https://github.com/TaskingAI/TaskingAI
  33. def _process_parameters(schema: Dict[str, ActionParam], parameters: Dict) -> Dict:
  34. """
  35. Processes parameters based on their schema and provided values.
  36. :param schema: A dictionary representing the parameter schema.
  37. :param parameters: A dictionary of provided parameter values.
  38. :return: A dictionary of processed parameters.
  39. """
  40. processed_params = {}
  41. for param_name, action_param in schema.items():
  42. param_value = None
  43. # Check for the presence of 'enum' in the parameter schema
  44. if action_param.is_single_value_enum():
  45. param_value = action_param.enum[0]
  46. elif param_name in parameters:
  47. param_value = parameters[param_name]
  48. if param_value is not None:
  49. processed_params[param_name] = param_value
  50. # todo: check if required
  51. # if action_param.required and param_value is None:
  52. # raise_http_error(ErrorCode.REQUEST_VALIDATION_ERROR, message=f"Missing required parameter {param_name}")
  53. return processed_params
  54. # This function code from the Open Source Project TaskingAI.
  55. # The original code can be found at: https://github.com/TaskingAI/TaskingAI
  56. def call_action_api(
  57. url: str,
  58. method: ActionMethod,
  59. path_param_schema: Dict[str, ActionParam],
  60. query_param_schema: Dict[str, ActionParam],
  61. body_type: ActionBodyType,
  62. body_param_schema: Dict[str, ActionParam],
  63. parameters: Dict,
  64. headers: Dict,
  65. authentication: Authentication,
  66. ) -> Dict:
  67. """
  68. Call an API according to OpenAPI schema.
  69. :param url: the URL of the API call
  70. :param method: the method of the API call
  71. :param path_param_schema: the path parameters schema
  72. :param query_param_schema: the query parameters schema
  73. :param body_type: the body type
  74. :param body_param_schema: the body parameters schema
  75. :param parameters: the parameters input by the user
  76. :param headers: the extra headers to be included in the API call
  77. :param authentication: the authentication of the action
  78. :return: Response from the API call
  79. """
  80. authentication.decrypt()
  81. # Update URL with path parameters
  82. if path_param_schema:
  83. path_params = _process_parameters(path_param_schema, parameters)
  84. for param_name, param_value in path_params.items():
  85. url = url.replace(f"{{{param_name}}}", urllib.parse.quote(str(param_value)))
  86. # Prepare query parameters
  87. query_params = {}
  88. if query_param_schema:
  89. query_params = _process_parameters(query_param_schema, parameters)
  90. # cast boolean values to string
  91. for param_name, param_value in query_params.items():
  92. if isinstance(param_value, bool):
  93. query_params[param_name] = str(param_value).lower()
  94. # Prepare body
  95. body = None
  96. if body_type != ActionBodyType.NONE:
  97. body = _process_parameters(body_param_schema, parameters)
  98. # Prepare headers
  99. prepared_headers = _prepare_headers(authentication, headers)
  100. # Making the API call
  101. try:
  102. request_kwargs = {"headers": prepared_headers}
  103. if query_params:
  104. request_kwargs["params"] = query_params
  105. if os.environ.get("HTTP_PROXY_URL"):
  106. request_kwargs["proxy"] = os.environ.get("HTTP_PROXY_URL")
  107. if body_type == ActionBodyType.JSON:
  108. request_kwargs["json"] = body
  109. prepared_headers["Content-Type"] = "application/json"
  110. elif body_type == ActionBodyType.FORM:
  111. request_kwargs["data"] = body
  112. prepared_headers["Content-Type"] = "application/x-www-form-urlencoded"
  113. logging.info(f"call_action_api url={url} request kwargs: {request_kwargs}")
  114. with requests.request(method.value, url, **request_kwargs) as response:
  115. response_content_type = response.headers.get("Content-Type", "").lower()
  116. if "application/json" in response_content_type:
  117. data = response.json()
  118. else:
  119. data = response.text
  120. if response.status_code == 500:
  121. error_message = f"API call failed with status {response.status_code}"
  122. if data:
  123. error_message += f": {data}"
  124. return {"status": response.status_code, "error": error_message}
  125. return {"status": response.status_code, "data": data}
  126. except requests.exceptions.RequestException as e:
  127. return {"status": 500, "error": f"Failed to make the API call: {e}"}
  128. except Exception:
  129. return {"status": 500, "error": "Failed to make the API call"}