From c7b1095e1d96f8ded5d94c4ad2a713d49e9f3bd6 Mon Sep 17 00:00:00 2001 From: Bobronium Date: Thu, 24 Nov 2022 14:47:19 +0400 Subject: [PATCH 1/7] Properly encode model and dataclass default for schema --- pydantic/schema.py | 10 ++++++++-- tests/test_schema.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/pydantic/schema.py b/pydantic/schema.py index 41fc8824da..156eaa4dfc 100644 --- a/pydantic/schema.py +++ b/pydantic/schema.py @@ -1,6 +1,7 @@ import re import warnings from collections import defaultdict +from dataclasses import is_dataclass from datetime import date, datetime, time, timedelta from decimal import Decimal from enum import Enum @@ -971,7 +972,11 @@ def multitypes_literal_field_for_schema(values: Tuple[Any, ...], field: ModelFie def encode_default(dft: Any) -> Any: - if isinstance(dft, Enum): + from .main import BaseModel + + if isinstance(dft, BaseModel) or is_dataclass(dft): + dft = cast(dict[str, Any], pydantic_encoder(dft)) + elif isinstance(dft, Enum): return dft.value elif isinstance(dft, (int, float, str)): return dft @@ -979,7 +984,8 @@ def encode_default(dft: Any) -> Any: t = dft.__class__ seq_args = (encode_default(v) for v in dft) return t(*seq_args) if is_namedtuple(t) else t(seq_args) - elif isinstance(dft, dict): + + if isinstance(dft, dict): return {encode_default(k): encode_default(v) for k, v in dft.items()} elif dft is None: return None diff --git a/tests/test_schema.py b/tests/test_schema.py index 9942ea2f94..b6a043302d 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1523,6 +1523,39 @@ class UserModel(BaseModel): } +def test_model_default(): + """Make sure inner model types are encoded properly""" + from pydantic import BaseModel + + class Inner(BaseModel): + a: dict[Path, str] = {Path(): ''} + + class Outer(BaseModel): + inner: Inner = Inner() + + assert Outer.schema() == { + 'definitions': { + 'Inner': { + 'properties': { + 'a': { + 'additionalProperties': {'type': 'string'}, + 'default': {'.': ''}, + 'title': 'A', + 'type': 'object', + } + }, + 'title': 'Inner', + 'type': 'object', + } + }, + 'properties': { + 'inner': {'allOf': [{'$ref': '#/definitions/Inner'}], 'default': {'a': {'.': ''}}, 'title': 'Inner'} + }, + 'title': 'Outer', + 'type': 'object', + } + + @pytest.mark.parametrize( 'kwargs,type_,expected_extra', [ From f9349baa02751602618e5069d1cc55b84ba0ef27 Mon Sep 17 00:00:00 2001 From: Bobronium Date: Thu, 24 Nov 2022 14:54:25 +0400 Subject: [PATCH 2/7] Wrap type annotation in quotes --- pydantic/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic/schema.py b/pydantic/schema.py index 156eaa4dfc..a0d3bf408c 100644 --- a/pydantic/schema.py +++ b/pydantic/schema.py @@ -975,7 +975,7 @@ def encode_default(dft: Any) -> Any: from .main import BaseModel if isinstance(dft, BaseModel) or is_dataclass(dft): - dft = cast(dict[str, Any], pydantic_encoder(dft)) + dft = cast('dict[str, Any]', pydantic_encoder(dft)) elif isinstance(dft, Enum): return dft.value elif isinstance(dft, (int, float, str)): From 3dd595141f2ff009a51270957503e9a44be19fa0 Mon Sep 17 00:00:00 2001 From: Bobronium Date: Thu, 24 Nov 2022 15:41:05 +0400 Subject: [PATCH 3/7] Fix compatibility --- tests/test_schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index b6a043302d..90edd146fe 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1528,7 +1528,7 @@ def test_model_default(): from pydantic import BaseModel class Inner(BaseModel): - a: dict[Path, str] = {Path(): ''} + a: Dict[Path, str] = {Path(): ''} class Outer(BaseModel): inner: Inner = Inner() From 74ceaafcaed19c8b098170280f6bc19d3e394476 Mon Sep 17 00:00:00 2001 From: Bobronium Date: Thu, 24 Nov 2022 16:56:00 +0400 Subject: [PATCH 4/7] Add changes file --- changes/4781-Bobronium.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/4781-Bobronium.md diff --git a/changes/4781-Bobronium.md b/changes/4781-Bobronium.md new file mode 100644 index 0000000000..26d1221db9 --- /dev/null +++ b/changes/4781-Bobronium.md @@ -0,0 +1 @@ +Fix `schema` and `schema_json` on models where a model instance is a one of default values. From 87d1a3898d144988d1f2b1d54ac316a0e94fb1c7 Mon Sep 17 00:00:00 2001 From: Arseny Boykov <36469655+Bobronium@users.noreply.github.com> Date: Tue, 29 Nov 2022 16:21:25 +0400 Subject: [PATCH 5/7] Remove unnecessary import Co-authored-by: Hasan Ramezani --- tests/test_schema.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 90edd146fe..f17bcecc45 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1525,8 +1525,6 @@ class UserModel(BaseModel): def test_model_default(): """Make sure inner model types are encoded properly""" - from pydantic import BaseModel - class Inner(BaseModel): a: Dict[Path, str] = {Path(): ''} From 6a10b6b3fcecae08704e5cb1995af2e9d69e5992 Mon Sep 17 00:00:00 2001 From: Bobronium Date: Tue, 29 Nov 2022 16:26:53 +0400 Subject: [PATCH 6/7] Add a blank line back --- tests/test_schema.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_schema.py b/tests/test_schema.py index f17bcecc45..6b070f98ed 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1525,6 +1525,7 @@ class UserModel(BaseModel): def test_model_default(): """Make sure inner model types are encoded properly""" + class Inner(BaseModel): a: Dict[Path, str] = {Path(): ''} From 8a41c4af123160ba01437299d4c90c58a2cc1f35 Mon Sep 17 00:00:00 2001 From: Bobronium Date: Tue, 29 Nov 2022 16:53:14 +0400 Subject: [PATCH 7/7] Reorder conditions As per suggestion in https://github.com/pydantic/pydantic/pull/4781#discussion_r1034709528. --- pydantic/schema.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pydantic/schema.py b/pydantic/schema.py index a0d3bf408c..727700f70a 100644 --- a/pydantic/schema.py +++ b/pydantic/schema.py @@ -976,6 +976,9 @@ def encode_default(dft: Any) -> Any: if isinstance(dft, BaseModel) or is_dataclass(dft): dft = cast('dict[str, Any]', pydantic_encoder(dft)) + + if isinstance(dft, dict): + return {encode_default(k): encode_default(v) for k, v in dft.items()} elif isinstance(dft, Enum): return dft.value elif isinstance(dft, (int, float, str)): @@ -984,9 +987,6 @@ def encode_default(dft: Any) -> Any: t = dft.__class__ seq_args = (encode_default(v) for v in dft) return t(*seq_args) if is_namedtuple(t) else t(seq_args) - - if isinstance(dft, dict): - return {encode_default(k): encode_default(v) for k, v in dft.items()} elif dft is None: return None else: