domains / app-frameworks / pallets/flask
Safe async-to-sync bridging
When code must run async coroutines from a synchronous context (or vice versa), be deliberate about correctness, extensibility, and dependency handling:
When code must run async coroutines from a synchronous context (or vice versa), be deliberate about correctness, extensibility, and dependency handling:
- Detect coroutine functions robustly. Don’t just check the raw callable — unwrap bound methods and
functools.partialwrappers before testing, sinceinspect.iscoroutinefunctioncan give false negatives otherwise:def iscoroutinefunction(func): while inspect.ismethod(func): func = func.__func__ while isinstance(func, functools.partial): func = func.func return inspect.iscoroutinefunction(func)Prefer using the standard library’s version when available instead of reintroducing a compatibility shim.
-
Make the actual async-to-sync execution step an overridable hook (e.g. a dedicated
async_to_syncmethod) rather than baking the conversion logic into a larger dispatch method. This lets users swap in alternate event-loop/runner implementations without duplicating the coroutine-detection boilerplate. - Treat the async runtime bridge (e.g.
asgiref) as an optional dependency. Import it lazily inside the function that needs it and raise a clear, actionable error if it’s missing, rather than requiring it unconditionally:try: import asgiref except ImportError: raise RuntimeError("Install asgiref in order to run async views.") - Guard against shared mutable state when the same options/config object may be reused across multiple registrations or calls (e.g. blueprint options dicts). Copy before mutating so that one registration/call doesn’t leak state into another:
for blueprint, bp_options in self._blueprints: bp_options = bp_options.copy() # avoid mutating shared state
Applying these practices keeps async/sync bridges correct across wrapped callables, extensible for custom runners, dependency-light for users who don’t need async, and free of subtle bugs caused by shared mutable state across repeated registrations or invocations.</description> </invoke>