fix: keep old behaviour of json() by default (#3542)

* fix: handle basemodel fallback for custom encoders

* put back old behaviour and add to_dict

* typo

Co-authored-by: Christian Bundy <christianbundy@fraction.io>

Co-authored-by: Christian Bundy <christianbundy@fraction.io>
This commit is contained in:
Eric Jolibois
2021-12-24 14:14:13 +01:00
committed by GitHub
parent e14e7561ed
commit edad0dbc46
4 changed files with 62 additions and 7 deletions
@@ -31,4 +31,4 @@ wolfgang = User(
User(name='John', address=Address(city='London', country='UK')),
],
)
print(wolfgang.json())
print(wolfgang.json(models_as_dict=False))
+3
View File
@@ -109,6 +109,9 @@ _(This script is complete, it should run "as is")_
### Serialising self-reference or other models
By default, models are serialised as dictionaries.
If you want to serialise them differently, you can add `models_as_dict=False` when calling `json()` method
and add the classes of the model in `json_encoders`.
In case of forward references, you can use a string with the class name instead of the class itself
```py
{!.tmp_examples/exporting_models_json_forward_ref.py!}
+4 -2
View File
@@ -454,6 +454,7 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
exclude_defaults: bool = False,
exclude_none: bool = False,
encoder: Optional[Callable[[Any], Any]] = None,
models_as_dict: bool = True,
**dumps_kwargs: Any,
) -> str:
"""
@@ -469,11 +470,12 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
exclude_unset = skip_defaults
encoder = cast(Callable[[Any], Any], encoder or self.__json_encoder__)
# We don't directly call `self.dict()`, which does exactly the same thing but
# with `to_dict = True` because we want to keep raw `BaseModel` instances and not as `dict`.
# We don't directly call `self.dict()`, which does exactly this with `to_dict=True`
# because we want to be able to keep raw `BaseModel` instances and not as `dict`.
# This allows users to write custom JSON encoders for given `BaseModel` classes.
data = dict(
self._iter(
to_dict=models_as_dict,
by_alias=by_alias,
include=include,
exclude=exclude,
+54 -4
View File
@@ -281,7 +281,7 @@ def test_custom_decode_encode():
assert m.json() == '{\n "a": 1,\n "b": "foo"\n}'
def test_json_nested_encode():
def test_json_nested_encode_models():
class Phone(BaseModel):
manufacturer: str
number: int
@@ -314,8 +314,58 @@ def test_json_nested_encode():
timon.friend = pumbaa
assert iphone.json() == '{"manufacturer": "Apple", "number": 18002752273}'
assert iphone.json(models_as_dict=False) == '{"manufacturer": "Apple", "number": 18002752273}'
assert (
pumbaa.json() == '{"name": "Pumbaa", "SSN": 234, "birthday": 737424000.0, "phone": 18007267864, "friend": null}'
pumbaa.json(models_as_dict=False)
== '{"name": "Pumbaa", "SSN": 234, "birthday": 737424000.0, "phone": 18007267864, "friend": null}'
)
assert timon.json() == '{"name": "Timon", "SSN": 123, "birthday": 738892800.0, "phone": 18002752273, "friend": 234}'
assert (
timon.json(models_as_dict=False)
== '{"name": "Timon", "SSN": 123, "birthday": 738892800.0, "phone": 18002752273, "friend": 234}'
)
def test_custom_encode_fallback_basemodel():
class MyExoticType:
pass
def custom_encoder(o):
if isinstance(o, MyExoticType):
return 'exo'
raise TypeError('not serialisable')
class Foo(BaseModel):
x: MyExoticType
class Config:
arbitrary_types_allowed = True
class Bar(BaseModel):
foo: Foo
assert Bar(foo=Foo(x=MyExoticType())).json(encoder=custom_encoder) == '{"foo": {"x": "exo"}}'
def test_custom_encode_error():
class MyExoticType:
pass
def custom_encoder(o):
raise TypeError('not serialisable')
class Foo(BaseModel):
x: MyExoticType
class Config:
arbitrary_types_allowed = True
with pytest.raises(TypeError, match='not serialisable'):
Foo(x=MyExoticType()).json(encoder=custom_encoder)
def test_recursive():
class Model(BaseModel):
value: Optional[str]
nested: Optional[BaseModel]
assert Model(value=None, nested=Model(value=None)).json(exclude_none=True) == '{"nested": {}}'