Replies: 4 comments 11 replies
-
I think we can avoid the restriction on installing both. As we discussed yesterday, we might want to install both anyway for Quart to share code with Flask. Instead, the app objects can gain an attribute like def apply_async(app, names):
prefix = "async" if app.prefers_async else "sync"
for name in names:
setattr(self, name, getattr(self, f"{prefix}_{name}")) To support older versions, since they would already need to import both in your original example, they can use if hasattr(app, "prefers_async"):
prefers_async = app.prefers_async
else:
from quart import Quart
prefers_async = isinstance(app, Quart) |
Beta Was this translation helpful? Give feedback.
-
An alternative idea I've been considering is to require the usage of def extension(func):
if iscoroutinefunction(func):
async def wrapper(*args, **kwargs):
else:
def wrapper(*args, **kwargs):
return wrapper
# E.g. usage in a Quart codebase
@extension
@ensure_async
def index():
... With the class extension simply determining if the app is a Quart or Flask instance, i.e.: class Extension:
def __init__(self, app):
if isinstance(app, Quart):
app.method = self.async_method
else:
app.method = self.method
async def async_method(self):
pass
def sync_method(self):
pass |
Beta Was this translation helpful? Give feedback.
-
@patkennedy79 I wondered if you had a view? |
Beta Was this translation helpful? Give feedback.
-
I've had a further thought about the type 3 extensions. The issue as I see it can be solved if raising a def extension(func):
if iscoroutinefunction(func):
async def wrapper(*args, **kwargs):
if isinstance(request, QuartRequest):
data = await request.get_json()
else:
data = request.get_json()
validate_data(data)
return await func(*args, data=data, **kwargs)
else:
def wrapper(*args, **kwargs):
if isinstance(request, QuartRequest):
raise RuntimeError("func must be a coroutine")
else:
data = request.get_json()
validate_data(data)
return func(*args, data=data, **kwargs)
return wrapper I think this is acceptable as Quart already disallows the usage of Request coroutine-methods in sync route handlers. Therefore if the extension also raises an error then there is no additional issues. This roughly matches @patkennedy79's suggestion above, only without the usage of |
Beta Was this translation helpful? Give feedback.
-
I've been thinking about recommending patterns for extensions so that they can support both Flask and Quart. I'd like to discuss these patterns here, then publish them in the docs and adopt them for the extensions I maintain.
The problem
The problem is that IO is either async/await based or not, and if it is the former the full call stack must be async/await based. I think we should rule out Flask-Patch, greenback, or other patching solutions as I think they are unpleasant and will not be adopted wide-scale.
How extensions interact
I think we can simplify extensions to do two things, the first is alter the
app
instance and the second is to decorate route handlers i.e.To summarise the problem: The extension needs to know if it is extending Flask or Quart at import time and adjust between
async def
anddef
based on which framework it extends.The solution
I think we should advise that extensions can be one of three types:
request.get_json()
orrequest.form
)The first type
Extensions that do their own IO should not try to support Flask and Quart, as the extension must choose how it does the IO i.e. is it async/await based or not.
The second and third types
These are both need to figure out if they are extending Quart or Flask, and I propose this pattern,
Note on type 3
In the above pattern
request
can be imported alongsidecurrent_app
andawait request.get_json()
orrequest.get_json
can be added as appropriate. For example,Downsides to this solution
Whilst it is unlikely that Flask and Quart will be installed in the same environment, it now becomes vital that they are not.
Beta Was this translation helpful? Give feedback.
All reactions