Open abulka opened 3 years ago
Same issue when using async. I am getting this error message.
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 2073, in wsgi_app
response = self.handle_exception(e)
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 2070, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1516, in full_dispatch_request
return self.finalize_request(rv)
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1535, in finalize_request
response = self.make_response(rv)
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1727, in make_response
raise TypeError(
TypeError: The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a coroutine.
Edit: Did work (sometimes) if wrap it inside a render_template(), but for other responses like json, instead of ignore it like it should, it tries to run it anyway if using async.
The flask-debugtoolbar has its own dispatch_request
method which implementation differs from the Flask version.
def dispatch_request(self) -> ResponseReturnValue:
"""Does the request dispatching. Matches the URL and returns the
return value of the view or error handler. This does not have to
be a response object. In order to convert the return value to a
proper response object, call :func:`make_response`.
.. versionchanged:: 0.7
This no longer does the exception handling, this code was
moved to the new :meth:`full_dispatch_request`.
"""
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule
# if we provide automatic options for this URL and the
# request came with the OPTIONS method, reply automatically
if (
getattr(rule, "provide_automatic_options", False)
and req.method == "OPTIONS"
):
return self.make_default_options_response()
# otherwise dispatch to the handler for that endpoint
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
I'm not an expert on this but I guess calling ensure_sync
in dispatch_request
will solve this.
Happy to merge a PR if you or anyone else wants to dig into it.
On the surface, it looks relatively straightforward--port the updated version from Flask
itself into here, along with the update to call process_view
, but I'm sure there's a wrinkle or two to keep things interesting.
My current work is unrelated to Flask, so won't have time to look into it myself anytime soon.
I've quickly tried the following hack/changes. Seems to work:
:one: override DebugToolbarExtension
to handle coroutines:
from asyncio import iscoroutinefunction
from asgiref.sync import async_to_sync
from flask_debugtoolbar import DebugToolbarExtension
class MyDebugToolbarExtension(DebugToolbarExtension):
def process_view(self, app, view_func, view_kwargs):
processed_view = super().process_view(app, view_func, view_kwargs)
if iscoroutinefunction(processed_view):
processed_view = async_to_sync(processed_view)
return processed_view
:two: Enable sql query recording of an async engine:
from flask_sqlalchemy import record_queries
from sqlalchemy.ext.asyncio import create_async_engine
engine = create_async_engine(db_url)
record_queries._listen(engine.sync_engine)
Note that this is calling a "private" function, _listen
in the record_queries
module.
Disclaimer: I've only just discovered flask-sqlalchemy and flask-debugtoolbar, so there may be better ways of doing this!
It seems that flask-debugtoolbar is incompatible with flask 2 async endpoints.
Repro:
Run this flask project (app.py) and hit
/data
to trigger an async call - which works.Then uncomment the line enabling the
DebugToolbarExtension
and we get an exception.