Skip to content

Commit d99593b

Browse files
authored
Split persister errors into CassetteNotFoundError and CassetteDecodeError (#681)
1 parent 8c03c37 commit d99593b

File tree

5 files changed

+63
-7
lines changed

5 files changed

+63
-7
lines changed

docs/advanced.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ Create your own persistence class, see the example below:
136136

137137
Your custom persister must implement both ``load_cassette`` and ``save_cassette``
138138
methods. The ``load_cassette`` method must return a deserialized cassette or raise
139-
``ValueError`` if no cassette is found.
139+
either ``CassetteNotFoundError`` if no cassette is found, or ``CassetteDecodeError``
140+
if the cassette cannot be successfully deserialized.
140141

141142
Once the persister class is defined, register with VCR like so...
142143

tests/integration/test_register_persister.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
import os
66
from urllib.request import urlopen
77

8+
import pytest
9+
810
# Internal imports
911
import vcr
10-
from vcr.persisters.filesystem import FilesystemPersister
12+
from vcr.persisters.filesystem import CassetteDecodeError, CassetteNotFoundError, FilesystemPersister
1113

1214

1315
class CustomFilesystemPersister:
@@ -25,6 +27,19 @@ def save_cassette(cassette_path, cassette_dict, serializer):
2527
FilesystemPersister.save_cassette(cassette_path, cassette_dict, serializer)
2628

2729

30+
class BadPersister(FilesystemPersister):
31+
"""A bad persister that raises different errors."""
32+
33+
@staticmethod
34+
def load_cassette(cassette_path, serializer):
35+
if "nonexistent" in cassette_path:
36+
raise CassetteNotFoundError()
37+
elif "encoding" in cassette_path:
38+
raise CassetteDecodeError()
39+
else:
40+
raise ValueError("buggy persister")
41+
42+
2843
def test_save_cassette_with_custom_persister(tmpdir, httpbin):
2944
"""Ensure you can save a cassette using custom persister"""
3045
my_vcr = vcr.VCR()
@@ -53,3 +68,22 @@ def test_load_cassette_with_custom_persister(tmpdir, httpbin):
5368
with my_vcr.use_cassette(test_fixture, serializer="json"):
5469
response = urlopen(httpbin.url).read()
5570
assert b"difficult sometimes" in response
71+
72+
73+
def test_load_cassette_persister_exception_handling(tmpdir, httpbin):
74+
"""
75+
Ensure expected errors from persister are swallowed while unexpected ones
76+
are passed up the call stack.
77+
"""
78+
my_vcr = vcr.VCR()
79+
my_vcr.register_persister(BadPersister)
80+
81+
with my_vcr.use_cassette("bad/nonexistent") as cass:
82+
assert len(cass) == 0
83+
84+
with my_vcr.use_cassette("bad/encoding") as cass:
85+
assert len(cass) == 0
86+
87+
with pytest.raises(ValueError):
88+
with my_vcr.use_cassette("bad/buggy") as cass:
89+
pass

tests/unit/test_cassettes.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@ def test_cassette_load(tmpdir):
2929
assert len(a_cassette) == 1
3030

3131

32+
def test_cassette_load_nonexistent():
33+
a_cassette = Cassette.load(path="something/nonexistent.yml")
34+
assert len(a_cassette) == 0
35+
36+
37+
def test_cassette_load_invalid_encoding(tmpdir):
38+
a_file = tmpdir.join("invalid_encoding.yml")
39+
with open(a_file, "wb") as fd:
40+
fd.write(b"\xda")
41+
a_cassette = Cassette.load(path=str(a_file))
42+
assert len(a_cassette) == 0
43+
44+
3245
def test_cassette_not_played():
3346
a = Cassette("test")
3447
assert not a.play_count

vcr/cassette.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .errors import UnhandledHTTPRequestError
1212
from .matchers import get_matchers_results, method, requests_match, uri
1313
from .patch import CassettePatcherBuilder
14-
from .persisters.filesystem import FilesystemPersister
14+
from .persisters.filesystem import CassetteDecodeError, CassetteNotFoundError, FilesystemPersister
1515
from .record_mode import RecordMode
1616
from .serializers import yamlserializer
1717
from .util import partition_dict
@@ -352,7 +352,7 @@ def _load(self):
352352
self.append(request, response)
353353
self.dirty = False
354354
self.rewound = True
355-
except ValueError:
355+
except (CassetteDecodeError, CassetteNotFoundError):
356356
pass
357357

358358
def __str__(self):

vcr/persisters/filesystem.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,25 @@
55
from ..serialize import deserialize, serialize
66

77

8+
class CassetteNotFoundError(FileNotFoundError):
9+
pass
10+
11+
12+
class CassetteDecodeError(ValueError):
13+
pass
14+
15+
816
class FilesystemPersister:
917
@classmethod
1018
def load_cassette(cls, cassette_path, serializer):
1119
cassette_path = Path(cassette_path) # if cassette path is already Path this is no operation
1220
if not cassette_path.is_file():
13-
raise ValueError("Cassette not found.")
21+
raise CassetteNotFoundError()
1422
try:
1523
with cassette_path.open() as f:
1624
data = f.read()
17-
except UnicodeEncodeError as err:
18-
raise ValueError("Can't read Cassette, Encoding is broken") from err
25+
except UnicodeDecodeError as err:
26+
raise CassetteDecodeError("Can't read Cassette, Encoding is broken") from err
1927

2028
return deserialize(data, serializer)
2129

0 commit comments

Comments
 (0)