Skip to content

Releases: koxudaxi/datamodel-code-generator

0.56.1

16 Apr 17:08
6274b70

Choose a tag to compare

What's Changed

  • Fix --base-class-map and --enum-field-as-literal-map long inline json support by @ilovelinux in #3075
  • Prefer CLI input over pyproject url by @koxudaxi in #3083
  • Fix relative URL refs with path-only root ids by @koxudaxi in #3085
  • tomli was merged under the name tomllib into std library 3.11 by @a-detiste in #3088
  • Fix root model reuse collapse by @koxudaxi in #3089
  • Docs: describe --keep-model-order as deterministic dependency-aware ordering by @koxudaxi in #3090

New Contributors

Full Changelog: 0.56.0...0.56.1

0.56.0

04 Apr 09:45
52d9ef9

Choose a tag to compare

Breaking Changes

Code Generation Changes

  • Generated default field syntax changed - Fields with structured defaults (dicts, lists, model references) now use Field(default_value, validate_default=True) instead of default_factory=lambda: TypeAdapter(...).validate_python(...) or default_factory=lambda: Model.model_validate(...). This produces simpler, more readable code but changes the generated output format. (#3050)
  • TypeAdapter import removed from generated code - Generated models no longer import TypeAdapter from pydantic since validate_default=True handles validation natively. (#3050)
  • Default value handling for model-referencing fields rewritten - Fields with defaults referencing Pydantic models (BaseModel, RootModel, type aliases) now generate Field(<raw_value>, validate_default=True) instead of default_factory=lambda: Model.model_validate(...), default_factory=lambda: TypeAdapter(...).validate_python(...), or default_factory=lambda: Model(...). Empty collection defaults changed from default_factory=list/default_factory=dict to Field([], validate_default=True)/Field({}, validate_default=True). The generated code is semantically equivalent under Pydantic v2 but textually different, which will break snapshot tests or tooling that matches exact output. pydantic.TypeAdapter is no longer imported in generated code. (#3070)
  • Default values for model-referencing fields now use validate_default=True instead of default_factory lambdas - Fields with structured defaults (dicts, lists, or scalars referencing Pydantic models/RootModels) previously generated default_factory=lambda: ModelName.model_validate(value) or default_factory=lambda: ModelName(value). They now generate Field(value, validate_default=True), producing simpler but different output. Empty collection defaults changed from default_factory=list/default_factory=dict to Field([], validate_default=True)/Field({}, validate_default=True). Users who regenerate code will see different output. (#3071)
    Before:
    count: CountType | None = Field(default_factory=lambda: CountType(10))
    items: dict[str, Item] | None = Field(default_factory=dict, title='Items')
    After:
    count: CountType | None = Field(10, validate_default=True)
    items: dict[str, Item] | None = Field({}, title='Items', validate_default=True)
  • Default values for fields referencing models now use validate_default=True instead of default_factory=lambda: - Fields with structured defaults (dicts/lists) that reference Pydantic models previously generated default_factory=lambda: Model.model_validate(...) or default_factory=lambda: TypeAdapter(Type).validate_python(...) patterns. They now generate the raw default value directly with validate_default=True (e.g., Field({'key': 'val'}, validate_default=True) instead of Field(default_factory=lambda: Model.model_validate({'key': 'val'}))). This changes the generated code output and may affect users who depend on the exact generated code structure, pin generated output in tests, or use custom post-processing. The runtime behavior should be equivalent for Pydantic v2 users. (#3072)
  • TypeAdapter import removed from generated code - Generated code no longer imports pydantic.TypeAdapter for default value handling. Code that previously used TypeAdapter(...).validate_python(...) in default factories now uses inline defaults with validate_default=True. (#3072)
  • Integer and boolean discriminator values now supported in generated Literal types - Discriminator fields previously only generated string literal values. They now support int and bool discriminator values (e.g., Literal[1] instead of Literal['1']), which changes generated code for schemas using integer discriminator mappings. (#3072)

API/CLI Changes

  • ValidatedDefault and WrappedDefault classes removed - These internal classes were exported from datamodel_code_generator.model.base and have been removed. Code importing these types will break:
    # Before (broken)
    from datamodel_code_generator.model.base import ValidatedDefault, WrappedDefault
    (#3050)
  • SUPPORTS_WRAPPED_DEFAULT and SUPPORTS_VALIDATED_DEFAULT class variables removed - These flags were removed from the DataModel base class. Custom model classes that override these variables will see attribute errors. (#3050)
  • Internal types ValidatedDefault and WrappedDefault removed - The datamodel_code_generator.model._types module was deleted and ValidatedDefault/WrappedDefault are no longer exported from datamodel_code_generator.model.base. Code that imports or subclasses these types will break. The SUPPORTS_WRAPPED_DEFAULT and SUPPORTS_VALIDATED_DEFAULT class variables were removed from DataModel and its subclasses; custom model classes referencing these attributes will need updating. (#3070)
  • Removed WrappedDefault, ValidatedDefault classes and SUPPORTS_WRAPPED_DEFAULT, SUPPORTS_VALIDATED_DEFAULT class variables - The WrappedDefault and ValidatedDefault classes from datamodel_code_generator.model._types (re-exported via datamodel_code_generator.model.base) have been deleted. The DataModel class variables SUPPORTS_WRAPPED_DEFAULT and SUPPORTS_VALIDATED_DEFAULT have also been removed. Code that imports or references these will break. (#3071)
  • New --allow-remote-refs / --no-allow-remote-refs CLI option and allow_remote_refs config field - Remote $ref fetching over HTTP/HTTPS now emits a deprecation warning by default. Pass --allow-remote-refs to suppress the warning, or --no-allow-remote-refs to block remote fetching entirely. In a future version, remote fetching will be disabled by default. Users relying on remote $ref resolution should add --allow-remote-refs to their invocations to avoid the deprecation warning and prepare for the future default change. (#3072)
  • New SchemaFetchError exception for HTTP fetch failures - Remote schema fetching now raises SchemaFetchError (instead of propagating raw httpx exceptions) on HTTP errors, non-2xx status codes, or unexpected HTML responses. Users catching specific httpx exceptions from remote ref resolution will need to catch SchemaFetchError instead. (#3072)

Error Handling Changes

  • Missing local $ref now raises Error instead of FileNotFoundError - Previously, when a $ref pointed to a non-existent local file, a raw FileNotFoundError propagated to callers. Now it raises datamodel_code_generator.Error with the message "$ref file not found: <path>". Programmatic users catching FileNotFoundError specifically will need to catch Error instead (#3051)
  • HTTP fetch failures now raise SchemaFetchError instead of propagating raw exceptions - HTTP errors (4xx/5xx status codes), unexpected HTML responses, and transport errors (DNS, timeout, connection) that previously resulted in downstream YAML/JSON parse errors or raw httpx exceptions now raise SchemaFetchError (a subclass of Error) before parsing is attempted. Users catching specific parse errors or httpx exceptions for these scenarios will need to update their error handling (#3051)
  • HTTP fetch errors now raise SchemaFetchError instead of raw httpx exceptions - The get_body() function in http.py now catches HTTP errors and raises SchemaFetchError (a new Error subclass) for HTTP status >= 400, network failures, and unexpected HTML responses. Code that caught raw httpx exceptions from remote schema fetching will need to catch SchemaFetchError instead. (#3071)
  • Remote $ref fetching now emits FutureWarning without --allow-remote-refs - Fetching remote HTTP/HTTPS $ref references without explicitly passing --allow-remote-refs now emits a FutureWarning deprecation warning. In a future version, remote fetching will be disabled by default. Users relying on implicit remote ref fetching should add --allow-remote-refs to suppress the warning. (#3071)
  • HTTP fetch errors now raise SchemaFetchError with validation of response content type - Previously, fetching a remote $ref that returned an HTML error page would silently pass the HTML through as schema content. Now it raises SchemaFetchError if the response has text/html content type or a 4xx/5xx status code. This may cause previously-silent failures to become loud errors. (#3072)

Default Behavior Changes

  • Implicit remote $ref fetching now emits FutureWarning - When a $ref resolves to an HTTP(S) URL and --allow-remote-refs is not explicitly passed, the tool still fetches the remote reference but emits a FutureWarning. This may cause failures in environments running with -W error (warnings as errors) or strict warning filters. Pass --allow-remote-refs explicitly to suppress the warning (#3051)
  • Remote $ref fetching now emits a FutureWarning - When the parser encounters an HTTP/HTTPS $ref without --allow-remote-refs being explicitly set, a FutureWarning is emitted warning that remote fetching will be disabled by default in a future version. Pass --allow-remote-refs to silence the warning, or --no-allow-remote-refs to block remote fetching immediately. (#3070)

Custom Template Update Required

  • Type alias templates updated with fields guard - All six type alias templates (TypeAliasAnnotation.jinja2, TypeAliasType.jinja2, TypeStatement.jinja2, UnionTypeAliasAnnotation.jinja2, UnionTypeAliasType.jinja2, UnionTypeStatement.jinja2) now wrap the main body in {% if fields %}...{% else %} blocks that fall back to {{ base_class }} when no fields are present. Users with custom copies of these templates must add the same guard or handle the empty-fields case. (#3070)
  • Type alias Jinja2 templates now require fields guard and base_class fallback - The built-in templates TypeAliasAnnotation.jinja2, TypeAliasType.jinja2, `TypeStatement.jinja2...
Read more

0.55.0

10 Mar 20:40
3624533

Choose a tag to compare

Breaking Changes

Dependency Changes

  • Pydantic v1 runtime support removed - Pydantic v2 is now required as a runtime dependency. Users running datamodel-code-generator with Pydantic v1 installed must upgrade to Pydantic v2. The previously deprecated v1 compatibility layer has been completely removed. (#3025)

Default Behavior Changes

  • Default output model switched from Pydantic v1 to v2 - Running datamodel-codegen without specifying --output-model-type now generates Pydantic v2 models (pydantic_v2.BaseModel) instead of Pydantic v1 models (pydantic.BaseModel). Users who depend on the previous default behavior must now explicitly specify --output-model-type pydantic.BaseModel to continue generating Pydantic v1 compatible code. (#3029)

Code Generation Changes

  • Generated model syntax changed for default output - The default generated code now uses Pydantic v2 syntax including RootModel instead of __root__ fields, native union syntax (str | None) instead of Optional[str], and Pydantic v2 validator/serializer decorators. Existing code that consumes generated models may need updates to work with the new Pydantic v2 output format. (#3029)
  • Pydantic v1 output support removed - The pydantic.BaseModel output model type has been completely removed. Generated code now only supports Pydantic v2 patterns including RootModel instead of __root__, model_rebuild() instead of update_forward_refs(), and model_config instead of class Config. Users generating Pydantic v1 models must migrate to v2 output. (#3031)

Custom Template Update Required

  • Pydantic v1 templates removed - The following Jinja2 templates have been deleted and users with custom templates extending them must migrate to v2 equivalents:
    • pydantic/BaseModel.jinja2 → use pydantic_v2/BaseModel.jinja2
    • pydantic/BaseModel_root.jinja2 → use pydantic_v2/RootModel.jinja2
    • pydantic/Config.jinja2 → removed (v2 uses model_config dict)
      (#3031)

API/CLI Changes

  • --output-model-type pydantic.BaseModel removed - The pydantic.BaseModel value for --output-model-type is no longer valid. Use pydantic_v2.BaseModel instead (now the default). (#3031)
  • Pydantic v1 compatibility utilities removed from Python API - The following functions were removed from datamodel_code_generator.util: model_dump(), model_validate(), get_fields_set(), model_copy(). Use Pydantic v2 methods directly (obj.model_dump(), cls.model_validate(), etc.). (#3031)
  • datamodel_code_generator.model.pydantic module removed - The entire Pydantic v1 model module including BaseModel, CustomRootType, DataModelField, DataTypeManager, and dump_resolve_reference_action has been removed. Use datamodel_code_generator.model.pydantic_v2 instead. (#3031)
  • Pydantic v2 now required at runtime - The minimum pydantic dependency changed from pydantic>=1.5 to pydantic>=2,<3. Users with pydantic v1 installed must upgrade to pydantic v2 before using datamodel-code-generator (#3027)
  • Removed internal pydantic compatibility utilities - The following functions/classes were removed from datamodel_code_generator.util: get_pydantic_version(), is_pydantic_v2(), model_validator(), field_validator(), and ConfigDict. Users who imported these internal utilities directly must update their code to use pydantic's native APIs (#3027)
  • Removed datamodel_code_generator.pydantic_patch module - The entire pydantic compatibility patching module was removed. Any code importing from this module will fail (#3027)
  • Removed packaging dependency - The packaging library is no longer a dependency. Code that relied on it being transitively available should add it explicitly (#3027)

What's Changed

New Contributors

Full Changelog: 0.54.1...0.55.0

0.54.1

04 Mar 04:13
690c535

Choose a tag to compare

What's Changed

  • Add dismissible announce bar to docs site by @koxudaxi in #3004
  • docs: update maintainer announcement to reflect open to work status by @koxudaxi in #3007
  • Support isort 8 by @cjwatson in #3011
  • Fix --allow-population-by-field-name for pydantic v2 dataclass output by @butvinm in #3013
  • Support --use-annotated and --use-non-positive-negative-number-constrained-types by @torarvid in #3015
  • Skip default_factory wrapping for non-callable type aliases by @butvinm in #3012
  • Fix incorrect relative imports with --use-exact-imports and --collapse-root-models by @koxudaxi in #3020
  • feat: Add --external-ref-mapping to import from external packages instead of generating by @matssun in #3006
  • Build(deps): Bump cryptography from 46.0.3 to 46.0.5 by @dependabot[bot] in #3016
  • Build(deps): Bump urllib3 from 2.6.2 to 2.6.3 by @dependabot[bot] in #3017

New Contributors

Full Changelog: 0.54.0...0.54.1

0.54.0

14 Feb 16:17
2ea6244

Choose a tag to compare

Breaking Changes

Code Generation Changes

  • Enum member names from oneOf/anyOf const constructs now use title field when provided - Previously, when creating enums from oneOf/anyOf constructs with const values, the title field was incorrectly ignored and enum member names were generated using the pattern {type}_{value} (e.g., integer_200). Now, when a title is specified, it is correctly used as the enum member name (e.g., OK instead of integer_200). Users who have code depending on the previously generated enum member names will need to update their references. (#2975)
    Before:
    class StatusCode(IntEnum):
        integer_200 = 200
        integer_404 = 404
        integer_500 = 500
    After:
    class StatusCode(IntEnum):
        OK = 200
        Not_Found = 404
        Server_Error = 500
  • Field names matching Python builtins are now automatically sanitized - When a field name matches a Python builtin type AND the field's type annotation uses that same builtin (e.g., int: int, list: list[str], dict: dict[str, Any]), the field is now renamed with a trailing underscore (e.g., int_) and an alias is added to preserve the original JSON field name. This prevents Python syntax issues and shadowing of builtin types. Previously, such fields were generated as-is (e.g., int: int | None = None), which could cause code that shadows Python builtins. After this change, the same field becomes int_: int | None = Field(None, alias='int'). This affects fields named: int, float, bool, str, bytes, list, dict, set, frozenset, tuple, and other Python builtins when their type annotation uses the matching builtin type. (#2968)
  • $ref with non-standard metadata fields no longer triggers schema merging - Previously, when a $ref was combined with non-standard fields like markdownDescription, if, then, else, or other extras not in the whitelist, the generator would merge schemas and potentially create duplicate models (e.g., UserWithExtra alongside User). Now, only whitelisted schema-affecting extras (currently just const) trigger merging. This means:
    • Fewer merged/duplicate models will be generated
    • References are preserved directly instead of being expanded
    • Field types may change from inline merged types to direct references
      Example schema:
    properties:
      user:
        $ref: "#/definitions/User"
        nullable: true
        markdownDescription: "A user object"
    Before: Could generate a merged UserWithMarkdownDescription model
    After: Directly uses User | None reference (#2993)
  • Enum member names no longer get underscore suffix with --capitalise-enum-members - Previously, enum values like replace, count, index would generate REPLACE_, COUNT_, INDEX_ when using --capitalise-enum-members. Now they correctly generate REPLACE, COUNT, INDEX. The underscore suffix is only added when --use-subclass-enum is also used AND the lowercase name conflicts with builtin type methods. Users relying on the previous naming (e.g., referencing MyEnum.REPLACE_ in code) will need to update to use the new names without trailing underscores. (#2999)
  • Fields using $ref with inline keywords now include merged metadata - When a schema property uses $ref alongside additional keywords (e.g., const, enum, readOnly, constraints), the generator now correctly merges metadata (description, title, constraints, defaults, readonly/writeOnly) from the referenced schema into the field definition. Previously, this metadata was lost. For example, a field like type: Type may now become type: Type = Field(..., description='Type of this object.', title='type') when the referenced schema includes those attributes. This also affects additionalProperties and OpenAPI parameter schemas. (#2997)

What's Changed

  • Refactor ruff check+format to use sequential subprocess calls by @koxudaxi in #2967
  • Fix title ignored when creating enums from merging allOf's or anyOf's objects by @ilovelinux in #2975
  • Fix aliased imports not applied to base classes and non-matching fields by @koxudaxi in #2981
  • Fix handling of falsy default values for enums in set-default-enum-member option by @kkinugasa in #2977
  • Fix use_union_operator with Python builtin type field names by @koxudaxi in #2968
  • Support $recursiveRef/$dynamicRef in JSON Schema and OpenAPI by @koxudaxi in #2982
  • Address review feedback for recursive/dynamic ref support by @koxudaxi in #2985
  • Fix RecursionError in _merge_ref_with_schema for circular $ref by @koxudaxi in #2983
  • Fix missing Field import with multiple aliases on required fields by @koxudaxi in #2992
  • Fix patternProperties/propertyNames key constraints lost with field_constraints by @koxudaxi in #2994
  • Fix type loss when $ref is used with non-standard metadata fields by @koxudaxi in #2993
  • Fix missing | None for nullable enum literals in TypedDict by @koxudaxi in #2991
  • Fix exact imports with module/class name collision by @koxudaxi in #2998
  • Fix extra underscore on enum members like replace with --capitalise-enum-members by @koxudaxi in #2999
  • Fix merged result in parse_item not passed back to parse_object_fields by @koxudaxi in #2997
  • Fix codespeed python version by @koxudaxi in #3000
  • Fix incorrect relative imports with --use-exact-imports and --collapse-root-models by @koxudaxi in #2996

New Contributors

Full Changelog: 0.53.0...0.54.0

0.53.0

12 Jan 18:12
a6a7b04

Choose a tag to compare

Breaking Changes

Custom Template Update Required

  • Parser subclass signature change - The Parser base class now requires two generic type parameters: Parser[ParserConfigT, SchemaFeaturesT] instead of just Parser[ParserConfigT]. Custom parser subclasses must be updated to include the second type parameter. (#2929)
    # Before
    class MyCustomParser(Parser["MyParserConfig"]):
        ...
    # After
    class MyCustomParser(Parser["MyParserConfig", "JsonSchemaFeatures"]):
        ...
  • New abstract schema_features property required - Custom parser subclasses must now implement the schema_features abstract property that returns a JsonSchemaFeatures (or subclass) instance. (#2929)
    from functools import cached_property
    from datamodel_code_generator.parser.schema_version import JsonSchemaFeatures
    from datamodel_code_generator.enums import JsonSchemaVersion
    class MyCustomParser(Parser["MyParserConfig", "JsonSchemaFeatures"]):
        @cached_property
        def schema_features(self) -> JsonSchemaFeatures:
            return JsonSchemaFeatures.from_version(JsonSchemaVersion.Draft202012)
  • Parser _create_default_config refactored to use class variable - Subclasses that override _create_default_config should now set the _config_class_name class variable instead. The base implementation uses this variable to dynamically instantiate the correct config class. (#2929)
    # Before
    @classmethod
    def _create_default_config(cls, options: MyConfigDict) -> MyParserConfig:
        # custom implementation...
    # After
    _config_class_name: ClassVar[str] = "MyParserConfig"
    # No need to override _create_default_config if using standard config creation
  • Template condition for default values changed - If you use custom Jinja2 templates based on BaseModel_root.jinja2 or RootModel.jinja2, the condition for including default values has changed from field.required to (field.required and not field.has_default). Update your custom templates if you override these files. (#2960)

Code Generation Changes

  • RootModel default values now included in generated code - Previously, default values defined in JSON Schema or OpenAPI specifications for root models were not being applied to the generated Pydantic code. Now these defaults are correctly included. For example, a schema defining a root model with default: 1 will generate __root__: int = 1 (Pydantic v1) or root: int = 1 (Pydantic v2) instead of just __root__: int or root: int. This may affect code that relied on the previous behavior where RootModel fields had no default values. (#2960)
  • Required fields with list defaults now use default_factory - Previously, required fields with list-type defaults (like __root__: list[ID] = ['abc', 'efg']) were generated with direct list assignments. Now they correctly use Field(default_factory=lambda: ...) which follows Python best practices for mutable defaults. This changes the structure of generated code for root models and similar patterns with list defaults. (#2958)
    Before:
    class Family(BaseModel):
        __root__: list[ID] = ['abc', 'efg']
    After:
    class Family(BaseModel):
        __root__: list[ID] = Field(
            default_factory=lambda: [ID.parse_obj(v) for v in ['abc', 'efg']]
        )

What's Changed

  • Separate pytest-benchmark into dedicated benchmark dependency group by @koxudaxi in #2937
  • Support ClassVar for Pydantic v2 by @ubaumann in #2920
  • Add schema version detection and feature flags by @koxudaxi in #2924
  • Fix MRO ordering for multiple inheritance in GraphQL and JSON Schema/OpenAPI by @koxudaxi in #2941
  • Add schema_features property to parsers for version detection by @koxudaxi in #2929
  • Fix $ref handling in request-response mode for readOnly/writeOnly schemas by @koxudaxi in #2942
  • Ensure codecov upload runs even when coverage check fails by @koxudaxi in #2944
  • Add FeatureMetadata to schema feature classes for doc generation by @koxudaxi in #2945
  • Add schema-docs auto-generation with pre-commit and CI by @koxudaxi in #2949
  • Add comprehensive feature metadata to schema version dataclasses by @koxudaxi in #2946
  • fix: move UnionMode import outside TYPE_CHECKING for Pydantic runtime… by @phil65 in #2950
  • Fix IndexError when using --reuse-scope=tree with single file output by @koxudaxi in #2954
  • Add --use-closed-typed-dict option to control PEP 728 TypedDict generation by @koxudaxi in #2956
  • Fix RootModel default value not being applied by @koxudaxi in #2960
  • Fix required list fields ignoring empty default values by @koxudaxi in #2958
  • Add GenerateConfig lazy import from top-level module by @koxudaxi in #2961
  • Fix allOf array property merging to preserve child $ref by @koxudaxi in #2962
  • Fix array RootModel default value handling in parser by @koxudaxi in #2963
  • Fix bug in handling of graphql empty list defaults by @rpmcginty in #2948

New Contributors

Full Changelog: 0.52.2...0.53.0

0.52.2

05 Jan 17:24
794e02b

Choose a tag to compare

What's Changed

  • Add support for multiple base classes in base_class_map and customBasePath by @koxudaxi in #2916
  • Add hash to Pydantic v2 models used in sets by @koxudaxi in #2918
  • fix: Handle class name prefix correctly in GraphQL parser by @siminn-arnorgj in #2926
  • Add TypedDict closed and extra_items support (PEP 728) by @koxudaxi in #2922
  • Fix release-draft workflow to use pull_request_target and increase max_turns to 50 by @koxudaxi in #2930
  • Migrate from pyright to ty type checker by @koxudaxi in #2928
  • Fix URL port handling in get_url_path_parts by @koxudaxi in #2933

Full Changelog: 0.52.1...0.52.2

0.52.1

03 Jan 17:48
361fb5a

Choose a tag to compare

What's Changed

  • Add --validators option for Pydantic v2 field validators by @koxudaxi in #2906
  • Add dynamic model generation support by @koxudaxi in #2901
  • Sync zensical.toml nav with docs directory by @koxudaxi in #2908
  • Add deprecation warning for default output-model-type by @koxudaxi in #2910
  • Add deprecation warning and explicit --output-model-type to docs by @koxudaxi in #2911
  • Add llms.txt generator for LLM-friendly documentation by @koxudaxi in #2912
  • Move coverage fail_under check to combined coverage environment by @koxudaxi in #2909
  • Fix YAML scientific notation parsing as float by @koxudaxi in #2913
  • Add deprecated field support for Pydantic v2 by @koxudaxi in #2915
  • Add deprecation warning for Pydantic v2 without --use-annotated by @koxudaxi in #2914

Full Changelog: 0.52.0...0.52.1

0.52.0

02 Jan 20:07
ccf8794

Choose a tag to compare

Breaking Changes

Code Generation Changes

  • Union fields with titles now wrapped in named models when --use-title-as-name is enabled - Previously, union-typed fields with a title were generated as inline union types (e.g., TypeA | TypeB | TypeC | None). Now they generate a separate wrapper model using the title name, and the field references this wrapper type (e.g., ProcessingStatusUnionTitle | None). This affects code that directly accesses union field values, as they now need to access the .root attribute (Pydantic v2) or .__root__ (Pydantic v1) of the wrapper model. (#2889)
    Before:
    class ProcessingTaskTitle(BaseModel):
        processing_status_union: (
            ProcessingStatusDetail | ExtendedProcessingTask | ProcessingStatusTitle | None
        ) = Field('COMPLETED', title='Processing Status Union Title')
    After:
    class ProcessingStatusUnionTitle(BaseModel):
        __root__: (
            ProcessingStatusDetail | ExtendedProcessingTask | ProcessingStatusTitle
        ) = Field(..., title='Processing Status Union Title')
    class ProcessingTaskTitle(BaseModel):
        processing_status_union: ProcessingStatusUnionTitle | None = Field(
            default_factory=lambda: ProcessingStatusUnionTitle.parse_obj('COMPLETED'),
            title='Processing Status Union Title',
        )
  • Inline types with titles now generate named type aliases when --use-title-as-name is enabled - Arrays, dicts, enums-as-literals, and oneOf/anyOf unions that have a title in the schema now generate named type aliases or RootModel classes instead of being inlined. This improves readability but changes the generated type structure. For TypedDict output, generates type MyArrayName = list[str]. For Pydantic output, generates class MyArrayName(RootModel[list[str]]). (#2889)
  • Default value handling changed for wrapped union fields - Fields that previously had simple default values now use default_factory with a lambda that calls parse_obj() (Pydantic v1) or model_validate() (Pydantic v2) to construct the wrapper model. Code that introspects field defaults will see a factory function instead of a direct value. (#2889)
  • Different output for $ref with nullable: true - When a JSON Schema property has a $ref combined with only nullable: true (and optionally metadata like title/description), the generator now uses the referenced type directly with Optional annotation instead of creating a new merged model. For example, a schema with multiple properties referencing User with nullable: true will now generate user_a: User | None instead of creating separate UserA, UserB model classes. This is a bug fix that reduces redundant model generation, but existing code that depends on the previously generated class names will break. (#2890)
    Before:
    class UserA(BaseModel):
        name: str
    class UserB(BaseModel):
        name: str
    class Model(BaseModel):
        user_a: UserA | None = None
        user_b: UserB | None = None
    After:
    class User(BaseModel):
        name: str
    class Model(BaseModel):
        user_a: User | None = None
        user_b: User | None = None
  • Type alias generation expanded for --use-title-as-name - When using --use-title-as-name, the generator now creates type aliases for additional cases: nested array items with titles, additionalProperties values with titles, oneOf/anyOf branches with titles, patternProperties, propertyNames, and primitive types with titles. Previously these were inlined; now they generate named type aliases. This is a bug fix per #2887, but changes generated output for schemas with titles on nested elements. (#2891)
  • Title no longer inherited in combined schemas - In anyOf/oneOf/allOf schemas, the parent schema's title is now excluded when merging with child schemas. This prevents unintended title inheritance that could affect model naming when --use-title-as-name is enabled. (#2891)
  • allOf with single $ref no longer creates wrapper class - When a schema property uses allOf with only a single $ref and no additional properties, the generator now directly references the target type instead of creating an unnecessary wrapper class. This may affect code that depends on the previously generated wrapper class names or structure. For example, a property defined as allOf: [$ref: '#/components/schemas/ACHClass'] will now generate ach_class: ACHClass | None instead of creating an intermediate wrapper type. (#2902)

What's Changed

  • Add ULID and Email format documentation by @koxudaxi in #2886
  • Add --class-name-prefix, --class-name-suffix, and --class-name-affix-scope options by @koxudaxi in #2885
  • Use class-name-suffix for parser config TypedDicts by @koxudaxi in #2888
  • Create type aliases for inline types with title when use-title-as-name is enabled by @koxudaxi in #2889
  • Fix duplicate model generation for $ref with nullable by @koxudaxi in #2890
  • Create type aliases for nested elements with titles when use-title-as-name is enabled by @koxudaxi in #2891
  • Clarify --aliases help text to explain schema field becomes Pydantic alias by @koxudaxi in #2892
  • Document external library import use case for --type-overrides by @koxudaxi in #2893
  • Add documentation for reducing duplicate field types by @koxudaxi in #2896
  • Add FutureWarning for upcoming ruff default formatters by @koxudaxi in #2895
  • Add --openapi-include-paths option for path-based model filtering by @koxudaxi in #2894
  • Add --graphql-no-typename option to exclude typename field by @koxudaxi in #2899
  • Add --default-values CLI option for overriding field defaults by @koxudaxi in #2897
  • Fix allOf with single ref creating unnecessary wrapper class by @koxudaxi in #2902
  • Fix --reuse-model --collapse-reuse-models to deduplicate identical inline definitions by @koxudaxi in #2903
  • Add --use-serialization-alias option for Pydantic v2 by @koxudaxi in #2905
  • Fix Pydantic v2 discriminated unions in array fields by @koxudaxi in #2907

Full Changelog: 0.51.0...0.52.0

0.51.0

01 Jan 00:01
74cd08b

Choose a tag to compare

Breaking Changes

Code Generation Changes

  • Different output when using --input-model with Set, FrozenSet, Mapping, or Sequence types - When using --input-model to convert Pydantic models or dataclasses, types that were previously converted to list or dict are now preserved as their original Python types. For example, a field typed as Set[str] now generates set[str] instead of list[str], FrozenSet[T] generates frozenset[T], Mapping[K, V] generates Mapping[K, V] instead of dict[K, V], and Sequence[T] generates Sequence[T] instead of list[T]. This may cause type checking differences or runtime behavior changes if your code depended on the previous output types. (#2837)
  • allOf multi-ref with property overrides now preserves inheritance - Schemas using allOf with multiple $ref items where the child schema also defines properties that override parent properties will now generate classes with multiple inheritance (e.g., class Person(Thing, Location)) instead of a flattened single class with all properties merged inline. Previously, child property overrides were incorrectly treated as conflicts, triggering schema merging. Users relying on the flattened output may need to adjust their code. (#2838)
    Before:
    class Person(BaseModel):
        type: str | None = 'playground:Person'
        name: constr(min_length=1) | None = None
        address: constr(min_length=5)
        age: int | None = None
    After:
    class Thing(BaseModel):
        type: str
        name: constr(min_length=1)
    class Location(BaseModel):
        address: constr(min_length=5)
    class Person(Thing, Location):
        type: str | None = 'playground:Person'
        name: constr(min_length=1) | None = None
        age: int | None = None
  • Ruff unsafe fixes now applied automatically - When using the ruff-check formatter, the --unsafe-fixes flag is now passed to ruff, which enables fixes that may change code behavior in potentially incorrect ways. This includes removing unused imports that might have side effects, removing unused variables that could affect debugging, and other transformations ruff considers "unsafe". Users who relied on the previous conservative safe-only fix behavior may see different generated code output. To restore the previous behavior, users can configure ruff via pyproject.toml or ruff.toml to disable specific unsafe rules. (#2847)
  • Type aliases now generate as class inheritance - When using --reuse-model (Pydantic v2 only), models that would previously generate as type aliases (ChildModel = ParentModel) now generate as explicit subclasses (class ChildModel(ParentModel): pass). This change improves type checker compatibility and maintains proper type identity, but may affect code that relied on type alias semantics or compared types directly. (#2853)
    Before:
    ArmLeft = ArmRight
    After:
    class ArmLeft(ArmRight):
        pass
  • Fields with const values in anyOf/oneOf now generate Literal types instead of inferred base types - Previously, a const value like "MODE_2D" in an anyOf/oneOf schema would generate str type. Now it generates Literal["MODE_2D"]. This change affects type hints in generated models and may require updates to code that type-checks against the generated output. For example:
    # Before (v0.x)
    map_view_mode: str = Field("MODE_2D", alias="mapViewMode", const=True)
    apiVersion: str = Field('v1', const=True)
    # After (this PR)
    map_view_mode: Literal["MODE_2D"] = Field("MODE_2D", alias="mapViewMode", const=True)
    apiVersion: Literal['v1'] = Field('v1', const=True)
    This is a bug fix that makes the generated code more type-safe, but downstream code performing type comparisons or using isinstance(field, str) checks may need adjustment. (#2864)

Custom Template Update Required

  • New DataType flags available for custom templates - Three new boolean flags have been added to the DataType class: is_frozen_set, is_mapping, and is_sequence. Custom Jinja2 templates that inspect DataType flags may need to be updated to handle these new type variations if they contain logic that depends on exhaustive type flag checks. (#2837)
  • Pydantic v2 BaseModel.jinja2 template structure changed - If you have a custom template that extends or modifies the default pydantic_v2/BaseModel.jinja2 template, you need to update it. The conditional block that generated type aliases ({% if base_class != "BaseModel" and ... %}{{ class_name }} = {{ base_class }}{% else %}...{% endif %}) has been removed. Templates should now always generate class declarations. (#2853)

Default Behavior Changes

  • --input-model-ref-strategy reuse-foreign behavior changed - Previously, this strategy compared the source type family against the input model's family (e.g., if input was Pydantic, any non-Pydantic type like dataclass was considered "foreign" and reused). Now it compares against the output model's family. This means types that were previously imported/reused may now be regenerated, and vice versa. For example, when converting a Pydantic model containing a dataclass to TypedDict output, the dataclass was previously imported (it was "foreign" to Pydantic input), but now it will be regenerated (it's not the same family as TypedDict output). Enums are always reused regardless of output type. (#2854)

API/CLI Changes

  • Mixing config and keyword arguments now raises ValueError - Previously, generate() allowed passing both a config object and individual keyword arguments, with keyword arguments overriding config values. Now, providing both raises ValueError: "Cannot specify both 'config' and keyword arguments. Use one or the other." Users must choose one approach: either pass a GenerateConfig object or use keyword arguments, but not both. (#2874)
    # Before (worked): keyword args overrode config values
    generate(input_=schema, config=config, output=some_path)
    # After (raises ValueError): must use one or the other
    # Option 1: Use config only (include output in config)
    config = GenerateConfig(output=some_path, ...)
    generate(input_=schema, config=config)
    # Option 2: Use keyword args only
    generate(input_=schema, output=some_path, ...)
  • Parser signature simplified to config + options pattern - Parser.__init__, JsonSchemaParser.__init__, OpenAPIParser.__init__, and GraphQLParser.__init__ now accept either a config: ParserConfig object OR keyword arguments via **options: Unpack[ParserConfigDict], but not both simultaneously. Passing both raises a ValueError. Existing code using only keyword arguments continues to work unchanged. (#2877)
    # Before: Could potentially mix config with kwargs (undefined behavior)
    parser = JsonSchemaParser(source="{}", config=some_config, field_constraints=True)
    # After: Raises ValueError - must use one approach or the other
    parser = JsonSchemaParser(source="{}", config=some_config)  # Use config object
    # OR
    parser = JsonSchemaParser(source="{}", field_constraints=True)  # Use keyword args
  • Subclass compatibility - Code that subclasses Parser, JsonSchemaParser, OpenAPIParser, or GraphQLParser may need updates if they override __init__ and call super().__init__() with explicit parameter lists. The new signature uses **options: Unpack[ParserConfigDict] instead of explicit parameters. (#2877)
  • Config.input_model type changed from str to list[str] - The input_model field in the Config class now stores a list of strings instead of a single string. While backward compatibility is maintained when setting the value (single strings are automatically coerced to lists), code that reads config.input_model will now receive a list[str] instead of str | None. Users who programmatically access this field should update their code to handle the list type. (#2881)
    # Before
    if config.input_model:
        process_model(config.input_model)  # config.input_model was str
    # After
    if config.input_model:
        for model in config.input_model:  # config.input_model is now list[str]
            process_model(model)

What's Changed

  • Add public API signature baselines by @koxudaxi in #2832
  • Add deprecation warning for Pydantic v1 runtime by @koxudaxi in #2833
  • Fix --use-generic-container-types documentation by @koxudaxi in #2835
  • Add extends support for profile inheritance by @koxudaxi in #2834
  • Fix CLI option docstrings and add missing tests by @koxudaxi in #2836
  • Preserve Python types (Set, Mapping, Sequence) in --input-model by @koxudaxi in #2837
  • Replace docstring with option_description in cli_doc marker by @koxudaxi in #2839
  • Fix allOf multi-ref to preserve inheritance with property overrides by @koxudaxi in #2838
  • Fix x-python-type for Optional container types in anyOf schemas by @koxudaxi in #2840
  • Support incompatible Python types in x-python-type extension by @koxudaxi in #2841
  • Fix nested type imports in x-python-type override by @koxudaxi in #2842
  • Fix deep hierarchy type inheritance in allOf property overrides by @koxudaxi in #2843
  • Fix CLI doc option...
Read more