ASYNC101: cover the practical set of 3rd-party CMs with cancel scopes#449
Merged
Zac-HD merged 4 commits intopython-trio:mainfrom Apr 24, 2026
Merged
Conversation
Replaces the earlier httpx/httpx_ws additions (which use bare
asyncio.create_task internally and don't actually open a cancel scope
across the body) with the verified set of structured-concurrency CMs:
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
mcp.client.sse.sse_client
Also fixes build_cst_matcher to handle bases with 3+ dotted components
(it previously crashed on `mcp.client.streamable_http`).
`calls_any_of(node, *qualnames)` takes dotted fully-qualified names like "trio.open_nursery" or "mcp.client.sse.sse_client", groups them by base internally, and delegates to `with_has_call`. This flattens the visitor's chain of `with_has_call(...)` expressions into a single table of strings.
Previously, the test framework's `replace_library` rewrote every substring occurrence of "trio" / "anyio" / "asyncio", which mangled unrelated package names like trio_websocket, trio_util, qtrio, and anyio.from_thread in eval files. Now identifier-like replacements match at word boundaries; library names additionally allow a leading `_` so the "_trio" suffix in error codes like "ASYNC103_trio" still rewrites per library. With the substitution no longer mangling trio_websocket et al., the library-specific test file async101_trio_pkgs.py is no longer needed -- those cases merge into async101_third_party.py and are now verified under all three libraries.
This call is explicitly a schedule-but-not-cancel point, so it's safe to await inside a finally block, a cancelled except, or __aexit__ -- the same conditions where ASYNC102 / ASYNC120 would otherwise flag an await. Matches the existing exemption for bare `.aclose()` calls. Also covers the anyio.lowlevel equivalent for symmetry with the rest of the visitor.
676eb62 to
974456b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #350.
Also fixes build_cst_matcher to handle bases with 3+ dotted components, and exempts
<>.lowlevel.cancel_shielded_checkpoint()from ASYNC120 (#446 (comment)).