Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

For custom cassette persisters no longer catch ValueError #681

Merged
merged 3 commits into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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