Problem: an enrichment pipeline (Wikidata search -> LLM pick -> wbgetentities -> Wikipedia summaries) needed a test for items that have no labels.en (fall back to the enwiki sitelink title) or neither label nor article (drop the item). Real Wikidata items in that state are rare and get fixed by editors, so a recorded cassette would rot; hand-writing a 13-interaction cassette or committing a 450 KB edited copy was worse.
What worked (pytest-recording 0.13 + vcrpy 8, record_mode none, --block-network on):
import vcr as vcrpy
def _without_english_labels(src, dst, qids):
with open(src, encoding="utf8") as f:
tape = vcrpy.serializers.yamlserializer.deserialize(f.read())
for it in tape["interactions"]:
if "action=wbgetentities" not in it["request"]["uri"]:
continue
data = json.loads(it["response"]["body"]["string"])
for qid in qids:
data["entities"][qid]["labels"].pop("en")
it["response"]["body"]["string"] = json.dumps(data)
with open(dst, "w", encoding="utf8") as f:
f.write(vcrpy.serializers.yamlserializer.serialize(tape))
def test_label_fallbacks(vcr_config, vcr_cassette_dir, tmp_path, ...):
derived = str(tmp_path / "labels.yaml")
_without_english_labels(os.path.join(vcr_cassette_dir, MAIN_CASSETTE), derived, {Q1, Q2})
replay = {k: v for k, v in vcr_config.items() if k != "allowed_hosts"} # pytest-recording-only key
with vcrpy.VCR(record_mode="none", **replay).use_cassette(derived):
... # the WHOLE flow that the main cassette recordedThe test is unmarked (no @pytest.mark.vcr), so pytest-recording's network block stays on; vcrpy intercepts at httpx.Client.send before any socket, so the block never fires. Re-recording the main cassette regenerates this one for free.
Gotcha that cost one failed run: vcrpy matches on method+URL, not body, and serves same-URL interactions in recorded order. The main cassette started at yap creation (two LLM calls on the same generateContent URL before the pick), so entering the derived cassette only around the enrichment step made the pick call consume the first recorded LLM response (the wrong one) and silently produce zero picks. The replay context must wrap everything from the first recorded request on.
Bonus: queued-on-commit analytics events (session.info list drained in after_commit) are assertable in the same test by reading the session's pending list, without any network or mock.