Skip to content

Commit eb3b462

Browse files
authored
Revert "add max_depth arg to control recursion"
1 parent 59da11e commit eb3b462

4 files changed

Lines changed: 86 additions & 220 deletions

File tree

README.md

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ this mixin definitely suits you.
1111
- [Advanced usage](#Advanced-usage)
1212
- [Custom formats](#Custom-formats)
1313
- [Custom types](#Custom-types)
14-
- [Custom Serializer](#Custom-serializer)
1514
- [Timezones](#Timezones)
1615
- [Troubleshooting](#Troubleshooting)
1716
- [Tests](#Tests)
@@ -187,42 +186,6 @@ item.to_dict('-children.children')
187186
In this case only the first level of `children` will be included
188187
See [Max recursion](#Max-recursion)
189188

190-
Alternatively you can pass the `max_depth` kwarg, which allows to control recursion in general without having to explicitly list each attribute you don't want recursed.
191-
Please note that this check is performed at the beginning of `serialize_model()`, which means every other nested Iterable is *not affected* by this setting.
192-
The default value returned when recursion is no longer permitted is a string in the form `tablename.id` (e.g. `users.123`), with fallback to `repr(item)` in case the `id` attribute is not available for the model.
193-
This option can be disabled using `max_depth=-1`, which is also the default.
194-
```python
195-
# Stop recursion at top level, meaning all relationships will be "dropped"
196-
# For example:
197-
# item = User(id=123, name="john", articles=[Article(id=10), Article(id=11)])
198-
# will be serialized to
199-
# {
200-
# "id": 123, "name": "john",
201-
# "articles": ["articles.10", "articles.11"]
202-
# }
203-
item.to_dict(max_depth=0)
204-
205-
# Stop at first level, meaning only the first children will be processed
206-
# For example, the same user above will be serialized to:
207-
# {
208-
# "id": 123, "name": "john",
209-
# "articles": [
210-
# {"id": 10, "title": ...},
211-
# {"id": 11, "title": ...},
212-
# ]
213-
# }
214-
item.to_dict(max_depth=1)
215-
```
216-
The `max_depth` option can be also configured at mixin level, setting the `serialize_max_depth` class attribute which avoids passing it to each `to_dict` call.
217-
```python
218-
# Set max_depth option in mixin
219-
class SomeModel(db.Model, SerializerMixin):
220-
serialize_max_depth = 0
221-
222-
# Call to_dict without max_depth arg
223-
item.to_dict()
224-
```
225-
226189
# Custom formats
227190
If you want to change datetime/date/time/decimal format in one model you can specify it like below:
228191
```python
@@ -341,27 +304,6 @@ Unfortunately you can not access formats or tzinfo in that functions.
341304
I'll implement this logic later if any of users needs it.
342305

343306

344-
# Custom Serializer
345-
To have full control over the Serializer, you can define your own subclass with custom logic, and then configure the mixin to use it.
346-
```python
347-
from sqlalchemy_serializer import Serializer, SerializerMixin
348-
349-
350-
class CustomSerializer(Serializer):
351-
352-
def serialize_model(self, value) -> dict:
353-
"""Custom override adding special case for a complex model."""
354-
if isinstance(value, ComplexModel):
355-
return complex_logic(value)
356-
return super().serialize_model(value)
357-
358-
359-
class CustomSerializerMixin(SerializerMixin):
360-
361-
serialize_class = CustomSerializer
362-
```
363-
364-
365307
# Timezones
366308
To keep `datetimes` consistent its better to store it in the database normalized to **UTC**.
367309
But when you return response, sometimes (mostly in web, mobile applications can do it themselves)

sqlalchemy_serializer/serializer.py

Lines changed: 86 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,88 @@
1919
logger.setLevel(level="WARN")
2020

2121

22-
Options = namedtuple(
23-
"Options",
24-
"date_format datetime_format time_format decimal_format tzinfo serialize_types max_depth",
25-
)
22+
class SerializerMixin:
23+
"""
24+
Mixin for retrieving public fields of sqlAlchemy-model in json-compatible format
25+
with no pain
26+
It can be inherited to redefine get_tzinfo callback, datetime formats or to add
27+
some extra serialization logic
28+
"""
2629

30+
# Default exclusive schema.
31+
# If left blank, serializer becomes greedy and takes all SQLAlchemy-model's attributes
32+
serialize_only: tuple = ()
2733

28-
class IsNotSerializable(Exception):
29-
pass
34+
# Additions to default schema. Can include negative rules
35+
serialize_rules: tuple = ()
3036

37+
# Extra serialising functions
38+
serialize_types: tuple = ()
3139

32-
def get_type(value) -> str:
33-
return type(value).__name__
40+
# Custom list of fields to serialize in this model
41+
serializable_keys: tuple = ()
42+
43+
date_format = "%Y-%m-%d"
44+
datetime_format = "%Y-%m-%d %H:%M:%S"
45+
time_format = "%H:%M"
46+
decimal_format = "{}"
47+
48+
# Serialize fields of the model defined as @property automatically
49+
auto_serialize_properties: bool = False
50+
51+
def get_tzinfo(self):
52+
"""
53+
Callback to make serializer aware of user's timezone. Should be redefined if needed
54+
Example:
55+
return pytz.timezone('Africa/Abidjan')
56+
57+
:return: datetime.tzinfo
58+
"""
59+
return None
60+
61+
def to_dict(
62+
self,
63+
only=(),
64+
rules=(),
65+
date_format=None,
66+
datetime_format=None,
67+
time_format=None,
68+
tzinfo=None,
69+
decimal_format=None,
70+
serialize_types=None,
71+
):
72+
"""
73+
Returns SQLAlchemy model's data in JSON compatible format
74+
75+
For details about datetime formats follow:
76+
https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior
77+
78+
:param only: exclusive schema to replace the default one
79+
always have higher priority than rules
80+
:param rules: schema to extend default one or schema defined in "only"
81+
:param date_format: str
82+
:param datetime_format: str
83+
:param time_format: str
84+
:param decimal_format: str
85+
:param serialize_types:
86+
:param tzinfo: datetime.tzinfo converts datetimes to local user timezone
87+
:return: data: dict
88+
"""
89+
s = Serializer(
90+
date_format=date_format or self.date_format,
91+
datetime_format=datetime_format or self.datetime_format,
92+
time_format=time_format or self.time_format,
93+
decimal_format=decimal_format or self.decimal_format,
94+
tzinfo=tzinfo or self.get_tzinfo(),
95+
serialize_types=serialize_types or self.serialize_types,
96+
)
97+
return s(self, only=only, extend=rules)
98+
99+
100+
Options = namedtuple(
101+
"Options",
102+
"date_format datetime_format time_format decimal_format tzinfo serialize_types",
103+
)
34104

35105

36106
class Serializer:
@@ -123,7 +193,7 @@ def fork(self, key: str) -> "Serializer":
123193
Return new serializer for a key
124194
:return: serializer
125195
"""
126-
serializer = self.__class__(**self.opts._asdict())
196+
serializer = Serializer(**self.opts._asdict())
127197
serializer.set_serialization_depth(self.serialization_depth + 1)
128198
serializer.schema = self.schema.fork(key=key)
129199

@@ -197,12 +267,7 @@ def serialize_dict(self, value: dict) -> dict:
197267
logger.debug("Skip key:%s of dict", k)
198268
return res
199269

200-
def serialize_model(self, value) -> t.Any:
201-
# Check if user set a recursion limit and we can no longer recurse
202-
if self.serialization_depth > self.opts.max_depth > -1:
203-
# Return early calling internal helper
204-
return self.serialize_model_fallback(value)
205-
270+
def serialize_model(self, value) -> dict:
206271
self.schema.update(only=value.serialize_only, extend=value.serialize_rules)
207272

208273
res = {}
@@ -225,106 +290,14 @@ def serialize_model(self, value) -> t.Any:
225290
logger.debug("Skip key:%s of model:%s", k, get_type(value))
226291
return res
227292

228-
def serialize_model_fallback(self, value) -> t.Any:
229-
"""
230-
Called when serialization can no longer recurse.
231-
Default output is a string like "tablename.id", e.g. "users.123", if attributes
232-
are not available it will return "repr(value)".
233-
Override this method to implement own logic.
234-
:return: str
235-
"""
236-
try:
237-
return f'{value.__tablename__}.{value.id}'
238-
except AttributeError:
239-
return repr(value)
240-
241-
242-
def serialize_collection(iterable: t.Iterable, *args, **kwargs) -> list:
243-
return [item.to_dict(*args, **kwargs) for item in iterable]
244-
245-
246-
class SerializerMixin:
247-
"""
248-
Mixin for retrieving public fields of sqlAlchemy-model in json-compatible format
249-
with no pain
250-
It can be inherited to redefine get_tzinfo callback, datetime formats or to add
251-
some extra serialization logic
252-
"""
253-
254-
# Default Serializer class
255-
serialize_class: Serializer = Serializer
256-
257-
# Default exclusive schema.
258-
# If left blank, serializer becomes greedy and takes all SQLAlchemy-model's attributes
259-
serialize_only: tuple = ()
260-
261-
# Additions to default schema. Can include negative rules
262-
serialize_rules: tuple = ()
263293

264-
# Extra serialising functions
265-
serialize_types: tuple = ()
266-
267-
# Custom list of fields to serialize in this model
268-
serializable_keys: tuple = ()
269-
270-
date_format = "%Y-%m-%d"
271-
datetime_format = "%Y-%m-%d %H:%M:%S"
272-
time_format = "%H:%M"
273-
decimal_format = "{}"
274-
275-
# Maximum recursion depth, setting to "-1" disables logic
276-
serialize_max_depth: int = -1
277-
278-
# Serialize fields of the model defined as @property automatically
279-
auto_serialize_properties: bool = False
280-
281-
def get_tzinfo(self):
282-
"""
283-
Callback to make serializer aware of user's timezone. Should be redefined if needed
284-
Example:
285-
return pytz.timezone('Africa/Abidjan')
294+
class IsNotSerializable(Exception):
295+
pass
286296

287-
:return: datetime.tzinfo
288-
"""
289-
return None
290297

291-
def to_dict(
292-
self,
293-
only=(),
294-
rules=(),
295-
date_format=None,
296-
datetime_format=None,
297-
time_format=None,
298-
tzinfo=None,
299-
decimal_format=None,
300-
serialize_types=None,
301-
max_depth=None
302-
):
303-
"""
304-
Returns SQLAlchemy model's data in JSON compatible format
298+
def get_type(value) -> str:
299+
return type(value).__name__
305300

306-
For details about datetime formats follow:
307-
https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior
308301

309-
:param only: exclusive schema to replace the default one
310-
always have higher priority than rules
311-
:param rules: schema to extend default one or schema defined in "only"
312-
:param date_format: str
313-
:param datetime_format: str
314-
:param time_format: str
315-
:param decimal_format: str
316-
:param serialize_types:
317-
:param tzinfo: datetime.tzinfo converts datetimes to local user timezone
318-
:param max_depth: int maximum recursion depth allowed, "-1" disables logic
319-
:return: data: dict
320-
"""
321-
s = self.serialize_class(
322-
date_format=date_format or self.date_format,
323-
datetime_format=datetime_format or self.datetime_format,
324-
time_format=time_format or self.time_format,
325-
decimal_format=decimal_format or self.decimal_format,
326-
tzinfo=tzinfo or self.get_tzinfo(),
327-
serialize_types=serialize_types or self.serialize_types,
328-
max_depth=max_depth if max_depth is not None else self.serialize_max_depth,
329-
)
330-
return s(self, only=only, extend=rules)
302+
def serialize_collection(iterable: t.Iterable, *args, **kwargs) -> list:
303+
return [item.to_dict(*args, **kwargs) for item in iterable]

tests/test_custom_serializer_class.py

Lines changed: 0 additions & 49 deletions
This file was deleted.

0 commit comments

Comments
 (0)