Open Xdynix opened 1 month ago
This issue is probably beyond the scope of Django Ninja, but I think it would be helpful if the documentation mentioned it.
My current workaround is to create a decorator like this:
from asyncio import get_running_loop
from collections.abc import Callable
from concurrent.futures import ThreadPoolExecutor
from functools import wraps
from typing import Any, TypeVar, cast
F = TypeVar("F", bound=Callable[..., Any])
def ensure_sync_context(func: F) -> F:
"""Run function with its own thread when in async context."""
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
try:
get_running_loop()
except RuntimeError:
return func(*args, **kwargs)
else:
with ThreadPoolExecutor(max_workers=1) as executor:
return executor.submit(func, *args, **kwargs).result()
return cast(F, wrapper)
Then for callbacks that I am sure will perform async-unsafe operations, I will add this decorator (such as SessionAuth.authenticate()
).
Describe the bug
If an authentication callback can only works in sync context, then it will not work on async views.
Example Code:
Accessing the view will raise:
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
This is because accessing
request.user
involves DB query (when using Django's default session engine) and can only be run in sync context. But inAsyncOperation._run_authentication()
it didn't switch the context for the authentication callback.Versions (please complete the following information):