Repository navigation
refactor: annotate contextmanager returns as Generator, not Iterator - #3176
Merged
Merged
Conversation
typeshed marks the `@contextmanager` / `@asynccontextmanager` overload that accepts `Iterator[T]` / `AsyncIterator[T]` as deprecated, so pyright 1.1.409+ flags 31 sites in this repo. The warning is worth acting on rather than silencing: `_AsyncGeneratorContextManager.__aexit__` calls `gen.athrow()`, so the decorator needs a real generator. The iterator annotation is an under-specification — it type-checks, while a non-generator async iterator would fail at runtime. Rewrites the return annotation at all 31 decorated functions (27 async, 4 sync) and trims the `collections.abc` import lines. Functions that are not decorated keep `AsyncIterator`; there it is the correct consumer-facing type. Uses the single-argument form (`AsyncGenerator[None]`, not `[None, None]`), which is valid on Python 3.10 because every site imports from `collections.abc`, whose `GenericAlias` does not enforce arity — unlike `typing.AsyncGenerator`, which would need both parameters. Records the rule in AGENTS.md. Verified: ruff check and format clean; pyright with `pythonVersion=3.10` and `reportDeprecated` reports 0 remaining contextmanager deprecations and 0 arity errors (was 31); 18 of the 19 touched modules import under 3.10, the 19th needing an unavailable optional dependency; 1397 tests pass across the touched areas.
Codecov Report✅ All modified and coverable lines are covered by tests.
🚀 New features to boost your workflow:
|
Lancetnik
approved these changes
Aug 18, 2026
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.
Why are these changes needed?
In typeshed's
stdlib/contextlib.pyi,contextmanagerandasynccontextmanagereach have two overloads, and the one acceptingIterator[T]/AsyncIterator[T]carries a@deprecatedmarker. pyright 1.1.409+ and Pylance therefore flag 31 sites acrossag2/andtest/with The function "asynccontextmanager" is deprecated — wording that overstates the case, since the decorator itself is not going anywhere.The diagnostic is still worth acting on rather than silencing, because the underlying point is real:
_AsyncGeneratorContextManager.__aexit__callsgen.athrow(), so the decorator requires an actual generator.AsyncIteratoris an under-specification — it type-checks, while a plain non-generator async iterator handed to the decorator fails at runtime.The codebase was already split on this: of the 43 decorated context managers in tracked code, 12 used the generator form before this PR. This makes the convention uniform and writes it down.
What changed
AsyncIterator[T]→AsyncGenerator[T], 3 ×Iterator[T]→Generator[T].from collections.abc import …lines adjusted. The old name is dropped only where nothing else in the file still uses it —ag2/stream.pykeepsAsyncIteratorbecause there it is nested:Generator[AsyncIterator[BaseEvent]].AsyncIteratorreturn annotations remain on undecorated functions inag2/(27AsyncIterator[...]occurrences in total). Outside the decorator,AsyncIteratoris the correct consumer-facing type and narrowing it toAsyncGeneratorwould over-promise.AsyncGenerator[None], notAsyncGenerator[None, None].AGENTS.mdunder Code Style Guidelines, next to the existingfrom __future__ import annotationsrule, so the pattern does not creep back in.One file is knowingly left alone:
docs/superpowers/plans/2026-07-23-acp-tool-gateway.mdshows the old form in a historical plan snapshot, and the code that plan produced already usesAsyncGenerator. Plan documents record what was planned at the time and are not retrofitted.Why the single-argument form is safe on Python 3.10
AGENTS.mdforbidsfrom __future__ import annotations, so on every version before 3.14 (PEP 649) these return annotations are evaluated at definition time — arity is a runtime concern, not just a type-checker one. Every site imports fromcollections.abc, whosetypes.GenericAliasdoes not enforce the number of type arguments, soAsyncGenerator[None]builds fine on 3.10.typing.AsyncGenerator[None]would raiseTypeError: Too few arguments, but no site imports fromtyping. pyright withpythonVersion = "3.10"accepts the single-argument form as well, since typeshed gives the send type a PEP 696 default.Related issue number
None — this came out of the Pylance diagnostic showing up across the repository rather than a filed issue.
Checks
website/reference the pattern, so nothing on the docs site needed updating. The contributor-facing rule went intoAGENTS.md.test/mcp test/a2a test/live test/tools test/network test/agent test/extensions test/*.pygives 1727 passed, 1 skipped, 1 xfailed.Beyond CI, one check worth calling out because the pipeline does not run it: pyright with
pythonVersion = "3.10"andreportDeprecatedenabled reports 0 remaining contextmanager deprecations (down from 31) and 0 type-argument arity errors. The 14 diagnostics that remain are pre-existing and unrelated (absent optional dependencies, a deprecation inside theexaSDK,bool.__invert__in tests).ruff checkandruff format --checkare clean across 932 files.The arity question is the one that genuinely needed the matrix rather than a type checker, since these annotations are evaluated at definition time on everything below 3.14 — and
test-ubuntu (3.10)passes, alongside 3.11 through 3.14, macOS and Windows.AI assistance