Skip to content

Commit fb0c605

Browse files
authored
Merge pull request #52 from n0nSmoker/feature/serialization.depth.limit
Preparations for such an impactfull limitation simplify methods for clear testing and understanding more tests to cover edge cases
2 parents a0b69c6 + 4ade014 commit fb0c605

2 files changed

Lines changed: 55 additions & 44 deletions

File tree

sqlalchemy_serializer/serializer.py

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def get_tzinfo(self):
5252
"""
5353
Callback to make serializer aware of user's timezone. Should be redefined if needed
5454
Example:
55-
return pytz.timezone('Asia/Krasnoyarsk')
55+
return pytz.timezone('Africa/Abidjan')
5656
5757
:return: datetime.tzinfo
5858
"""
@@ -114,10 +114,33 @@ class Serializer:
114114
)
115115

116116
def __init__(self, **kwargs):
117-
self.serialization_depth = 0
118-
self.opts = Options(**kwargs)
117+
self.set_serialization_depth(0)
118+
self.set_options(Options(**kwargs))
119+
self.init_callbacks()
120+
119121
self.schema = Schema()
120122

123+
def __call__(self, value, only=(), extend=()):
124+
"""
125+
Serialization starts here
126+
:param value: Value to serialize
127+
:param only: Exclusive schema of serialization
128+
:param extend: Rules that extend default schema
129+
:return: object: JSON-compatible object
130+
"""
131+
self.schema.update(only=only, extend=extend)
132+
133+
logger.debug("Call serializer for type:%s", get_type(value))
134+
return self.serialize(value)
135+
136+
def set_serialization_depth(self, value: int):
137+
self.serialization_depth = value
138+
139+
def set_options(self, opts: Options):
140+
self.opts = opts
141+
142+
def init_callbacks(self):
143+
"""Initialize callbacks"""
121144
self.serialize_types = (
122145
*(self.opts.serialize_types or ()),
123146
(self.atomic_types, lambda x: x), # Should be checked before any other type
@@ -141,22 +164,6 @@ def __init__(self, **kwargs):
141164
(SerializerMixin, self.serialize_model),
142165
)
143166

144-
def __call__(self, value, only=(), extend=()):
145-
"""
146-
Serialization starts here
147-
:param value: Value to serialize
148-
:param only: Exclusive schema of serialization
149-
:param extend: Rules that extend default schema
150-
:return: object: JSON-compatible object
151-
"""
152-
self.schema.update(only=only, extend=extend)
153-
154-
logger.debug("Call serializer for type:%s", get_type(value))
155-
return self.serialize(value)
156-
157-
def set_serialization_depth(self, value: int):
158-
self.serialization_depth = value
159-
160167
@staticmethod
161168
def is_valid_callable(func) -> bool:
162169
"""
@@ -181,17 +188,17 @@ def is_forkable(self, value):
181188
value, (Iterable, dict, SerializerMixin)
182189
)
183190

184-
def fork(self, value, key: str):
191+
def fork(self, key: str) -> "Serializer":
185192
"""
186-
Process data in a separate serializer
193+
Return new serializer for a key
187194
:return: serialized value
188195
"""
189196
serializer = Serializer(**self.opts._asdict())
190197
serializer.set_serialization_depth(self.serialization_depth + 1)
191198
serializer.schema = self.schema.fork(key=key)
192199

193-
logger.debug("Fork serializer for type:%s key:%s", get_type(value), key)
194-
return serializer(value)
200+
logger.debug("Fork serializer for key:%s", key)
201+
return serializer
195202

196203
def serialize(self, value):
197204
if self.is_valid_callable(value):
@@ -202,12 +209,23 @@ def serialize(self, value):
202209
return callback(value)
203210
raise IsNotSerializable(f"Unserializable type:{type(value)} value:{value}")
204211

212+
def serialize_with_fork(self, value, key):
213+
# TODO: merge this function with the serialize function
214+
# TODO: this should be performed after is_valid_callable check
215+
serializer = self
216+
if self.is_forkable(value):
217+
serializer = self.fork(key=key)
218+
219+
return serializer.serialize(value)
220+
205221
def serialize_iter(self, value: Iterable) -> list:
206222
res = []
207223
for v in value:
208224
try:
209225
r = self.serialize(v)
210-
except IsNotSerializable:
226+
except (
227+
IsNotSerializable
228+
): # FIXME: Why we swallow exception only in iterable?
211229
logger.warning("Can not serialize type:%s", get_type(v))
212230
continue
213231

@@ -219,10 +237,8 @@ def serialize_dict(self, value: dict) -> dict:
219237
for k, v in value.items():
220238
if self.schema.is_included(k): # TODO: Skip check if is NOT greedy
221239
logger.debug("Serialize key:%s type:%s of dict", k, get_type(v))
222-
if self.is_forkable(v):
223-
res[k] = self.fork(key=k, value=v)
224-
else:
225-
res[k] = self.serialize(v)
240+
241+
res[k] = self.serialize_with_fork(value=v, key=k)
226242
else:
227243
logger.debug("Skip key:%s of dict", k)
228244
return res
@@ -241,10 +257,7 @@ def serialize_model(self, value) -> dict:
241257
logger.debug(
242258
"Serialize key:%s type:%s model:%s", k, get_type(v), get_type(value)
243259
)
244-
if self.is_forkable(v):
245-
res[k] = self.fork(key=k, value=v)
246-
else:
247-
res[k] = self.serialize(v)
260+
res[k] = self.serialize_with_fork(value=v, key=k)
248261

249262
else:
250263
logger.debug("Skip key:%s of model:%s", k, get_type(value))
Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
1+
from sqlalchemy_serializer.serializer import Serializer
2+
3+
14
def test_fork_with_key(mocker, get_serializer):
2-
value, key, expected = "test_value", "test_key", "serialized"
5+
key = "test_value"
36
schema = mocker.MagicMock()
47
serializer = get_serializer()
58

6-
mocker.patch(
7-
"sqlalchemy_serializer.serializer.Serializer.__call__", return_value=expected
8-
)
9-
109
mocker.patch.object(serializer, "schema", schema)
11-
result = serializer.fork(value=value, key=key)
10+
result = serializer.fork(key=key)
1211

13-
assert result == expected
12+
assert isinstance(result, Serializer)
13+
assert result.opts == serializer.opts
1414
schema.fork.assert_called_once_with(key=key)
1515

1616

1717
def test_fork_logger(mocker, get_serializer):
18-
value, key = "test_value", "test_key"
18+
key = "test_key"
1919
mocked_logger = mocker.patch("sqlalchemy_serializer.serializer.logger")
2020
mocker.patch("sqlalchemy_serializer.serializer.Serializer.__call__")
2121
serializer = get_serializer()
2222

2323
mocker.patch.object(serializer, "schema", mocker.MagicMock())
24-
serializer.fork(value, key=key)
24+
serializer.fork(key=key)
2525

26-
mocked_logger.debug.assert_called_once_with(
27-
"Fork serializer for type:%s key:%s", "str", key
28-
)
26+
mocked_logger.debug.assert_called_once_with("Fork serializer for key:%s", key)

0 commit comments

Comments
 (0)