Skip to content

Commit

Permalink
Merge pull request #152 from betamaxpy/allow-binary-cassettes
Browse files Browse the repository at this point in the history
Add support for binary serializer storage
  • Loading branch information
sigmavirus24 committed Mar 19, 2018
2 parents 8655a6c + a8a03e0 commit 6dd3d84
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 15 deletions.
7 changes: 7 additions & 0 deletions docs/source/serializers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ implement three methods:
- :py:meth:`betamax.BaseSerializer.deserialize` is a method that takes a
string and returns the data serialized in it as a dictionary.

.. versionadded:: 0.9.0

Allow Serializers to indicate their format is a binary format via
``stored_as_binary``.

Additionally, if your Serializer is utilizing a binary format, you will want
to set the ``stored_as_binary`` attribute to ``True`` on your class.

.. autoclass:: betamax.BaseSerializer
:members:
Expand Down
2 changes: 1 addition & 1 deletion src/betamax/serializers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@


class BaseSerializer(object):

"""
Base Serializer class that provides an interface for other serializers.
Expand Down Expand Up @@ -37,6 +36,7 @@ def deserialize(self, cassette_data):
"""

name = None
stored_as_binary = False

@staticmethod
def generate_cassette_name(cassette_library_dir, cassette_name):
Expand Down
1 change: 1 addition & 0 deletions src/betamax/serializers/json_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
class JSONSerializer(BaseSerializer):
# Serializes and deserializes a cassette to JSON
name = 'json'
stored_as_binary = False

@staticmethod
def generate_cassette_name(cassette_library_dir, cassette_name):
Expand Down
15 changes: 13 additions & 2 deletions src/betamax/serializers/proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ def _ensure_path_exists(self):
if not os.path.exists(self.cassette_path):
open(self.cassette_path, 'w+').close()

def corrected_file_mode(self, base_mode):
storing_binary_data = getattr(self.proxied_serializer,
'stored_as_binary',
False)
if storing_binary_data:
return '{}b'.format(base_mode)
return base_mode

@classmethod
def find(cls, serialize_with, cassette_library_dir, cassette_name):
from . import serializer_registry
Expand All @@ -63,15 +71,18 @@ def serialize(self, cassette_data):
return

self._ensure_path_exists()
mode = self.corrected_file_mode('w')

with open(self.cassette_path, 'w') as fd:
with open(self.cassette_path, mode) as fd:
fd.write(self.proxied_serializer.serialize(cassette_data))

def deserialize(self):
self._ensure_path_exists()

data = {}
with open(self.cassette_path) as fd:
mode = self.corrected_file_mode('r')

with open(self.cassette_path, mode) as fd:
data = self.proxied_serializer.deserialize(fd.read())

return data
99 changes: 92 additions & 7 deletions tests/unit/test_serializers.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,127 @@
"""Tests for serializers."""
import os
import pytest
import unittest

from betamax.serializers import BaseSerializer, JSONSerializer
import pytest

from betamax.serializers import base
from betamax.serializers import json_serializer
from betamax.serializers import proxy


class TestJSONSerializer(unittest.TestCase):
"""Tests around the JSONSerializer default."""

def setUp(self):
"""Fixture setup."""
self.cassette_dir = 'fake_dir'
self.cassette_name = 'cassette_name'

def test_generate_cassette_name(self):
"""Verify the beaviour of generate_cassette_name."""
assert (os.path.join('fake_dir', 'cassette_name.json') ==
JSONSerializer.generate_cassette_name(self.cassette_dir,
self.cassette_name))
json_serializer.JSONSerializer.generate_cassette_name(
self.cassette_dir,
self.cassette_name,
))

def test_generate_cassette_name_with_instance(self):
serializer = JSONSerializer()
"""Verify generate_cassette_name works on an instance too."""
serializer = json_serializer.JSONSerializer()
assert (os.path.join('fake_dir', 'cassette_name.json') ==
serializer.generate_cassette_name(self.cassette_dir,
self.cassette_name))


class Serializer(BaseSerializer):
class Serializer(base.BaseSerializer):
"""Serializer to test NotImplementedError exceptions."""

name = 'test'


class BytesSerializer(base.BaseSerializer):
"""Serializer to test stored_as_binary."""

name = 'bytes-test'
stored_as_binary = True

# NOTE(sigmavirus24): These bytes, when decoded, result in a
# UnicodeDecodeError
serialized_bytes = b"hi \xAD"

def serialize(self, *args):
"""Return the problematic bytes."""
return self.serialized_bytes

def deserialize(self, *args):
"""Return the problematic bytes."""
return self.serialized_bytes


class TestBaseSerializer(unittest.TestCase):
"""Tests around BaseSerializer behaviour."""

def test_serialize_is_an_interface(self):
"""Verify we handle unimplemented methods."""
serializer = Serializer()
with pytest.raises(NotImplementedError):
serializer.serialize({})

def test_deserialize_is_an_interface(self):
"""Verify we handle unimplemented methods."""
serializer = Serializer()
with pytest.raises(NotImplementedError):
serializer.deserialize('path')

def test_requires_a_name(self):
"""Verify we handle unimplemented methods."""
with pytest.raises(ValueError):
BaseSerializer()
base.BaseSerializer()


class TestBinarySerializers(unittest.TestCase):
"""Verify the behaviour of stored_as_binary=True."""

@pytest.fixture(autouse=True)
def _setup(self):
serializer = BytesSerializer()
self.cassette_path = 'test_cassette.test'
self.proxy = proxy.SerializerProxy(
serializer,
self.cassette_path,
allow_serialization=True,
)

def test_serialize(self):
"""Verify we use the right mode with open()."""
mode = self.proxy.corrected_file_mode('w')
assert mode == 'wb'

def test_deserialize(self):
"""Verify we use the right mode with open()."""
mode = self.proxy.corrected_file_mode('r')
assert mode == 'rb'


class TestTextSerializer(unittest.TestCase):
"""Verify the default behaviour of stored_as_binary."""

@pytest.fixture(autouse=True)
def _setup(self):
serializer = Serializer()
self.cassette_path = 'test_cassette.test'
self.proxy = proxy.SerializerProxy(
serializer,
self.cassette_path,
allow_serialization=True,
)

def test_serialize(self):
"""Verify we use the right mode with open()."""
mode = self.proxy.corrected_file_mode('w')
assert mode == 'w'

def test_deserialize(self):
"""Verify we use the right mode with open()."""
mode = self.proxy.corrected_file_mode('r')
assert mode == 'r'
6 changes: 1 addition & 5 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ pip_pre = False
deps =
requests{env:REQUESTS_VERSION: >= 2.0}
pytest
pypy: mock
py26: mock < 1.1.0
py27: mock
py32: mock
py33: mock
mock
commands = py.test {posargs}

[testenv:py27-flake8]
Expand Down

0 comments on commit 6dd3d84

Please sign in to comment.