Skip to content

Commit

Permalink
Add converter for marshmallow
Browse files Browse the repository at this point in the history
  • Loading branch information
prkumar committed Dec 5, 2017
1 parent 67836fa commit 2e2b2ad
Show file tree
Hide file tree
Showing 17 changed files with 379 additions and 176 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ omit =
uplink/__about__.py
uplink/interfaces.py
uplink/clients/interfaces.py
uplink/converters/interfaces.py

[report]
# When running a summary report, show missing lines.
Expand Down
3 changes: 2 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,5 +189,6 @@
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'requests': ('http://docs.python-requests.org/en/master/', None)
'requests': ('http://docs.python-requests.org/en/master/', None),
'marshmallow': ('https://marshmallow.readthedocs.io/en/latest/', None),
}
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ twisted==17.1.0
# support (see https://github.com/aio-libs/aiohttp/issues/537)
aiohttp==2.3.0; python_version >= '3.4'

# Marshmallow Support
marshmallow==2.15.0

# Testing
pytest==3.2.1
pytest-mock==1.6.2
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ def read(filename):
]

extras_require = {
":python_version>='3.4.2'": ["aiohttp>=2.3.0"]
":python_version>='3.4.2'": ["aiohttp>=2.3.0"],
"marshmallow": ["marshmallow>=2.15.0"]
}

metadata = dict({
Expand Down
6 changes: 3 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pytest

# Local imports
from uplink import clients, hooks, interfaces, helpers
from uplink import clients, converters, hooks, interfaces, helpers


@pytest.fixture
Expand All @@ -25,12 +25,12 @@ def transaction_hook_mock(mocker):

@pytest.fixture
def converter_mock(mocker):
return mocker.Mock(spec=interfaces.Converter)
return mocker.Mock(spec=converters.interfaces.Converter)


@pytest.fixture
def converter_factory_mock(mocker):
return mocker.Mock(spec=interfaces.ConverterFactory)
return mocker.Mock(spec=converters.interfaces.ConverterFactory)


@pytest.fixture
Expand Down
12 changes: 6 additions & 6 deletions tests/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import pytest

# Local imports
from uplink import builder, converter, hooks, exceptions, utils
from uplink import builder, converters, hooks, exceptions, utils


@pytest.fixture
Expand Down Expand Up @@ -91,8 +91,8 @@ def test_call(self, mocker, request_definition, request_builder):
class TestBuilder(object):
def test_init_adds_standard_converter_factory(self, uplink_builder):
assert isinstance(
uplink_builder._converter_factories[0],
converter.StandardConverterFactory
uplink_builder._converters[0],
converters.StandardConverter
)

def test_client_getter(self, uplink_builder, http_client_mock):
Expand All @@ -110,8 +110,8 @@ def test_base_url(self, uplink_builder):
def test_add_converter_factory(self,
uplink_builder,
converter_factory_mock):
uplink_builder.add_converter_factory(converter_factory_mock)
factory = list(uplink_builder.converter_factories)[0]
uplink_builder.add_converter(converter_factory_mock)
factory = list(uplink_builder.converters)[0]
assert factory == converter_factory_mock

def test_build_failure(self, fake_service_cls):
Expand All @@ -136,5 +136,5 @@ def test_build(mocker, http_client_mock, fake_service_cls):
client=http_client_mock
)
assert builder_mock.base_url == "example.com"
builder_mock.add_converter_factory.assert_called_with()
builder_mock.add_converter.assert_called_with()
assert builder_mock.client is http_client_mock
103 changes: 0 additions & 103 deletions tests/test_converter.py

This file was deleted.

195 changes: 195 additions & 0 deletions tests/test_converters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Third party imports
import marshmallow
import pytest

# Local imports
from uplink import converters


class TestCast(object):
def test_converter_without_caster(self, converter_mock):
converter_mock.convert.return_value = 2
cast = converters.Cast(None, converter_mock)
return_value = cast.convert(1)
converter_mock.convert.assert_called_with(1)
assert return_value == 2

def test_convert_with_caster(self, mocker, converter_mock):
caster = mocker.Mock(return_value=2)
converter_mock.convert.return_value = 3
cast = converters.Cast(caster, converter_mock)
return_value = cast.convert(1)
caster.assert_called_with(1)
converter_mock.convert.assert_called_with(2)
assert return_value == 3


class TestResponseBodyConverter(object):
def test_convert(self):
converter_ = converters.ResponseBodyConverter()
response = "json response"
converted = converter_.convert(response)
assert converted == response


class TestRequestBodyConverter(object):
def test_convert_str(self):
converter_ = converters.RequestBodyConverter()
assert converter_.convert("example") == "example"

def test_convert_obj(self):
converter_ = converters.RequestBodyConverter()
example = {"hello": "2"}
assert converter_.convert(example) == example


class TestStringConverter(object):
def test_convert(self):
converter_ = converters.StringConverter()
assert converter_.convert(2) == "2"


class TestMappingConverterDecorator(object):
def test_convert(self, mocker, converter_mock):
mapping = {"key1": "value1", "key2": "value2"}
converter_mock.convert.return_value = "converted"
mapping_converter = converters.MappingConverterDecorator(converter_mock)
converted_value = mapping_converter.convert(mapping)
calls = map(mocker.call, mapping.values())
assert converter_mock.convert.mock_calls == list(calls)
assert converted_value == {"key1": "converted", "key2": "converted"}


class TestConverterFactoryRegistry(object):
backend = converters.ConverterFactoryRegistry._converter_factory_registry

def test_init_args_are_passed_to_factory(self, converter_factory_mock):
args = ("arg1", "arg2")
kwargs = {"arg3": "arg3"}
converter_factory_mock.make_string_converter.return_value = "test"
registry = converters.ConverterFactoryRegistry(
(converter_factory_mock,), *args, **kwargs)
return_value = registry[converters.CONVERT_TO_STRING]()
converter_factory_mock.make_string_converter.assert_called_with(
*args, **kwargs
)
assert return_value == "test"

def test_with_converter_map(self, converter_factory_mock):
registry = converters.ConverterFactoryRegistry(
(converter_factory_mock,))
converter_ = registry[converters.Map(converters.CONVERT_TO_STRING)]()
assert isinstance(converter_, converters.MappingConverterDecorator)

def test_len(self):
registry = converters.ConverterFactoryRegistry(())
assert len(registry) == len(self.backend)

def test_iter(self):
registry = converters.ConverterFactoryRegistry(())
assert list(iter(registry)) == list(iter(self.backend))


def test_make_request_body_converter(converter_factory_mock):
method = converters.make_request_body_converter(converter_factory_mock)
assert method is converter_factory_mock.make_request_body_converter


def test_make_response_body_converter(converter_factory_mock):
method = converters.make_response_body_converter(converter_factory_mock)
assert method is converter_factory_mock.make_response_body_converter


def test_make_string_converter(converter_factory_mock):
method = converters.make_string_converter(converter_factory_mock)
assert method is converter_factory_mock.make_string_converter


@pytest.fixture(params=["class", "instance"])
def schema_mock_and_argument(request, mocker):
class Schema(marshmallow.Schema):
def __new__(cls, *args, **kwargs):
return schema

schema = mocker.Mock(spec=marshmallow.Schema)
if request.param == "class":
return schema, Schema
else:
return schema, schema


class TestMarshmallowConverter(object):
def test_init_without_marshmallow(self):
old_marshmallow = converters.MarshmallowConverter.marshmallow
converters.MarshmallowConverter.marshmallow = None
with pytest.raises(ImportError):
converters.MarshmallowConverter()
converters.MarshmallowConverter.marshmallow = old_marshmallow

def test_make_request_body_converter(self, mocker, schema_mock_and_argument):
# Setup
schema_mock, argument = schema_mock_and_argument
expected_result = "data"
dump_result = mocker.Mock()
dump_result.data = expected_result
schema_mock.dump.return_value = dump_result
converter = converters.MarshmallowConverter()
request_body = {"id": 0}

# Run
c = converter.make_request_body_converter(argument)
result = c.convert(request_body)

# Verify
schema_mock.dump.assert_called_with(request_body)
assert expected_result == result

def test_make_request_body_converter_without_schema(self):
# Setup
converter = converters.MarshmallowConverter()

# Run
c = converter.make_request_body_converter("not a schema")

# Verify
assert c is None

def test_make_response_body_converter(self, mocker, schema_mock_and_argument):
# Setup
schema_mock, argument = schema_mock_and_argument
expected_result = "data"
load_result = mocker.Mock()
load_result.data = expected_result
schema_mock.load.return_value = load_result
converter = converters.MarshmallowConverter()
response = mocker.Mock()

# Run
c = converter.make_response_body_converter(argument)
result = c.convert(response)

# Verify
response.json.assert_called_with()
schema_mock.load.assert_called_with(response.json())
assert expected_result == result

def test_make_response_body_converter_without_schema(self):
# Setup
converter = converters.MarshmallowConverter()

# Run
c = converter.make_response_body_converter("not a schema")

# Verify
assert c is None

def test_make_string_converter(self, schema_mock_and_argument):
# Setup
_, argument = schema_mock_and_argument
converter = converters.MarshmallowConverter()

# Run
c = converter.make_string_converter(argument)

# Verify
assert c is None
Loading

0 comments on commit 2e2b2ad

Please sign in to comment.