Skip to content

Commit

Permalink
Split persister errors into CassetteNotFoundError and CassetteDecodeE…
Browse files Browse the repository at this point in the history
…rror (#681)
  • Loading branch information
amosjyng authored Jun 26, 2023
1 parent 8c03c37 commit d99593b
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 7 deletions.
3 changes: 2 additions & 1 deletion docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ Create your own persistence class, see the example below:

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

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

Expand Down
36 changes: 35 additions & 1 deletion tests/integration/test_register_persister.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import os
from urllib.request import urlopen

import pytest

# Internal imports
import vcr
from vcr.persisters.filesystem import FilesystemPersister
from vcr.persisters.filesystem import CassetteDecodeError, CassetteNotFoundError, FilesystemPersister


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


class BadPersister(FilesystemPersister):
"""A bad persister that raises different errors."""

@staticmethod
def load_cassette(cassette_path, serializer):
if "nonexistent" in cassette_path:
raise CassetteNotFoundError()
elif "encoding" in cassette_path:
raise CassetteDecodeError()
else:
raise ValueError("buggy persister")


def test_save_cassette_with_custom_persister(tmpdir, httpbin):
"""Ensure you can save a cassette using custom persister"""
my_vcr = vcr.VCR()
Expand Down Expand Up @@ -53,3 +68,22 @@ def test_load_cassette_with_custom_persister(tmpdir, httpbin):
with my_vcr.use_cassette(test_fixture, serializer="json"):
response = urlopen(httpbin.url).read()
assert b"difficult sometimes" in response


def test_load_cassette_persister_exception_handling(tmpdir, httpbin):
"""
Ensure expected errors from persister are swallowed while unexpected ones
are passed up the call stack.
"""
my_vcr = vcr.VCR()
my_vcr.register_persister(BadPersister)

with my_vcr.use_cassette("bad/nonexistent") as cass:
assert len(cass) == 0

with my_vcr.use_cassette("bad/encoding") as cass:
assert len(cass) == 0

with pytest.raises(ValueError):
with my_vcr.use_cassette("bad/buggy") as cass:
pass
13 changes: 13 additions & 0 deletions tests/unit/test_cassettes.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ def test_cassette_load(tmpdir):
assert len(a_cassette) == 1


def test_cassette_load_nonexistent():
a_cassette = Cassette.load(path="something/nonexistent.yml")
assert len(a_cassette) == 0


def test_cassette_load_invalid_encoding(tmpdir):
a_file = tmpdir.join("invalid_encoding.yml")
with open(a_file, "wb") as fd:
fd.write(b"\xda")
a_cassette = Cassette.load(path=str(a_file))
assert len(a_cassette) == 0


def test_cassette_not_played():
a = Cassette("test")
assert not a.play_count
Expand Down
4 changes: 2 additions & 2 deletions vcr/cassette.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from .errors import UnhandledHTTPRequestError
from .matchers import get_matchers_results, method, requests_match, uri
from .patch import CassettePatcherBuilder
from .persisters.filesystem import FilesystemPersister
from .persisters.filesystem import CassetteDecodeError, CassetteNotFoundError, FilesystemPersister
from .record_mode import RecordMode
from .serializers import yamlserializer
from .util import partition_dict
Expand Down Expand Up @@ -352,7 +352,7 @@ def _load(self):
self.append(request, response)
self.dirty = False
self.rewound = True
except ValueError:
except (CassetteDecodeError, CassetteNotFoundError):
pass

def __str__(self):
Expand Down
14 changes: 11 additions & 3 deletions vcr/persisters/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,25 @@
from ..serialize import deserialize, serialize


class CassetteNotFoundError(FileNotFoundError):
pass


class CassetteDecodeError(ValueError):
pass


class FilesystemPersister:
@classmethod
def load_cassette(cls, cassette_path, serializer):
cassette_path = Path(cassette_path) # if cassette path is already Path this is no operation
if not cassette_path.is_file():
raise ValueError("Cassette not found.")
raise CassetteNotFoundError()
try:
with cassette_path.open() as f:
data = f.read()
except UnicodeEncodeError as err:
raise ValueError("Can't read Cassette, Encoding is broken") from err
except UnicodeDecodeError as err:
raise CassetteDecodeError("Can't read Cassette, Encoding is broken") from err

return deserialize(data, serializer)

Expand Down

0 comments on commit d99593b

Please sign in to comment.