Skip to content

Commit

Permalink
Add merged json_encoders inheritance (#2064)
Browse files Browse the repository at this point in the history
* Implement merged json_encoders inheritance

* json_encoders inheritance documentation
  • Loading branch information
art049 committed Feb 13, 2021
1 parent b87e249 commit 33c5a4d
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 2 deletions.
1 change: 1 addition & 0 deletions changes/2064-art049.md
@@ -0,0 +1 @@
Add merged `json_encoders` inheritance
24 changes: 24 additions & 0 deletions docs/examples/exporting_models_json_encoders_merge.py
@@ -0,0 +1,24 @@
from datetime import datetime, timedelta
from pydantic import BaseModel
from pydantic.json import timedelta_isoformat


class BaseClassWithEncoders(BaseModel):
dt: datetime
diff: timedelta

class Config:
json_encoders = {
datetime: lambda v: v.timestamp()
}


class ChildClassWithEncoders(BaseClassWithEncoders):
class Config:
json_encoders = {
timedelta: timedelta_isoformat
}


m = ChildClassWithEncoders(dt=datetime(2032, 6, 1), diff=timedelta(hours=100))
print(m.json())
10 changes: 9 additions & 1 deletion docs/usage/exporting_models.md
Expand Up @@ -99,11 +99,19 @@ _(This script is complete, it should run "as is")_
By default, `timedelta` is encoded as a simple float of total seconds. The `timedelta_isoformat` is provided
as an optional alternative which implements ISO 8601 time diff encoding.

The `json_encoders` are also merged during the models inheritance with the child
encoders taking precedence over the parent one.

```py
{!.tmp_examples/exporting_models_json_encoders_merge.py!}
```
_(This script is complete, it should run "as is")_

### Serialising subclasses

!!! note
New in version **v1.5**.

Subclasses of common types were not automatically serialised to JSON before **v1.5**.

Subclasses of common types are automatically encoded like their super-classes:
Expand Down
8 changes: 7 additions & 1 deletion pydantic/main.py
Expand Up @@ -169,13 +169,19 @@ def prepare_field(cls, field: 'ModelField') -> None:


def inherit_config(self_config: 'ConfigType', parent_config: 'ConfigType') -> 'ConfigType':
namespace = {}
if not self_config:
base_classes = (parent_config,)
elif self_config == parent_config:
base_classes = (self_config,)
else:
base_classes = self_config, parent_config # type: ignore
return type('Config', base_classes, {})
namespace['json_encoders'] = {
**getattr(parent_config, 'json_encoders', {}),
**getattr(self_config, 'json_encoders', {}),
}

return type('Config', base_classes, namespace)


EXTRA_LINK = 'https://pydantic-docs.helpmanual.io/usage/model_config/'
Expand Down
29 changes: 29 additions & 0 deletions tests/test_json.py
Expand Up @@ -170,6 +170,35 @@ class Config:
assert m.json() == '{"x": "P0DT0H2M3.000000S"}'


def test_json_encoder_simple_inheritance():
class Parent(BaseModel):
dt: datetime.datetime = datetime.datetime.now()
timedt: datetime.timedelta = datetime.timedelta(hours=100)

class Config:
json_encoders = {datetime.datetime: lambda _: 'parent_encoder'}

class Child(Parent):
class Config:
json_encoders = {datetime.timedelta: lambda _: 'child_encoder'}

assert Child().json() == '{"dt": "parent_encoder", "timedt": "child_encoder"}'


def test_json_encoder_inheritance_override():
class Parent(BaseModel):
dt: datetime.datetime = datetime.datetime.now()

class Config:
json_encoders = {datetime.datetime: lambda _: 'parent_encoder'}

class Child(Parent):
class Config:
json_encoders = {datetime.datetime: lambda _: 'child_encoder'}

assert Child().json() == '{"dt": "child_encoder"}'


def test_custom_encoder_arg():
class Model(BaseModel):
x: datetime.timedelta
Expand Down

0 comments on commit 33c5a4d

Please sign in to comment.