What I’m trying to do:
I’m trying to use the Python Requests module to create a flexible API call function, but I’m having trouble getting it to operate properly. The code works as expected when run outside of the Anvil platform, but it has problems when run inside Anvil. The following error occurs specifically:
RecursionError: maximum recursion depth exceeded while calling a Python object
at /usr/local/lib/python3.7/ssl.py:518
Here is the code:
def _call_api(url, details={}, method="GET"):
"""
Internal function to make HTTP requests using the requests library.
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 {}.
method (str, optional): The HTTP method to use. Defaults to "GET".
json (bool, optional): Whether to treat the response as JSON. Defaults to True.
Returns:
dict: A dictionary containing the status code, headers, and the response body.
"""
try:
method = method.upper() if isinstance(method, str) else "GET"
if method not in ["GET", "POST", "PUT", "DELETE"]:
raise ValueError("Invalid HTTP method specified")
response = requests.request(method, url, **details)
response.raise_for_status()
status = response.status_code
headers = response.headers
body = response.json()["body"]
result = {
"status": status,
"headers": headers,
"body": body,
}
except requests.exceptions.HTTPError as e:
result = {
"status": e.response.status_code,
"headers": e.response.headers,
"body": e.response.json(),
}
except ValueError as e:
result = {
"status": 400,
"headers": {},
"body": {"error": str(e)},
}
return result
I don’t see anything wrong with the way the request is managed, and I don’t understand what may cause the specific error you are mentioning, but I see that you are using a mutable as a default value for the details argument, and this can have nasty side effects.
The function does not modify the dictionary nor it passes it to another function, so it shouldn’t be a problem here, but the right thing to do would be something like this:
def _call_api(url, details=None, method="GET"):
if details is None:
details = {}
I’ve updated the function to include a condition to confirm the details parameter is not the incorrect type and am still having the same issue. The function errors on the line that the request is being called.
The problem (the one I’m talking about, not the one you are asking about, sorry for the XY intrusion) is not that the type could be wrong, it’s that a function argument should never be assigned a mutable as default value.
I did the same as @jshaffstall above and googled it, I also found someone complaining about the maximum length of the **details being set on the server side with ssl.py, you might want to check how many items are being passed to details with print(len(details)) etc. etc
Thanks, I tried implementing this solution, based on the Stack Overflow post you provided:
# this is the import solution indicated
import gevent.monkey
gevent.monkey.patch_all()
The is the error I get using that solution:
RuntimeError: cannot release un-acquired lock
at <frozen importlib._bootstrap>:107
called from <frozen importlib._bootstrap>:152
called from <frozen importlib._bootstrap>:983
called from <frozen importlib._bootstrap>:1006
called from /usr/local/lib/python3.7/importlib/__init__.py:127
called from /downlink/anvil_downlink_worker/__init__.py:184
called from ColorPaletteCreater, line 17
I just asked chatGTP for help and it suggested that the reason why it works on your machine and not on another is due to invalid SSL certificates, and to disable them.
This sounds like a horrible idea, but it might be on to something about the reason for the difference in behavior from the same code in two different places. (security settings being different)
The order of imports of requests and gevent.monkey seems to be important. Clicking through to the original Github issue from that Stack Overflow answer might give some ideas.
A function argument should never be a dict, a list or any mutable.
The problem you are reporting has nothing to do with the empty dict being used as default value for a function argument. I mentioned it in my first answer, and Jay already found the cause of that problem, which has nothing to do with that empty dict.
Before asking if I have a problem with a function, I try to remove any potential source of problems, and having details={} in the arguments of a function is a big potential cause of the most unexpected and seemingly unrelated problems.
import anvil.http
def _call_api(url, details=None, method="GET"):
"""
Internal function to make HTTP requests using the requests library.
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 {}.
method (str, optional): The HTTP method to use. Defaults to "GET".
json (bool, optional): Whether to treat the response as JSON. Defaults to True.
Returns:
dict: A dictionary containing the status code, headers, and the response body.
"""
details = details if isinstance(details, dict) else {}
try:
method = method.upper() if isinstance(method, str) else "GET"
if method not in ["GET", "POST", "PUT", "DELETE"]:
raise ValueError("Invalid HTTP method specified")
response = anvil.http.request(method=method,url=url,**details,json=True)
#response = requests.request(method, url, **details)
#response.raise_for_status()
#status = response.status
#headers = response.headers
body = response
result = {
"status": 200,
"headers": None,
"body": body,
}
except anvil.http.HttpError as e:
result = {
"status": e.status,
"header": {},
"body": e.content,
}
except ValueError as e:
result = {
"status": 400,
"headers": {},
"body": {"error": str(e)},
}
return result
EDIT had to change successful response because I am not getting the header or status EDIT 2 Changed anvil.http.HTTPError to anvil.http.HttpError
This is definitely untested. I am not sure how to get the status/header on a successful response (The documents and api docs aren’t clear on that).
I am ignoring that portion just to test if it works.
EDIT
this is from the docs:
If the HTTP status code is successful (200-299), the anvil.http.request function returns the response body of the request. By default, this will be a Media object.