Upgrading from 8.x
9.0.0 has five changes that can affect existing test suites:
- Python 3.11+ is required. Python 3.10 reached its end of life on 2026-10-01 (#1061). On 3.10, pip keeps installing 8.x.
- New
CannotOverwriteExistingCassetteExceptionmessage. If your code or tests match on the message text (e.g."Can't overwrite existing cassette"), update them. The exception class and itscassetteandfailed_requestattributes are unchanged, and a newreasonattribute ("cassette_not_found","already_played","no_match"or"nothing_similar") is a sturdier thing to check (#1076). filter_post_data_parametersnow filters request bodies for every HTTP method, not justPOST(#328, #1004). Requests are filtered before they're matched, so if you match onbody, re-record cassettes that have unfilteredPUT/PATCH/DELETEbodies.vcr.filters.replace_post_data_parametersitself still only filtersPOST; use the newreplace_body_parametersfor any method.- aiohttp error responses are recorded when
raise_for_statusis used (on the session or the request), and replaying them raises like aiohttp does (#635, #925). Before, they weren't recorded, and a replayed error response didn't raise. - urllib3 (and requests) responses have
HTTPHeaderDictheaders, as they do without vcrpy, instead of anHTTPMessage(#841, #1015).
What's Changed
Much better errors when a request isn't in the cassette
The CannotOverwriteExistingCassetteException message now says what actually went wrong, shows how the closest recorded requests differ, and suggests how to fix it (#1076). It fixes:
- "Can't overwrite existing cassette" when the cassette doesn't exist (#838)
- no explanation or fix when a request is made more times than it was recorded (#516, #673, #929)
- empty output for body mismatches, which now show a short diff (#840)
- messages tens of MB long for cassettes with many similar requests, which are now summarized (#837)
For example:
Can't play back this request: it matches 1 recorded interaction, but that has already been played back.
record_mode 'none' doesn't record new requests.
GET https://example.com/foo
Cassette: tests/cassettes/ex.yaml
record_mode: none
match_on: method, scheme, host, port, path, query
interactions: 1 recorded, 1 played
How to fix:
* If it's fine for this request to get the same response each time, set
allow_playback_repeats=True [1].
* Otherwise record the extra request: run once with record_mode='new_episodes'
[2].
[1] https://vcrpy.readthedocs.io/en/latest/advanced.html#playback-repeats
[2] https://vcrpy.readthedocs.io/en/latest/usage.html#record-modes
Thanks @philsong4-ai (#1027) and @ChrisJr404 (#1037) for the PRs that started this.
Fixes
- aiohttp error responses are recorded and replayed when
raise_for_statusis used (#635, #925). Thanks @andrescollares filter_post_data_parametersfilters request bodies for every HTTP method (#328, #1004). Thanks @andrescollares- Headers sent more than once (e.g. two
Linkheaders) keep every value in urllib3 and requests responses (#947, #1015). Thanks @Sanjays2402 - The
bodymatcher no longer crashes when a body doesn't parse as its headers say, e.g. invalid JSON sent asapplication/json(#1075)
Python support
- Python 3.10 is no longer supported (#1061). Thanks @pylaterreur
Full Changelog: v8.5.0...v9.0.0