From c9c4aa19cc6738e2a35e3dbde0f39504dda91e22 Mon Sep 17 00:00:00 2001 From: feiiiiii5 <204683769+feiiiiii5@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:41:45 +0800 Subject: [PATCH] cleanup: remove dead aiohttp vcr compat shim (#2140) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vcrpy 8.2.0+ restored aiohttp 3.14 compatibility — the broken `AsyncStreamReaderMixin` reference was dropped in kevin1024/vcrpy#996 — and `requirements/local.txt` already pins `vcrpy==8.3.0`. The compat shim in `opencontractserver/utils/vcr_replay.py`, its call site in `conftest.py`, the `EnsureAiohttpVcrCompatTests` test class, and the eight-line comment block above the vcrpy pin were therefore dead code. Resolves #2140. --- .../2140-remove-aiohttp-vcr-shim.removed.md | 8 +++ conftest.py | 11 ---- opencontractserver/tests/test_vcr_replay.py | 66 ------------------- opencontractserver/utils/vcr_replay.py | 51 -------------- requirements/local.txt | 10 +-- 5 files changed, 9 insertions(+), 137 deletions(-) create mode 100644 changelog.d/2140-remove-aiohttp-vcr-shim.removed.md diff --git a/changelog.d/2140-remove-aiohttp-vcr-shim.removed.md b/changelog.d/2140-remove-aiohttp-vcr-shim.removed.md new file mode 100644 index 0000000000..5ac4a90315 --- /dev/null +++ b/changelog.d/2140-remove-aiohttp-vcr-shim.removed.md @@ -0,0 +1,8 @@ +- **Removed the `ensure_aiohttp_vcr_compat()` shim and its stale pin rationale.** + vcrpy 8.2.0+ restored aiohttp 3.14 compatibility (the broken + `AsyncStreamReaderMixin` reference was dropped in + [`kevin1024/vcrpy#996`](https://github.com/kevin1024/vcrpy/pull/996)), and + `requirements/local.txt` already pins `vcrpy==8.3.0`. The compat shim in + `opencontractserver/utils/vcr_replay.py`, its call site in `conftest.py`, + the `EnsureAiohttpVcrCompatTests` test class, and the eight-line comment + block above the vcrpy pin were dead code. Resolves #2140. diff --git a/conftest.py b/conftest.py index 7cd4698180..2ea97ec3a5 100644 --- a/conftest.py +++ b/conftest.py @@ -11,17 +11,6 @@ import pytest from django import db -from opencontractserver.utils.vcr_replay import ensure_aiohttp_vcr_compat - -# Many integration tests record/replay VCR cassettes. vcrpy 8.1.1 imports its -# aiohttp stub lazily when a cassette is entered, and that stub subclasses -# ``aiohttp.streams.AsyncStreamReaderMixin`` — a symbol aiohttp 3.14 removed — at -# module-evaluation time. A fresh CI resolution that picks up aiohttp >= 3.14 -# would therefore make every VCR-using test raise AttributeError. Apply the -# compat shim here, at conftest import, so the symbol exists before any test -# runs. See opencontractserver/utils/vcr_replay.py and issue #1920. -ensure_aiohttp_vcr_compat() - @pytest.fixture(scope="session", autouse=True) def make_create_permissions_xdist_safe(): diff --git a/opencontractserver/tests/test_vcr_replay.py b/opencontractserver/tests/test_vcr_replay.py index 50ccef9ba6..97f6c1dacd 100644 --- a/opencontractserver/tests/test_vcr_replay.py +++ b/opencontractserver/tests/test_vcr_replay.py @@ -34,7 +34,6 @@ _VOLATILE_PATTERNS, _match_llm_body, _normalize_body, - ensure_aiohttp_vcr_compat, maybe_vcr_cassette, ) @@ -323,71 +322,6 @@ def test_replay_mode_yields_active_cassette_when_file_missing(self): self.assertIsNotNone(ctx) -class EnsureAiohttpVcrCompatTests(TestCase): - """Regression for issue #1920 / kevin1024/vcrpy#995. - - aiohttp 3.14 removed ``aiohttp.streams.AsyncStreamReaderMixin``, which the - pinned vcrpy 8.1.1 aiohttp stub subclasses at module-evaluation time. vcrpy - imports that stub lazily when a cassette is entered, so under aiohttp >= 3.14 - every VCR cassette entry raises ``AttributeError``. The compat shim restores - the symbol so cassette entry works again. - """ - - def test_mixin_symbol_present_after_shim(self): - try: - from aiohttp import streams - except ModuleNotFoundError: # pragma: no cover - aiohttp always present - self.skipTest("aiohttp not installed") - - had_real_symbol = hasattr(streams, "AsyncStreamReaderMixin") - original = getattr(streams, "AsyncStreamReaderMixin", None) - - # Keep the test self-contained: restore the module to whatever state it - # was in before this test ran (the shim may add the symbol below). - def _restore() -> None: - if had_real_symbol: - setattr(streams, "AsyncStreamReaderMixin", original) - elif hasattr(streams, "AsyncStreamReaderMixin"): - delattr(streams, "AsyncStreamReaderMixin") - - self.addCleanup(_restore) - - ensure_aiohttp_vcr_compat() - - # Whichever aiohttp is installed, the symbol must exist afterward so - # vcrpy's MockStream class statement can evaluate. - self.assertTrue(hasattr(streams, "AsyncStreamReaderMixin")) - if had_real_symbol: - # Under aiohttp < 3.14 the real mixin must be left untouched. - self.assertIs(getattr(streams, "AsyncStreamReaderMixin"), original) - - def test_shim_is_idempotent(self): - # Called twice (conftest + maybe_vcr_cassette both invoke it) must not - # raise or swap the symbol out from under a prior call. - ensure_aiohttp_vcr_compat() - try: - from aiohttp import streams - except ModuleNotFoundError: # pragma: no cover - aiohttp always present - self.skipTest("aiohttp not installed") - first = getattr(streams, "AsyncStreamReaderMixin") - ensure_aiohttp_vcr_compat() - self.assertIs(getattr(streams, "AsyncStreamReaderMixin"), first) - - def test_vcr_cassette_entry_works(self): - # The real contract: vcrpy imports its aiohttp stub lazily when a - # cassette is entered (vcr/patch.py builds its patchers). Under aiohttp - # >= 3.14 that stub subclasses the removed AsyncStreamReaderMixin and - # raises AttributeError. With the shim applied, entering an (empty) - # cassette must succeed. (`import vcr` alone does NOT trigger the stub, - # which is why this enters a cassette rather than just importing.) - ensure_aiohttp_vcr_compat() - import vcr - - with tempfile.TemporaryDirectory() as td: - with vcr.VCR().use_cassette(os.path.join(td, "regression.yaml")): - pass - - class LlmHostsTests(TestCase): """Sanity check for the host allowlist.""" diff --git a/opencontractserver/utils/vcr_replay.py b/opencontractserver/utils/vcr_replay.py index 9ac49c5460..3068dd70c6 100644 --- a/opencontractserver/utils/vcr_replay.py +++ b/opencontractserver/utils/vcr_replay.py @@ -42,52 +42,6 @@ logger = logging.getLogger(__name__) -def ensure_aiohttp_vcr_compat() -> None: - """Make vcrpy 8.1.1's aiohttp stub importable under aiohttp >= 3.14. - - vcrpy 8.1.1 (the latest release) defines - ``class MockStream(asyncio.StreamReader, streams.AsyncStreamReaderMixin)`` - in ``vcr/stubs/aiohttp_stubs.py``. aiohttp 3.14 removed - ``aiohttp.streams.AsyncStreamReaderMixin`` (its helpers were folded onto the - stream classes). vcrpy imports that stub lazily — when a cassette is entered - and ``vcr/patch.py`` builds its patchers — so under aiohttp >= 3.14 every VCR - cassette entry raises ``AttributeError`` (issue #1920; upstream - kevin1024/vcrpy#995, fix proposed in the unreleased PR #996). - - This codebase only records/replays *httpx* (LLM provider) cassettes — the - aiohttp stub is pulled in incidentally because aiohttp is importable - (transitive via llama-index-core). ``MockStream`` is therefore never - instantiated here, so restoring the removed name as an empty mixin is enough - to let the stub's class body evaluate; none of the mixin's (now-removed) - helper methods are exercised. - - Idempotent, and a no-op under aiohttp < 3.14 (where the real mixin is still - present — it is left untouched). Delete this shim, and bump ``vcrpy`` in - requirements/local.txt, once vcrpy ships a release that supports the aiohttp - 3.14 stream API. - """ - try: - from aiohttp import streams - except ModuleNotFoundError: - # aiohttp isn't installed → vcrpy won't load its aiohttp stub anyway. - return - - if hasattr(streams, "AsyncStreamReaderMixin"): - return - - class AsyncStreamReaderMixin: - """Empty stand-in for the base class aiohttp 3.14 removed. - - Only needs to exist so vcrpy's ``MockStream`` class statement can - evaluate; its methods are never called (we never replay aiohttp - cassettes). - """ - - # setattr (string name) rather than attribute assignment so mypy does not - # flag a member aiohttp 3.14 removed from the ``streams`` module. - setattr(streams, "AsyncStreamReaderMixin", AsyncStreamReaderMixin) - - # These hostnames are the LLM provider endpoints VCR should intercept. # Other hosts (LlamaParse, embedder microservice, S3) bypass VCR. _LLM_HOSTS = {"api.openai.com", "api.anthropic.com"} @@ -248,11 +202,6 @@ def maybe_vcr_cassette() -> Iterator[object | None]: yield None return - # vcrpy 8.1.1's aiohttp stub references a symbol aiohttp 3.14 removed; - # restore it before importing vcr so the live E2E record/replay harness - # (which runs outside pytest, where conftest isn't loaded) also works under - # aiohttp >= 3.14. See ensure_aiohttp_vcr_compat above and issue #1920. - ensure_aiohttp_vcr_compat() import vcr # local import keeps prod paths free of vcr cost cassette_dir = os.path.dirname(os.path.abspath(cassette_path)) diff --git a/requirements/local.txt b/requirements/local.txt index 58f6a8217d..da6ec9672d 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -14,15 +14,7 @@ pytest-xdist==3.8.0 # https://github.com/pytest-dev/pytest-xdist (parallel test pytest-timeout==2.4.0 # https://github.com/pytest-dev/pytest-timeout (per-test hang guard in CI) djangorestframework-stubs==3.17.0 # https://github.com/typeddjango/djangorestframework-stubs responses==0.26.2 # https://github.com/getsentry/responses -# vcrpy 8.1.1 is the latest release and is NOT yet compatible with aiohttp -# >=3.14: its aiohttp stub subclasses aiohttp.streams.AsyncStreamReaderMixin at -# import time, a symbol aiohttp 3.14 removed (kevin1024/vcrpy#995; fix proposed -# in the unreleased PR #996). aiohttp is an unpinned transitive dep, so a fresh -# CI resolution picks up 3.14+. ensure_aiohttp_vcr_compat() in -# opencontractserver/utils/vcr_replay.py restores that symbol so vcrpy works -# under aiohttp >=3.14. Bump vcrpy here and delete the shim once a release ships -# the vcrpy#996 fix. See issue #1920. -vcrpy==8.3.0 +vcrpy==8.3.0 # https://github.com/kevin1024/vcrpy (8.2.0+ restored aiohttp 3.14 compat — see issue #2140) # Profiling # ------------------------------------------------------------------------------