v2 rewrite (Claude version)#1516
Draft
nicoddemus wants to merge 9 commits into
Draft
Conversation
Purely mechanical extraction ahead of the v2 hook-based rewrite: no behavior change. plugin.py becomes a thin aggregator that re-exports hookimpls and fixtures from _hooks, _config, _markers, _runner, _collection, _fixtures, and _dispatch, so later commits' diffs are reviewable in isolation from this reorganization. Verified byte-for-byte behavior preservation by running the full test suite before and after: identical 283 passed / 12 failed (pre-existing, environment-related) / 3 skipped in both cases. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Removes PytestAsyncioFunction and its four subclasses (Coroutine, AsyncGenerator, AsyncStaticMethod, AsyncHypothesisTest). Async tests are now tagged at collection time via pytest.Stash (asyncio_test_key, loop_scope_key) on plain pytest.Function items instead of swapped for a custom Item subclass, and dispatched by monkeypatching the coroutine directly inside pytest_pyfunc_call rather than through an overridden runtest(). is_async_test() becomes a stash lookup returning plain bool instead of an isinstance check returning TypeIs[PytestAsyncioFunction]. The async-generator-test rejection, staticmethod-coroutine support, and Hypothesis (@given) integration (including its deferred version-compat check, now run from a new pytest_runtest_setup hookimpl) are preserved behaviorally, just relocated out of the subclass hierarchy into shared classification helpers. Runner/event-loop lifecycle management is untouched: still internal, dynamically-generated per-scope fixtures, reusing pytest's own fixture- cache teardown-ordering machinery rather than hand-rolling scope- transition tracking. One correctness-critical detail surfaced while porting the loop_factory eager-fetch optimization: v1's PytestAsyncioFunction.setup() override ran code between ancestor-scope setup and this item's own _fillfixtures(), a seam that no longer exists without Item subclassing. Calling request.getfixturevalue() from a standalone pytest_runtest_setup hookimpl (before or after SetupState's own setup) trips pytest 9.1's newer fixture-request-lifecycle validation and breaks fixture resolution broadly. The fix instead controls _fillfixtures()'s own (in-order) resolution sequence: the side-effect-free _asyncio_loop_factory fixture is inserted first in the item's fixturenames so a loop-factory variant change invalidates stale same-scope async fixtures before they're resolved, while the scoped runner fixture -- which does have side effects (it becomes the current asyncio event loop on creation) -- stays appended last, matching v1's original position. Full test suite verified before and after: identical 288 passed / 7 failed (pre-existing, environment-related -- see below) / 3 skipped. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The autouse, user-overridable event_loop_policy fixture is gone: scoped runner fixtures now call _get_event_loop_policy() directly instead of taking it as a fixture parameter, and pytest_fixture_setup no longer has a deprecation-warning branch for user overrides (there's nothing left to override). Since this is a common pattern users likely still have in their conftest files (the standard v1 uvloop-customization idiom), silently turning it into inert dead code would be a bad upgrade experience -- the fixture would keep existing syntactically but quietly stop having any effect. A new pytest_collection_finish hook (_usage_checks.py) scans for any fixture literally named "event_loop_policy" after collection and raises pytest.UsageError pointing at the pytest_asyncio_loop_factories hook as the replacement. Removed the ~13 now-broken tests across the five loop-scope marker test files that exercised event_loop_policy overrides (superseded by tests/test_loop_factory_parametrization.py) and added tests/test_event_loop_policy_removed.py covering the new UsageError. Deleted the two event_loop_policy example docs and the multiple_loops how-to guide (entirely event_loop_policy-based, fully superseded by the existing hook-based loop_factory guides); trimmed the fixture-based section from the uvloop guide. Full suite verified: 275 passed / 7 failed (same pre-existing, environment-related failures as before) / 3 skipped, plus docs/ (26 passed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@pytest.mark.asyncio(scope=...) (the deprecated alias for loop_scope=) is no longer accepted. Since v2 is already a breaking release, this already-deprecated kwarg doesn't need to carry through another major version -- scope= now falls through to the same generic "accepts only keyword arguments 'loop_scope' and 'loop_factories'" ValueError as any other unrecognized kwarg. Updated tests/markers/test_function_scope.py: the "both scope and loop_scope present" test keeps its errors=1 outcome but now asserts the generic unrecognized-kwarg message instead of the old duplicate- definition error; the "scope alone" test changes from a passing/warning outcome to an error outcome. Full suite verified: 276 passed / 6 failed (pre-existing, environment-related) / 3 skipped, docs/ 26 passed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ytest-dev#1514) pytest-asyncio's loop_scope is a concept independent of pytest's own fixture cache scope (a function-cache-scoped fixture can declare loop_scope="session"), so pytest's built-in ScopeMismatch check gives no protection against a test and a fixture it depends on actually running on different event loops -- which can silently break asyncio.Future, Task, or Lock objects bound to the loop they were created on. New _mismatch.py computes, once per item at collection time, the full transitive closure of async-owned fixtures whose effective loop_scope differs from the test's own (walking pytest's own deduplicated names_closure, so a diamond-shaped fixture graph can't produce duplicate warnings for the same fixture). Detection is direction-agnostic: a narrower test consuming a wider fixture and a narrower fixture consumed by a wider test are both flagged, since either can leave asyncio objects bound to a loop that's already gone. The warning is computed eagerly at collection time but only *emitted* from pytest_pyfunc_call at actual test-run time, not from the collection hookwrapper where it's computed: this repo's own filterwarnings=["error"] would otherwise turn a collection-time warning into a collection error that can abort a whole module and can't be locally silenced with pytest.warns() inside a test, since the test hasn't started yet. _owns_fixture and _effective_fixture_loop_scope, previously inline in _fixtures.py's pytest_fixture_setup, are now shared helpers so "what we wrap" and "what we compare" can never drift apart. As expected, this surfaces real (mostly deliberate, a couple incidental) mismatches already present in this repo's own test suite -- 15 test functions needed pytest.mark.filterwarnings/-W default treatment for scope combinations they intentionally exercise. This is the intended effect of closing pytest-dev#1514, not a regression: it's exactly the class of bug the warning exists to catch. Full suite verified: 283 passed / 6 failed (pre-existing, environment-related) / 3 skipped, docs/ 26 passed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New docs/how-to-guides/migrate_from_1_x.rst, matching the structure of the existing migrate_from_0_21/migrate_from_0_23 guides: covers the event_loop_policy fixture removal, the scope= marker kwarg removal, the new PytestAsyncioLoopScopeMismatchWarning (flagged as likely to surface pre-existing bugs in bulk on first upgrade), and is_async_test's return type change from TypeIs to plain bool. Five towncrier fragments in changelog.d/ covering the removed/added/ changed/downstream categories for this release. Validated with `towncrier build --draft` and a full `sphinx-build -W` (warnings as errors) run -- both clean, confirming no broken toctree entries or rst syntax issues across the new and edited pages. Also tightened docs/reference/warnings.rst to use the same plain double-backtick/asterisk style as the rest of the docs instead of Sphinx :class: cross-reference roles, since this project's conf.py has neither autodoc nor intersphinx configured to resolve them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Use Literal instead of str/bool for the finite set of category names and monkeypatch-target attribute names, so a typo or new category mismatches with the callers' string comparisons (e.g. category == "asyncgen") get caught statically instead of silently doing nothing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- shed: reformat two new test files and a doc code-block to match the
repo's established style (collapsed makepyfile/makeconftest calls,
blank line between import groups).
- mypy: _effective_fixture_loop_scope's or-chain of Any/str values
needed an explicit cast to the Literal _ScopeName it's declared to
return. _is_hypothesis_wrapped_coroutine accessed .hypothesis.inner_test
directly on an object-typed param instead of via getattr. The
hookwrapper in _dispatch.py had a bare mid-function `return` where v1
always used `return None`, which mypy treats differently inside a
generator function. _runner.py's five scoped-runner fixtures were only
ever bound via `globals()[f"_{scope}_scoped_runner"] = ...` in a loop,
invisible to static analysis; replaced with explicit assignments per
scope, which plugin.py now needs anyway since -- unlike v1, which only
ever looked these fixtures up dynamically by string -- the module-split
aggregator imports them by name for pytest's fixture discovery.
Verified: full pre-commit run -a clean, full test suite unchanged (283
passed / 6 pre-existing environment-related failures / 3 skipped), docs/
26 passed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
nicoddemus
force-pushed
the
claude-rewrite
branch
from
July 21, 2026 12:57
668a3d8 to
40c361b
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1516 +/- ##
==========================================
+ Coverage 94.50% 94.63% +0.12%
==========================================
Files 2 11 +9
Lines 510 578 +68
Branches 62 70 +8
==========================================
+ Hits 482 547 +65
- Misses 22 23 +1
- Partials 6 8 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
No description provided.