diff --git a/changes/2064-art049.md b/changes/2064-art049.md new file mode 100644 index 0000000000..426c3f379a --- /dev/null +++ b/changes/2064-art049.md @@ -0,0 +1 @@ +Add merged `json_encoders` inheritance \ No newline at end of file diff --git a/docs/examples/exporting_models_json_encoders_merge.py b/docs/examples/exporting_models_json_encoders_merge.py new file mode 100644 index 0000000000..b1562cdd08 --- /dev/null +++ b/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()) diff --git a/docs/usage/exporting_models.md b/docs/usage/exporting_models.md index 26a6b0fa4f..15cbd206b3 100644 --- a/docs/usage/exporting_models.md +++ b/docs/usage/exporting_models.md @@ -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: diff --git a/pydantic/main.py b/pydantic/main.py index 75ae9208d9..c2f8cb37f5 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -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/' diff --git a/tests/test_json.py b/tests/test_json.py index 07bfc04f7c..06a4764d7e 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -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