I have developed a more robust solution for handling various types of HTTP responses. By utilizing Anvil’s built-in HTTP module instead of the Requests library, I was able to address the encountered issues. This approach should be investigated further by Anvil to improve compatibility. Additionally, the solution automatically determines whether the ‘json’ parameter should be set to True or False for each request.
This enhanced solution accommodates multiple response scenarios, and I have tested and verified the following cases:
# returning just the data
return data
# returning a dict with with additional data
return {"status": 200, "body": data, "headers": headers}
# returning an anvil.server.HttpResponse
return anvil.server.HttpResponse(200, data, headers)
Output for all of these is the same:
{'status': 200, 'headers': {'accept-encoding': 'gzip, deflate', 'content-length': '0', 'host': 'api-hose', 'user-agent': 'http-kit/2.0', 'x-forwarded-for': '00.000.000.0', 'cookie': None}, 'body': "data"}
Here is the full function:
# note get_key() is a more robust version of the dict.get() built-in that I created as a tool
def get_key(data: dict, key: str, no_value=None, alt_value=None):
"""Gets a key from a dictionary with optional return values if none found or found
Parameters
__________
:: data : dict # Dictionary value to search for key
:: key : str # String value to search dictionar for
:: no_value : obj=None # A value to return if key is not found in dictionary
:: alt_value : obj=None # An alternate value to return if key is found in dictionary other than the value found
Return
_________
:: `no_value` if error | `alt_value` if no error and `alt_value`
"""
try:
value = data[key] if not alt_value else alt_value
except:
value = no_value
return value
def _call_api(url, details: dict = None, method: str = "GET"):
"""
Makes an HTTP request using the Anvil HTTP module.
Args:
url (str): The URL to make the request to.
details (dict, optional): Additional details to pass to the request, such as headers. Defaults to None.
method (str, optional): The HTTP method to use. Defaults to "GET".
Returns:
dict: A dictionary containing the status code, headers, and the response body.
"""
def _handle_response(response):
try:
body = response.get_bytes()
except AttributeError:
body = response
try:
body = json.loads(body)
except (json.JSONDecodeError, TypeError):
body = body.decode("utf-8") if isinstance(body, bytes) else body
return {
"status": get_key(body, "status") or get_key(response, "status") or 200,
"body": get_key(body, "body", body),
}
def _handle_error_response(e):
try:
body = e.content.get_bytes()
except AttributeError:
body = e.content
body = body.decode("utf-8") if isinstance(body, bytes) else body
return {
"status": e.status,
"body": body,
}
method = method.upper() if isinstance(method, str) else "GET"
if method not in ["GET", "POST", "PUT", "DELETE"]:
raise ValueError("Invalid HTTP method specified")
details = details if isinstance(details, dict) else {}
details = details if isinstance(details, dict) else {}
headers = details.get("headers")
data = details.get("data")
username = details.get("username")
password = details.get("password")
timeout = details.get("timeout")
kwargs = {
"method": method,
"url": url,
"headers": headers,
"data": data,
"username": username,
"password": password,
"timeout": timeout,
}
for json_flag in (True, False):
try:
kwargs["json"] = json_flag
response = anvil.http.request(**kwargs)
return _handle_response(response)
except anvil.http.HttpError as e:
error_response = _handle_error_response(e)
except Exception as e:
error_response = {
"status": 400,
"body": {"error": str(e)},
}
return error_response