Requests Library experiencing RecursionError

I assumed you had meant to just hard code it to 200. This is where I had started but was hoping to get more information back from the request. Also, oddly, I get this error when testing on an HTTP endpoint that returns and anvil.HttpResponse:

AttributeError: module 'anvil.http' has no attribute 'HTTPError'

Again sorry, untested.

It should be anvil.http.HttpError

Sorry, I should have caught that myself. It’s curious as to why using the anvil.HttpResponse I’m getting this error:

example:

resp = HttpResponse(status=200, body=data, headers=headers)
anvil.server.SerializationError: Cannot serialize return value from function. Cannot serialize <class 'bytes'> object at msg['response']['body']

However, when the HTTP response from my test endpoint is structured like this, everything seems to work:

resp = {"status": 200, "body": data, "headers": headers}

I believe the json=True argument expects the return type to be a certain type.

But if its false you’ll have to structure the data yourself. Anvil Docs | Making HTTP requests

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

1 Like