Rules¶
General rules¶
Our ASYNC1xx rules check for semantic problems ranging from fatal errors (e.g. 101),
to idioms for clearer code (e.g. 116).
- ASYNC100cancel-scope-no-checkpoint
A timeout context does not contain any checkpoints. This makes it pointless, as the timeout can only be triggered by a checkpoint. This check also treats
yieldas a checkpoint, since checkpoints can happen in the caller we yield to.trio.open_nursery()andanyio.create_task_group()are excluded, as they are schedule points but not cancel points (unless they have tasks started in them). See ASYNC912 which will in addition guarantee checkpoints on every code path.- ASYNC101yield-in-cancel-scope
yieldinside a TaskGroup / Nursery or timeout context is only safe when implementing a context manager - otherwise, it breaks exception handling. See this thread for discussion of a future PEP. This has substantial overlap with ASYNC119, which will warn on almost all instances of ASYNC101, but ASYNC101 is about a conceptually different problem that will not get resolved by PEP 533. Also triggered on common third-party context managers that open internal cancel scopes, nurseries, or task groups:trio_websocket.{open_websocket, open_websocket_url, serve_websocket},trio_asyncio.open_loop,trio_parallel.open_worker_context,trio_util.{move_on_when, run_and_cancelling},qtrio.{open_emissions_nursery, enter_emissions_channel},anyio.from_thread.{BlockingPortal, start_blocking_portal},asgi_lifespan.LifespanManager,apscheduler.AsyncScheduler,mcp.client.streamable_http.streamablehttp_client, andmcp.client.sse.sse_client.- ASYNC102await-in-finally-or-cancelled
awaitinsidefinally, cancelled-catchingexcept:, or__aexit__must have shielded cancel scope with timeout. If not, the async call will immediately raise a new cancellation, suppressing any cancellation that was caught. Not applicable to asyncio due to edge-based cancellation semantics it uses as opposed to level-based used by trio and anyio. Calls to.aclose()(with no arguments) and totrio.aclose_forcefully()/anyio.aclose_forcefully()are exempt, as they are intended for use in cleanup. See ASYNC120 for the general case where other exceptions might get suppressed.- ASYNC103no-reraise-cancelled
Cancelled / CancelledError-catching exception that does not reraise the exception. If you don’t want to re-raise
BaseException, add a separate handler for Cancelled / CancelledError before.- ASYNC104cancelled-not-raised
Cancelled / CancelledError-catching exception does not raise the exception. Triggered on
returnor raising a different exception.- ASYNC105missing-await
async trio function called without using
await. This is only supported with trio functions, but you can get similar functionality with a type-checker.- ASYNC106bad-async-library-import
trio/anyio/asyncio should be imported with
import xxxfor consistency. Opt-in style check; the linter resolves other import styles correctly.- ASYNC109async-function-with-timeout
Async function definition with a
timeoutparameter. In structured concurrency the caller should instead use timeout context managers.- ASYNC110async-busy-wait
while ...: await [trio/anyio].sleep()should be replaced by atrio.Event/anyio.Event.- ASYNC111variable-from-cm-in-start-soon
Variable, from context manager opened inside TaskGroup / Nursery, passed to
start[_soon]might be invalidly accessed while in use, due to context manager closing before the nursery. This is usually a bug, and nurseries should generally be the inner-most context manager.- ASYNC112useless-nursery
TaskGroup / Nursery body with only a call to
.start[_soon]and not passing itself as a parameter can be replaced with a regular function call.- ASYNC113start-soon-in-aenter
Using
start_soon()/start_soon()in__aenter__doesn’t wait for the task to begin. Consider replacing withstart()/start(). This will only warn about functions listed in ASYNC114 or known from Trio. If you’re starting a function that does not define task_status, then neither will trigger.- ASYNC114startable-not-in-config
Startable function (i.e. has a
task_statuskeyword parameter) not in –startable-in-context-manager parameter list, please add it so ASYNC113 can catch errors when using it.- ASYNC115async-zero-sleep
Replace
trio.sleep(0)/anyio.sleep(0)with the more suggestivetrio.lowlevel.checkpoint()/anyio.lowlevel.checkpoint().- ASYNC116long-sleep-not-forever
trio.sleep()/anyio.sleep()with >24 hour interval should usually betrio.sleep_forever()/anyio.sleep_forever().- ASYNC118cancelled-class-saved
Don’t assign the value of
anyio.get_cancelled_exc_class()to a variable, since that breaks linter checks and multi-backend programs.- ASYNC119yield-in-cm-in-async-gen
yieldin context manager in async generator is unsafe, the cleanup may be delayed untilawaitis no longer allowed. We strongly encourage you to read PEP 533 and use async with aclosing(…).trio.as_safe_channel()has been designed to be a drop-in replacement to transform any unsafe async generator into a context manager that uses streams and safely runs the generator in a background task.- ASYNC120await-in-except
Dangerous Checkpoint inside an
exceptblock. If this checkpoint is cancelled, the current active exception will be replaced by theCancelledexception, and cannot be reraised later. This will not trigger when ASYNC102 does, and if you don’t care about losing non-cancelled exceptions you could disable this rule. This is currently not able to detect asyncio shields.To handle this correctly, use a shielded cancel scope with a timeout:
try: await process() except Exception: with trio.fail_after(seconds, shield=True): await cleanup() raise
The shield prevents cancellation from replacing the current exception, while the timeout ensures cleanup can’t block indefinitely. Use
trio.move_on_after()if you want to suppress timeout errors rather than raise them.- ASYNC121: control-flow-in-taskgroup
return, continue, and break inside a TaskGroup / Nursery can lead to counterintuitive behaviour. Refactor the code to instead cancel the cancel scope inside the TaskGroup/Nursery and place the statement outside of the TaskGroup/Nursery block. In asyncio a user might expect the statement to have an immediate effect, but it will wait for all tasks to finish before having an effect. See Trio issue #1493 for further issues specific to trio/anyio.
- ASYNC122: delayed-entry-of-relative-cancelscope
trio.move_on_after(),trio.fail_after(),anyio.move_on_after()andanyio.fail_after()behaves unintuitively if initialization and entry are separated, with the timeout starting on initialization. Trio>=0.27 changes this behaviour, so if you don’t support older versions you should disable this check. See Trio issue #2512.- ASYNC123: bad-exception-group-flattening
Raising one of the exceptions contained in an exception group will mutate it, replacing the original
.__context__with the group, and erasing the.__traceback__. Dropping this information makes diagnosing errors much more difficult. We recommendraise SomeNewError(...) from groupif possible; or consider using copy.copy to shallow-copy the exception before re-raising (for copyable types), or re-raising the error from outside the except block.- ASYNC124: async-function-could-be-sync
Triggers when an async function contain none of
await,async fororasync with. Calling an async function is slower than calling regular functions, so if possible you might want to convert your function to be synchronous. This currently overlaps with ASYNC910 and ASYNC911 which, if enabled, will autofix the function to have Checkpoint. This excludes class methods as they often have to be async for other reasons, if you really do want to check those you could manually run ASYNC910 and/or ASYNC911 and check the methods they trigger on.- ASYNC125: constant-absolute-deadline
Passing constant values (other than
math.inf) to timeouts expecting absolute deadlines is nonsensical. These should always be defined relative totrio.current_time()/anyio.current_time(), or you might want to usetrio.fail_after()/:func:`trio.move_on_after/anyio.fail_after()/anyio.move_on_after(), or therelative_deadlineparameter totrio.CancelScope.- ASYNC126: exceptiongroup-subclass-missing-derive
A subclass of
ExceptionGrouporBaseExceptionGroupmust overridederive()to return an instance of itself, otherwisesplit()andsubgroup()- which are used by e.g. TaskGroup / Nursery implementations - will silently produce plainExceptionGroupinstances and lose the custom subclass. See trio#3175 for motivation.- ASYNC127: unmaintained-httpx
The original
httpxpackage is no longer maintained, which is a problem for a networking library. Use httpx2 - the continuation of the project, maintained by Pydantic - to keep getting security updates. It uses the same API, so migration is usually just a matter of replacinghttpxwithhttpx2in imports. This rule triggers on any import ofhttpx.
Blocking sync calls in async functions¶
Our 2xx lint rules warn you to use the async equivalent for slow sync calls which would otherwise block the event loop (and therefore cause performance problems, or even deadlock).
- ASYNC200blocking-configured-call
User-configured error for blocking sync calls in async functions. Does nothing by default, see async200-blocking-calls for how to configure it.
- ASYNC210blocking-http-call
Sync HTTP call in async function, use httpx2.AsyncClient. This and the other ASYNC21x checks look for usage of urllib3, httpx.Client, and httpx2.Client, and recommend using httpx2.AsyncClient as that’s the largest http client supporting anyio/trio. Sync calls through the unmaintained
httpxpackage are detected the same way ashttpx2ones, but the recommendation is always httpx2.AsyncClient - see ASYNC127.- ASYNC211blocking-http-call-pool
Likely sync HTTP call in async function, use httpx2.AsyncClient. Looks for urllib3 method calls on pool objects, but only matching on the method signature and not the object.
- ASYNC212blocking-http-call-httpx
Blocking sync HTTP call on httpx/httpx2 object, use httpx2.AsyncClient.
- ASYNC220blocking-create-subprocess
Sync call to
subprocess.Popen(or equivalent) in async function, usetrio.run_process()/anyio.run_process()/asyncio.create_subprocess_[exec/shell] in a TaskGroup / Nursery.- ASYNC221blocking-run-process
Sync call to
subprocess.run()(or equivalent) in async function, usetrio.run_process()/anyio.run_process()/asyncio.create_subprocess_[exec/shell].- ASYNC222blocking-process-wait
Sync call to
os.wait()(or equivalent) in async function, wrap intrio.to_thread.run_sync()/anyio.to_thread.run_sync()/asyncio.loop.run_in_executor().- ASYNC230blocking-open-call
Sync call to
open()in async function, usetrio.open_file()/anyio.open_file().asynciousers need to use a library such as aiofiles, or switch to anyio.- ASYNC231blocking-fdopen-call
Sync call to
os.fdopen()in async function, usetrio.wrap_file()/anyio.wrap_file().asynciousers need to use a library such as aiofiles, or switch to anyio.- ASYNC232blocking-file-call
Blocking sync call on file object, wrap the file object in
trio.wrap_file()/anyio.wrap_file()to get an async file object.- ASYNC240blocking-path-usage
Avoid using
os.pathin async functions, prefer usingtrio.Path/anyio.Pathobjects.asynciousers should consider aiopath or anyio.- ASYNC250blocking-input
Builtin
input()should not be called from async function. Wrap intrio.to_thread.run_sync()/anyio.to_thread.run_sync()orasyncio.loop.run_in_executor().- ASYNC251blocking-sleep
time.sleep()should not be called from async function. Usetrio.sleep()/anyio.sleep()/asyncio.sleep().
Asyncio-specific rules¶
Asyncio encourages structured concurrency, with asyncio.TaskGroup, but does not require it.
We therefore provide some additional lint rules for common problems - although we’d also recommend a
gradual migration to AnyIO, which is much less error-prone.
- ASYNC300create-task-no-reference
Calling
asyncio.create_task()without saving the result. A task that isn’t referenced elsewhere may get garbage collected at any time, even before it’s done. Note that this rule won’t check whether the variable the result is saved in is susceptible to being garbage-collected itself. See the asyncio documentation for best practices. You might consider instead using a TaskGroup and callingasyncio.TaskGroup.create_task()to avoid this problem, and gain the advantages of structured concurrency with e.g. better cancellation semantics.
ExceptionGroup rules¶
- ASYNC400except-star-invalid-attribute
When converting a codebase to use except* <except_star> it’s easy to miss that the caught exception(s) are wrapped in a group, so accessing attributes on the caught exception must now check the contained exceptions. This checks for any attribute access on a caught
except*that’s not a known valid attribute on ExceptionGroup. This can be safely disabled on a type-checked or coverage-covered code base.
Optional rules disabled by default¶
Our 9xx rules check for semantics issues, like 1xx rules, but are disabled by default due to the higher volume of warnings. We encourage you to enable them - without guaranteed Checkpoints timeouts and cancellation can be arbitrarily delayed, and async generators are prone to the problems described in PEP 789 and PEP 533.
- ASYNC900unsafe-async-generator
Async generator without
@asynccontextmanagernot allowed. You might want to enable this on a codebase since async generators are inherently unsafe and cleanup logic might not be performed. See PEP 789 for control-flow problems, PEP 533 for delayed cleanup problems. Further decorators can be registered with the--transform-async-generator-decoratorsconfig option, e.g. @trio_util.trio_async_generator.- ASYNC910async-function-no-checkpoint
Exit or
returnfrom async function with no guaranteed Checkpoint or exception since function definition. You might want to enable this on a trio/anyio codebase to make it easier to reason about checkpoints, and make the logic of ASYNC911 correct.- ASYNC911async-generator-no-checkpoint
Exit,
yieldorreturnfrom async iterable with no guaranteed Checkpoint since possible function entry (yieldor function definition).- ASYNC912cancel-scope-no-guaranteed-checkpoint
A timeout/cancelscope has cancel points, but they’re not guaranteed to run. Similar to ASYNC100, but it does not warn on trivial cases where there is no cancel point at all. It instead shares logic with ASYNC910 and ASYNC911 for parsing conditionals and branches.
- ASYNC913indefinite-loop-no-guaranteed-checkpoint
An indefinite loop (e.g.
while True) has no guaranteed checkpoint. This could potentially cause a deadlock. This will also error if there’s no guaranteed cancel point, where even though it won’t deadlock the loop might become an uncancelable dry-run loop.
Autofix support¶
The following rules support autofixing.
Removed rules¶
TRIOxxx: All error codes are now renamed ASYNCxxx
TRIO107: Renamed to TRIO910
TRIO108: Renamed to TRIO911
TRIO117: “Don’t raise or catch
trio.[NonBase]MultiError, prefer[exceptiongroup.]BaseExceptionGroup.”MultiErrorwas removed in trio==0.24.0.