Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]¶
[0.1.7] - 2026-06-12¶
Fixed¶
Allow mock.ANY on JSON, data and headers on asserts.
Callbacks were receiving the compiled regex instead of the real URL; now every callback receives a yarl
URL.Callbacks with a
Content-Typeheader were raising an exception (the header collided with the default content type); now fixed.With
mock_external_urls=True, a request to a mocked host is now intercepted even when the client already holds a live keep-alive connection to the real server. A class-levelTCPConnector._getpatch refuses to reuse a pooled connection for intercepted hosts, forcing a fresh (redirected) connection; passthrough hosts still reuse their connections.A failed
start()(e.g. when bypass-session creation raises) now rolls back its class-level patches instead of leaking the patch refcount onto subsequent mocks.
Improved¶
DNS-cache invalidation no longer tracks
TCPConnectorinstances at all. Instead of patchingTCPConnector.__init__at import time and clearing every live connector’s cache onstart(), a class-level_resolve_hostpatch (installed only whenmock_external_urls=True) drops the relevant cache entry per lookup. No monkey-patching happens unlessmock_external_urls=True.Improved error messages
Added¶
AiointerceptRequest.kwargsnow includesdatafor non-JSON request bodies. Thanks to @eth2353.
[0.1.6] - 2026-06-09¶
Added¶
Public docs on https://aiointercept.readthedocs.io
Improved¶
The per-
start()DNS-cache purge now tracks liveTCPConnectorinstances in aweakref.WeakSetinstead of scanning the whole heap viagc.get_objects(), so its cost no longer grows with the process heap size. Especially impactful for large applications and test suites. Thanks to @agroebe.
[0.1.5] - 2026-06-02¶
Added¶
Public
async start()/async stop()lifecycle methods.__aenter__/__aexit__now delegate to them, so the mock can be driven from setup/teardown hooks (e.g.unittest.IsolatedAsyncioTestCase).Unmatched requests now log a diagnostic
WARNINGonaiointercept.coreexplaining why nothing matched: an exhausted finiterepeat, anndiffagainst the closest registeredMETHOD URL, or the registered patterns.
[0.1.4] - 2026-05-23¶
Added¶
Auto-discovered pytest plugin with
aiointercept_server(session-scoped) andaiointercept_mock(function-scoped, callsclear()between tests and re-pins_caller_loopso async callbacks dispatch on the test’s loop). Requirespytest-asyncio.benchmarks/bench_compare.py— standalone script comparingaiointercept(dns on/off) againstaioresponsesacross nine scenarios. Run viauv sync --group benchmarks && uv run python benchmarks/bench_compare.py.
Fixed¶
clear()followed by re-registering an HTTPS handler no longer breaks requests that reuse an existing keep-alive connection.add()now records the host in_https_hostseagerly when the registered URL ishttps://, so the dispatcher keeps the correct scheme even when aiohttp skips the SSL hook on reused connections.
[0.1.3] - 2026-05-17¶
Fixed¶
The intercept
TestServerno longer runs on the caller’s event loop. It now starts on a dedicated daemon thread with its own loop, so callers that block their own loop between__aenter__and__aexit__(e.g. Starlette/FastAPITestClient, which holds the loop during a synchronousclient.get(...)call) can no longer deadlock the mock server. Plain mocks (m.get(url, status=...)) underTestClientnow work by construction.During the DNS-cache purge in
_clear_existing_connector_caches, theisinstance(obj, aiohttp.TCPConnector)check is now performed insidecontextlib.suppress(Exception), so a stray object whose__class__lookup raises duringgc.get_objects()iteration no longer aborts the purge for the remaining connectors.
Changed¶
Async user callbacks registered via
callback=are now executed on the caller’s event loop (the loop active when__aenter__was called), even though the server runs on its own loop. This preserves prior semantics:asyncio.Event,asyncio.Queue,asyncio.Lock, and other loop-bound primitives shared between the test and the callback continue to work. Sync callbacks are unaffected and continue to run on the server loop.Package metadata modernized:
licenseis now an SPDX expression ("MIT") and the deprecatedLicense :: OSI Approved :: MIT Licensetrove classifier was removed.
Internal¶
Expanded the
rufflint rule set (UP,B,C4,SIM,N,RUF,PT,TCH,ASYNC,PERF,RET,ARG) withline-length = 120andtarget-version = "py310", and reformattedaiointercept/core.pyaccordingly: PEP 585type[X]overtyping.Type[X], string-formcast("X", ...)annotations,contextlib.suppressinstead oftry/except/pass, and consolidated imports. No behaviour change.Bumped the
mypydev dependency to>=2.0.0,<2.1.0.
Known limitations¶
The narrow combination of an async callback and a caller loop that is fully blocked (e.g. an async
callback=inside a StarletteTestClientrequest) still deadlocks: the callback is scheduled onto the caller’s loop, which cannot run it while it is blocked. Plain mocks underTestClientare unaffected.
[0.1.2] - 2026-05-12¶
Added¶
AiointerceptRequestis now exported from the top-levelaiointerceptpackage so users can type-annotate recorded requests without reaching intoaiointercept.core.
Fixed¶
clear()now also resets the internal_https_hostsset, so a host previously seen with HTTPS traffic is no longer incorrectly treated as HTTPS afterclear()is called.exception=(any truthy value) now correctly registers the target host in_host_listbefore returning, ensuring DNS is redirected to the mock server by design rather than by the fallback path.passthrough_unmatched=Truenow proxies unmatched paths for URL-registered hosts (not just pattern-registered ones). Previously, a registered host with an unknown path would close the connection even whenpassthrough_unmatched=True._host_listis now asetinstead of alist, preventing duplicate host entries when the same URL is registered multiple times.Passing
passthrough_unmatched=Truewithoutmock_external_urls=Truenow raisesValueErrorat construction time instead of being silently ignored.
Changed¶
mock_external_urlsnow defaults toFalse, making it an optional parameter. Callers that omit it get the recommended no-DNS-patching mode.Renamed
AiointerceptRequest._captured_body→AiointerceptRequest.captured_body(now public).
Internal¶
Renamed
AiointercepRequest→AiointerceptRequest(added missingt).Renamed
AiointerceptRequstKwargs→AiointerceptRequestKwargs(fixedRequst→Request).Replaced the
Exception-class-as-sentinel pattern with a named_CloseConnectionsentinel for the “close transport” handler marker.Added comments on the 502 fallback responses in
_dispatchclarifying they only surface iftransport.close()does not take effect.
[0.1.1] - 2026-05-04¶
Initial public release.
Added¶
Real
aiohttp.webtest server — requests travel through an actual HTTP stack instead of being short-circuited in memory.Two interception modes controlled by the
mock_external_urlsconstructor argument:False— server starts on localhost; point your client atm.server_urldirectly. No global state patched.True— patchesThreadedResolver/AsyncResolverat the class level so anyaiohttprequest is redirected, regardless of hostname.
HTTPS interception (
mock_external_urls=True): patchesTCPConnector._get_ssl_contextto strip TLS for intercepted hosts and reconstructs the originalhttps://URL server-side via an injectedX-Aiointercept-Orig-Schemeheader.aioresponses-compatible registration API:m.get/post/put/patch/delete/head/options,m.add,CallbackResult.Regex pattern matching via compiled
re.PatternURLs.Sync and async callback support; callbacks receive
url,headers,query, andjson.repeat=True(unlimited) andrepeat=N(finite) response queuing; multipleadd()calls for the same URL queue responses in order.passthrough— list of hosts to bypass the mock and hit the real network.passthrough_unmatched=True— forward unregistered requests to the real server instead of raisingClientConnectionError.m.requests— dict keyed by(METHOD, yarl.URL)recording every intercepted request, with parsedheaders,query, andjsoninrequest.kwargs.m.clear()— reset registered handlers and recorded requests without tearing down the server.m.server_url— base URL of the local test server (available inside theasync withblock).Assertion helpers:
assert_called,assert_not_called,assert_called_once,assert_any_call,assert_called_with,assert_called_once_with.SSL context caching to avoid redundant per-host lookups.
Decorator usage with optional
param=to name the injected mock argument.
Known limitations¶
Bare IP addresses (
http://1.2.3.4/path) are not intercepted whenmock_external_urls=Truebecause DNS patching has no effect on numeric addresses.exception=only closes the connection, surfacing aClientConnectionErroron the client. Raising arbitrary exception types is not supported.timeout=passthrough is not supported.CallbackResult(response_class=)is silently ignored.request
**kwargscontains onlyheaders,query, andjson— not the fullaiohttprequest kwargs set.