Skip to content

Commit cb2acb1

Browse files
authored
Ensure serializers are instances (#379)
1 parent f9b19b1 commit cb2acb1

15 files changed

+154
-68
lines changed

.codecov.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ codecov:
44

55
coverage:
66
status:
7-
patch: yes
8-
changes: yes
7+
patch: no
8+
changes: no
99

1010
comment:
1111
layout: "reach, diff, flags, files, footer"

Makefile

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,29 @@ lint:
55
flake8
66

77
install-dev:
8-
pipenv install '-e .' --dev
8+
pipenv install --dev
99

1010
pylint:
1111
pylint --disable=C0111 aiocache
1212

1313
unit:
14-
coverage run -m pytest tests/ut
14+
pipenv run coverage run -m pytest tests/ut
1515
@if [ $(cov-report) = true ]; then\
1616
coverage combine;\
1717
coverage report;\
1818
fi
1919

2020
acceptance:
21-
pytest -sv tests/acceptance
21+
pipenv run pytest -sv tests/acceptance
2222

2323
doc:
2424
make -C docs/ html
2525

2626
functional:
27-
bash examples/run_all.sh
27+
pipenv run bash examples/run_all.sh
2828

2929
performance:
30-
pytest -sv tests/performance
30+
pipenv run pytest -sv tests/performance
3131

3232
test: lint unit acceptance functional
3333

Pipfile.lock

+11-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

aiocache/backends/memcached.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class MemcachedCache(MemcachedBackend, BaseCache):
130130
131131
Config options are:
132132
133-
:param serializer: obj derived from :class:`aiocache.serializers.StringSerializer`.
133+
:param serializer: obj derived from :class:`aiocache.serializers.BaseSerializer`.
134134
:param plugins: list of :class:`aiocache.plugins.BasePlugin` derived classes.
135135
:param namespace: string to use as default prefix for the key used in all operations of
136136
the backend. Default is None

aiocache/backends/memory.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class SimpleMemoryCache(SimpleMemoryBackend, BaseCache):
112112
113113
Config options are:
114114
115-
:param serializer: obj derived from :class:`aiocache.serializers.StringSerializer`.
115+
:param serializer: obj derived from :class:`aiocache.serializers.BaseSerializer`.
116116
:param plugins: list of :class:`aiocache.plugins.BasePlugin` derived classes.
117117
:param namespace: string to use as default prefix for the key used in all operations of
118118
the backend. Default is None.

aiocache/backends/redis.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ class RedisCache(RedisBackend, BaseCache):
221221
222222
Config options are:
223223
224-
:param serializer: obj derived from :class:`aiocache.serializers.StringSerializer`.
224+
:param serializer: obj derived from :class:`aiocache.serializers.BaseSerializer`.
225225
:param plugins: list of :class:`aiocache.plugins.BasePlugin` derived classes.
226226
:param namespace: string to use as default prefix for the key used in all operations of
227227
the backend. Default is None.

aiocache/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class BaseCache:
8585
Base class that agregates the common logic for the different caches that may exist. Cache
8686
related available options are:
8787
88-
:param serializer: obj derived from :class:`aiocache.serializers.StringSerializer`. Default is
88+
:param serializer: obj derived from :class:`aiocache.serializers.BaseSerializer`. Default is
8989
:class:`aiocache.serializers.StringSerializer`.
9090
:param plugins: list of :class:`aiocache.plugins.BasePlugin` derived classes. Default is empty
9191
list.

aiocache/serializers.py

+43-35
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,25 @@
1111
import json
1212

1313

14-
class NullSerializer:
14+
_NOT_SET = object()
15+
16+
17+
class BaseSerializer:
18+
19+
DEFAULT_ENCODING = 'utf-8'
20+
21+
def __init__(self, *args, encoding=_NOT_SET, **kwargs):
22+
self.encoding = self.DEFAULT_ENCODING if encoding is _NOT_SET else encoding
23+
super().__init__(*args, **kwargs)
24+
25+
def dumps(self, value):
26+
raise NotImplementedError('dumps method must be implemented')
27+
28+
def loads(self, value):
29+
raise NotImplementedError('loads method must be implemented')
30+
31+
32+
class NullSerializer(BaseSerializer):
1533
"""
1634
This serializer does nothing. Its only recommended to be used by
1735
:class:`aiocache.SimpleMemoryCache` because for other backends it will
@@ -26,24 +44,20 @@ class NullSerializer:
2644
my_list.append(2)
2745
await cache.get("key") # Will return [1, 2]
2846
"""
29-
encoding = 'utf-8'
30-
31-
@classmethod
32-
def dumps(cls, value):
47+
def dumps(self, value):
3348
"""
3449
Returns the same value
3550
"""
3651
return value
3752

38-
@classmethod
39-
def loads(cls, value):
53+
def loads(self, value):
4054
"""
4155
Returns the same value
4256
"""
4357
return value
4458

4559

46-
class StringSerializer:
60+
class StringSerializer(BaseSerializer):
4761
"""
4862
Converts all input values to str. All return values are also str. Be
4963
careful because this means that if you store an ``int(1)``, you will get
@@ -54,13 +68,7 @@ class StringSerializer:
5468
If you want to keep python types, use ``PickleSerializer``. ``JsonSerializer``
5569
may also be useful to keep type of symple python types.
5670
"""
57-
encoding = 'utf-8'
58-
59-
def __init__(self, *args, **kwargs):
60-
super().__init__(*args, **kwargs)
61-
62-
@classmethod
63-
def dumps(cls, value):
71+
def dumps(self, value):
6472
"""
6573
Serialize the received value casting it to str.
6674
@@ -69,22 +77,20 @@ def dumps(cls, value):
6977
"""
7078
return str(value)
7179

72-
@classmethod
73-
def loads(cls, value):
80+
def loads(self, value):
7481
"""
7582
Returns value back without transformations
7683
"""
7784
return value
7885

7986

80-
class PickleSerializer(StringSerializer):
87+
class PickleSerializer(BaseSerializer):
8188
"""
8289
Transform data to bytes using pickle.dumps and pickle.loads to retrieve it back.
8390
"""
84-
encoding = None
91+
DEFAULT_ENCODING = None
8592

86-
@classmethod
87-
def dumps(cls, value):
93+
def dumps(self, value):
8894
"""
8995
Serialize the received value using ``pickle.dumps``.
9096
@@ -93,8 +99,7 @@ def dumps(cls, value):
9399
"""
94100
return pickle.dumps(value)
95101

96-
@classmethod
97-
def loads(cls, value):
102+
def loads(self, value):
98103
"""
99104
Deserialize value using ``pickle.loads``.
100105
@@ -106,7 +111,7 @@ def loads(cls, value):
106111
return pickle.loads(value)
107112

108113

109-
class JsonSerializer(StringSerializer):
114+
class JsonSerializer(BaseSerializer):
110115
"""
111116
Transform data to json string with json.dumps and json.loads to retrieve it back. Check
112117
https://docs.python.org/3/library/json.html#py-to-json-table for how types are converted.
@@ -116,9 +121,7 @@ class JsonSerializer(StringSerializer):
116121
- ujson dumps supports bytes while json doesn't
117122
- ujson and json outputs may differ sometimes
118123
"""
119-
120-
@classmethod
121-
def dumps(cls, value):
124+
def dumps(self, value):
122125
"""
123126
Serialize the received value using ``json.dumps``.
124127
@@ -127,8 +130,7 @@ def dumps(cls, value):
127130
"""
128131
return json.dumps(value)
129132

130-
@classmethod
131-
def loads(cls, value):
133+
def loads(self, value):
132134
"""
133135
Deserialize value using ``json.loads``.
134136
@@ -140,14 +142,21 @@ def loads(cls, value):
140142
return json.loads(value)
141143

142144

143-
class MsgPackSerializer(StringSerializer):
145+
class MsgPackSerializer(BaseSerializer):
144146
"""
145147
Transform data to bytes using msgpack.dumps and msgpack.loads to retrieve it back. You need
146148
to have ``msgpack`` installed in order to be able to use this serializer.
149+
150+
:param encoding: str. Can be used to change encoding param for ``msg.loads`` method.
151+
Default is utf-8.
152+
:param use_list: bool. Can be used to change use_list param for ``msgpack.loads`` method.
153+
Default is True.
147154
"""
155+
def __init__(self, *args, use_list=True, **kwargs):
156+
self.use_list = use_list
157+
super().__init__(*args, **kwargs)
148158

149-
@classmethod
150-
def dumps(cls, value):
159+
def dumps(self, value):
151160
"""
152161
Serialize the received value using ``msgpack.dumps``.
153162
@@ -156,8 +165,7 @@ def dumps(cls, value):
156165
"""
157166
return msgpack.dumps(value)
158167

159-
@classmethod
160-
def loads(cls, value):
168+
def loads(self, value):
161169
"""
162170
Deserialize value using ``msgpack.loads``.
163171
@@ -166,4 +174,4 @@ def loads(cls, value):
166174
"""
167175
if value is None:
168176
return None
169-
return msgpack.loads(value, encoding=cls.encoding)
177+
return msgpack.loads(value, encoding=self.encoding, use_list=self.use_list)

examples/marshmallow_serializer_class.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from marshmallow import fields, Schema, post_load
66

77
from aiocache import SimpleMemoryCache
8-
from aiocache.serializers import StringSerializer
8+
from aiocache.serializers import BaseSerializer
99

1010

1111
class RandomModel:
@@ -21,12 +21,16 @@ def __eq__(self, obj):
2121
return self.__dict__ == obj.__dict__
2222

2323

24-
class MarshmallowSerializer(Schema, StringSerializer):
24+
class MarshmallowSerializer(Schema, BaseSerializer):
2525
int_type = fields.Integer()
2626
str_type = fields.String()
2727
dict_type = fields.Dict()
2828
list_type = fields.List(fields.Integer())
2929

30+
# marshmallow Schema class doesn't play nicely with multiple inheritance and won't call
31+
# BaseSerializer.__init__
32+
encoding = 'utf-8'
33+
3034
def dumps(self, *args, **kwargs):
3135
# dumps returns (data, errors), we just want to save data
3236
return super().dumps(*args, **kwargs).data

examples/serializer_class.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
import zlib
33

44
from aiocache import RedisCache
5-
from aiocache.serializers import StringSerializer
5+
from aiocache.serializers import BaseSerializer
66

77

8-
class CompressionSerializer(StringSerializer):
8+
class CompressionSerializer(BaseSerializer):
99

1010
# This is needed because zlib works with bytes.
1111
# this way the underlying backend knows how to
1212
# store/retrieve values
13-
encoding = None
13+
DEFAULT_ENCODING = None
1414

1515
def dumps(self, value):
1616
print("I've received:\n{}".format(value))

tests/acceptance/test_serializers.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
from marshmallow import fields, Schema, post_load
1313

14-
from aiocache.serializers import NullSerializer, StringSerializer, PickleSerializer, JsonSerializer
14+
from aiocache.serializers import (
15+
BaseSerializer, NullSerializer, StringSerializer, PickleSerializer, JsonSerializer)
1516

1617

1718
class MyType:
@@ -24,8 +25,9 @@ def __eq__(self, obj):
2425
return self.__dict__ == obj.__dict__
2526

2627

27-
class MyTypeSchema(Schema, StringSerializer):
28+
class MyTypeSchema(Schema, BaseSerializer):
2829
r = fields.Integer()
30+
encoding = 'utf-8'
2931

3032
def dumps(self, *args, **kwargs):
3133
return super().dumps(*args, **kwargs).data
@@ -161,7 +163,7 @@ class TestAltSerializers:
161163

162164
@pytest.mark.asyncio
163165
async def test_get_set_alt_serializer_functions(self, cache):
164-
cache.serializer = StringSerializer
166+
cache.serializer = StringSerializer()
165167
await cache.set(pytest.KEY, "value", dumps_fn=dumps)
166168
assert await cache.get(pytest.KEY) == "v4lu3"
167169
assert await cache.get(pytest.KEY, loads_fn=loads) == "value"

tests/ut/conftest.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from aiocache.base import BaseCache, API
55
from aiocache import caches, RedisCache, MemcachedCache
66
from aiocache.plugins import BasePlugin
7-
from aiocache.serializers import NullSerializer
7+
from aiocache.serializers import BaseSerializer
88

99

1010
def pytest_namespace():
@@ -56,7 +56,8 @@ def mock_cache(mocker):
5656
for cmd in API.CMDS:
5757
mocker.spy(cache, cmd.__name__)
5858
mocker.spy(cache, "close")
59-
cache.serializer = asynctest.Mock(spec=NullSerializer)
59+
cache.serializer = asynctest.Mock(spec=BaseSerializer)
60+
cache.serializer.encoding = 'utf-8'
6061
cache.plugins = [asynctest.Mock(spec=BasePlugin)]
6162
return cache
6263

0 commit comments

Comments
 (0)