Skip to content

Commit 61e2ca7

Browse files
Use SerializeAsAny for pydantic v2 models with subclasses (#2612)
* Use SerializeAsAny for pydantic v2 models; ensuring that fields of child classes will be serialized as well. Fixes #1807 * Introduce parameter and support collections using SerializeAsAny * Add coverage and one extra test case --------- Co-authored-by: Koudai Aono <koxudaxi@gmail.com>
1 parent 1d3569f commit 61e2ca7

20 files changed

Lines changed: 225 additions & 2 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,9 @@ Typing customization:
393393
corresponding con* constrained types.
394394
--use-one-literal-as-default
395395
Use one literal as default value for one literal field
396+
--use-serialize-as-any
397+
Use pydantic.SerializeAsAny for fields with types that have subtypes
398+
(Pydantic v2 only)
396399
--use-specialized-enum, --no-use-specialized-enum
397400
Don''t use specialized Enum class (StrEnum, IntEnum) even if the
398401
target Python version supports it

docs/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,9 @@ Typing customization:
385385
corresponding con* constrained types.
386386
--use-one-literal-as-default
387387
Use one literal as default value for one literal field
388+
--use-serialize-as-any
389+
Use pydantic.SerializeAsAny for fields with types that have subtypes
390+
(Pydantic v2 only)
388391
--use-specialized-enum, --no-use-specialized-enum
389392
Don''t use specialized Enum class (StrEnum, IntEnum) even if the
390393
target Python version supports it

src/datamodel_code_generator/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915
418418
http_headers: Sequence[tuple[str, str]] | None = None,
419419
http_ignore_tls: bool = False,
420420
use_annotated: bool = False,
421+
use_serialize_as_any: bool = False,
421422
use_non_positive_negative_number_constrained_types: bool = False,
422423
original_field_name_delimiter: str | None = None,
423424
use_double_quotes: bool = False,
@@ -657,6 +658,7 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]:
657658
http_headers=http_headers,
658659
http_ignore_tls=http_ignore_tls,
659660
use_annotated=use_annotated,
661+
use_serialize_as_any=use_serialize_as_any,
660662
use_non_positive_negative_number_constrained_types=use_non_positive_negative_number_constrained_types,
661663
original_field_name_delimiter=original_field_name_delimiter,
662664
use_double_quotes=use_double_quotes,

src/datamodel_code_generator/__main__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ def validate_all_exports_collision_strategy(cls, values: dict[str, Any]) -> dict
424424
http_headers: Optional[Sequence[tuple[str, str]]] = None # noqa: UP045
425425
http_ignore_tls: bool = False
426426
use_annotated: bool = False
427+
use_serialize_as_any: bool = False
427428
use_non_positive_negative_number_constrained_types: bool = False
428429
original_field_name_delimiter: Optional[str] = None # noqa: UP045
429430
use_double_quotes: bool = False
@@ -853,6 +854,7 @@ def main(args: Sequence[str] | None = None) -> Exit: # noqa: PLR0911, PLR0912,
853854
http_headers=config.http_headers,
854855
http_ignore_tls=config.http_ignore_tls,
855856
use_annotated=config.use_annotated,
857+
use_serialize_as_any=config.use_serialize_as_any,
856858
use_non_positive_negative_number_constrained_types=config.use_non_positive_negative_number_constrained_types,
857859
original_field_name_delimiter=config.original_field_name_delimiter,
858860
use_double_quotes=config.use_double_quotes,

src/datamodel_code_generator/arguments.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,12 @@ def start_section(self, heading: str | None) -> None:
354354
action="store_true",
355355
default=None,
356356
)
357+
typing_options.add_argument(
358+
"--use-serialize-as-any",
359+
help="Use pydantic.SerializeAsAny for fields with types that have subtypes (Pydantic v2 only)",
360+
action="store_true",
361+
default=None,
362+
)
357363
typing_options.add_argument(
358364
"--use-generic-container-types",
359365
help="Use generic container types for type hinting (typing.Sequence, typing.Mapping). "

src/datamodel_code_generator/model/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class Config:
137137
parent: Optional[DataModel] = None # noqa: UP045
138138
extras: dict[str, Any] = Field(default_factory=dict)
139139
use_annotated: bool = False
140+
use_serialize_as_any: bool = False
140141
has_default: bool = False
141142
use_field_description: bool = False
142143
use_inline_field_description: bool = False

src/datamodel_code_generator/model/dataclass.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ def __init__( # noqa: PLR0913, PLR0917
183183
use_pendulum: bool = False, # noqa: FBT001, FBT002
184184
target_datetime_class: DatetimeClassType = DatetimeClassType.Datetime,
185185
treat_dot_as_module: bool = False, # noqa: FBT001, FBT002
186+
use_serialize_as_any: bool = False, # noqa: FBT001, FBT002
186187
) -> None:
187188
"""Initialize type manager with datetime type mapping."""
188189
super().__init__(
@@ -195,6 +196,7 @@ def __init__( # noqa: PLR0913, PLR0917
195196
use_pendulum,
196197
target_datetime_class,
197198
treat_dot_as_module,
199+
use_serialize_as_any,
198200
)
199201

200202
datetime_map = (

src/datamodel_code_generator/model/msgspec.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ def __init__( # noqa: PLR0913, PLR0917
407407
use_pendulum: bool = False, # noqa: FBT001, FBT002
408408
target_datetime_class: DatetimeClassType | None = None,
409409
treat_dot_as_module: bool = False, # noqa: FBT001, FBT002
410+
use_serialize_as_any: bool = False, # noqa: FBT001, FBT002
410411
) -> None:
411412
"""Initialize type manager with optional datetime type mapping."""
412413
super().__init__(
@@ -419,6 +420,7 @@ def __init__( # noqa: PLR0913, PLR0917
419420
use_pendulum,
420421
target_datetime_class,
421422
treat_dot_as_module,
423+
use_serialize_as_any,
422424
)
423425

424426
datetime_map = (

src/datamodel_code_generator/model/pydantic/types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ def __init__( # noqa: PLR0913, PLR0917
175175
use_pendulum: bool = False, # noqa: FBT001, FBT002
176176
target_datetime_class: DatetimeClassType | None = None,
177177
treat_dot_as_module: bool = False, # noqa: FBT001, FBT002
178+
use_serialize_as_any: bool = False, # noqa: FBT001, FBT002
178179
) -> None:
179180
"""Initialize the DataTypeManager with Pydantic v1 type mappings."""
180181
super().__init__(
@@ -187,6 +188,7 @@ def __init__( # noqa: PLR0913, PLR0917
187188
use_pendulum,
188189
target_datetime_class,
189190
treat_dot_as_module,
191+
use_serialize_as_any,
190192
)
191193

192194
self.type_map: dict[Types, DataType] = self.type_map_factory(

src/datamodel_code_generator/model/pydantic_v2/imports.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@
1212
IMPORT_NAIVE_DATETIME = Import.from_full_path("pydantic.NaiveDatetime")
1313
IMPORT_BASE64STR = Import.from_full_path("pydantic.Base64Str")
1414
# IMPORT_BASE64STR: Used for OpenAPI strings with format "byte" (base64 encoded characters).
15+
IMPORT_SERIALIZE_AS_ANY = Import.from_full_path("pydantic.SerializeAsAny")

0 commit comments

Comments
 (0)