Description
In a large codebase, you're going to have some functions which are slow, and so you don't want them to be called from an async task (without using trio.to_thread.run_sync(slow_fn)
, anyway). flake8-trio
's TRIO200
error can help find direct calls, but indirect calls are really hard to find via static analysis.
As I mentioned in my PyCon talk, and work we've implemented a decorator to mark sync functions which can be called in a sync context, but must be delegated to a worker thread instead of being called in an async context - no matter how many sync functions are in the intermediate call chain. Simple implementation:
def not_in_async_task(fn):
"""Decorator for sync functions which should not be called in an async context.
<detailed docstring here>
"""
@functools.wraps(fn)
def decorator(*args, **kwargs):
try:
# Identical logic to trio.lowlevel.current_task()
GLOBAL_RUN_CONTEXT.task
except AttributeError:
pass
else:
raise RuntimeError(
f"{fn.__name__} should not be called in an async context. "
f"Use an async alternative, or trio.to_thread.run_sync({fn.__name__})."
)
return fn(*args, *kwargs)
return decorator
Should we include this in trio
itself? I've certainly found the pattern useful, and upstreaming it would encourage wider use of the pattern. On the other hand it's not entirely clear where this would fit, and it's neither hard to implement nor something which benefits much from standardization.