Releases: koxudaxi/datamodel-code-generator
0.54.0
Breaking Changes
Code Generation Changes
- Enum member names from oneOf/anyOf const constructs now use
titlefield when provided - Previously, when creating enums fromoneOf/anyOfconstructs withconstvalues, thetitlefield was incorrectly ignored and enum member names were generated using the pattern{type}_{value}(e.g.,integer_200). Now, when atitleis specified, it is correctly used as the enum member name (e.g.,OKinstead ofinteger_200). Users who have code depending on the previously generated enum member names will need to update their references. (#2975)
Before:After:class StatusCode(IntEnum): integer_200 = 200 integer_404 = 404 integer_500 = 500
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 becomesint_: 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
$refwas combined with non-standard fields likemarkdownDescription,if,then,else, or other extras not in the whitelist, the generator would merge schemas and potentially create duplicate models (e.g.,UserWithExtraalongsideUser). Now, only whitelisted schema-affecting extras (currently justconst) 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:
Before: Could generate a mergedproperties: user: $ref: "#/definitions/User" nullable: true markdownDescription: "A user object"
UserWithMarkdownDescriptionmodel
After: Directly usesUser | Nonereference (#2993) - Enum member names no longer get underscore suffix with
--capitalise-enum-members- Previously, enum values likereplace,count,indexwould generateREPLACE_,COUNT_,INDEX_when using--capitalise-enum-members. Now they correctly generateREPLACE,COUNT,INDEX. The underscore suffix is only added when--use-subclass-enumis also used AND the lowercase name conflicts with builtin type methods. Users relying on the previous naming (e.g., referencingMyEnum.REPLACE_in code) will need to update to use the new names without trailing underscores. (#2999) - Fields using
$refwith inline keywords now include merged metadata - When a schema property uses$refalongside 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 liketype: Typemay now becometype: Type = Field(..., description='Type of this object.', title='type')when the referenced schema includes those attributes. This also affectsadditionalPropertiesand 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 oranyOf'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
- @kkinugasa made their first contribution in #2977
Full Changelog: 0.53.0...0.54.0
0.53.0
Breaking Changes
Custom Template Update Required
- Parser subclass signature change - The
Parserbase class now requires two generic type parameters:Parser[ParserConfigT, SchemaFeaturesT]instead of justParser[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_featuresproperty required - Custom parser subclasses must now implement theschema_featuresabstract property that returns aJsonSchemaFeatures(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_configrefactored to use class variable - Subclasses that override_create_default_configshould now set the_config_class_nameclass 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.jinja2orRootModel.jinja2, the condition for including default values has changed fromfield.requiredto(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: 1will generate__root__: int = 1(Pydantic v1) orroot: int = 1(Pydantic v2) instead of just__root__: intorroot: 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 useField(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:After:class Family(BaseModel): __root__: list[ID] = ['abc', 'efg']
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
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
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
Breaking Changes
Code Generation Changes
- Union fields with titles now wrapped in named models when
--use-title-as-nameis enabled - Previously, union-typed fields with atitlewere 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.rootattribute (Pydantic v2) or.__root__(Pydantic v1) of the wrapper model. (#2889)
Before:After:class ProcessingTaskTitle(BaseModel): processing_status_union: ( ProcessingStatusDetail | ExtendedProcessingTask | ProcessingStatusTitle | None ) = Field('COMPLETED', title='Processing Status Union Title')
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-nameis enabled - Arrays, dicts, enums-as-literals, and oneOf/anyOf unions that have atitlein 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, generatestype MyArrayName = list[str]. For Pydantic output, generatesclass MyArrayName(RootModel[list[str]]). (#2889) - Default value handling changed for wrapped union fields - Fields that previously had simple default values now use
default_factorywith a lambda that callsparse_obj()(Pydantic v1) ormodel_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
$refwithnullable: true- When a JSON Schema property has a$refcombined with onlynullable: true(and optionally metadata liketitle/description), the generator now uses the referenced type directly withOptionalannotation instead of creating a new merged model. For example, a schema with multiple properties referencingUserwithnullable: truewill now generateuser_a: User | Noneinstead of creating separateUserA,UserBmodel 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:After:class UserA(BaseModel): name: str class UserB(BaseModel): name: str class Model(BaseModel): user_a: UserA | None = None user_b: UserB | None = None
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
titleis now excluded when merging with child schemas. This prevents unintended title inheritance that could affect model naming when--use-title-as-nameis enabled. (#2891) allOfwith single$refno longer creates wrapper class - When a schema property usesallOfwith only a single$refand 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 asallOf: [$ref: '#/components/schemas/ACHClass']will now generateach_class: ACHClass | Noneinstead 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
Breaking Changes
Code Generation Changes
- Different output when using
--input-modelwith Set, FrozenSet, Mapping, or Sequence types - When using--input-modelto convert Pydantic models or dataclasses, types that were previously converted tolistordictare now preserved as their original Python types. For example, a field typed asSet[str]now generatesset[str]instead oflist[str],FrozenSet[T]generatesfrozenset[T],Mapping[K, V]generatesMapping[K, V]instead ofdict[K, V], andSequence[T]generatesSequence[T]instead oflist[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
allOfwith multiple$refitems 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:After:class Person(BaseModel): type: str | None = 'playground:Person' name: constr(min_length=1) | None = None address: constr(min_length=5) age: int | None = None
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-checkformatter, the--unsafe-fixesflag 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 viapyproject.tomlorruff.tomlto 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:After:ArmLeft = ArmRight
class ArmLeft(ArmRight): pass
- Fields with
constvalues in anyOf/oneOf now generateLiteraltypes instead of inferred base types - Previously, aconstvalue like"MODE_2D"in an anyOf/oneOf schema would generatestrtype. Now it generatesLiteral["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:This is a bug fix that makes the generated code more type-safe, but downstream code performing type comparisons or using# 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)
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
DataTypeclass:is_frozen_set,is_mapping, andis_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.jinja2template, 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-foreignbehavior 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 aconfigobject and individual keyword arguments, with keyword arguments overriding config values. Now, providing both raisesValueError: "Cannot specify both 'config' and keyword arguments. Use one or the other."Users must choose one approach: either pass aGenerateConfigobject 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__, andGraphQLParser.__init__now accept either aconfig: ParserConfigobject OR keyword arguments via**options: Unpack[ParserConfigDict], but not both simultaneously. Passing both raises aValueError. 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, orGraphQLParsermay need updates if they override__init__and callsuper().__init__()with explicit parameter lists. The new signature uses**options: Unpack[ParserConfigDict]instead of explicit parameters. (#2877) Config.input_modeltype changed fromstrtolist[str]- Theinput_modelfield in theConfigclass 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 readsconfig.input_modelwill now receive alist[str]instead ofstr | 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...
0.50.0
Breaking Changes
Code Generation Changes
- Models with
unevaluatedPropertiesnow generate extra field configuration - JSON Schemas containingunevaluatedProperties: falsewill now generate models withextra='forbid'(Pydantic v2) orextra = Extra.forbid(Pydantic v1), and schemas withunevaluatedProperties: truewill generateextra='allow'. Previously this keyword was ignored. This may cause validation errors for data that was previously accepted. (#2797)
Example - a schema like:Previously generated:{ "title": "Resource", "type": "object", "properties": { "name": { "type": "string" } }, "unevaluatedProperties": false }Now generates:class Resource(BaseModel): name: str | None = None
class Resource(BaseModel): model_config = ConfigDict(extra='forbid') name: str | None = None
Default Behavior Changes
- Default encoding changed from system locale to UTF-8 - The default encoding for reading input files and writing output is now always
utf-8instead of the system's locale-preferred encoding (e.g.,cp1252on Windows). Users who rely on locale-specific encoding must now explicitly use--encodingto specify their desired encoding (#2802)
What's Changed
- Fix missing model_config in query parameter classes by @koxudaxi in #2795
- Escape backslash and triple quotes in docstrings by @koxudaxi in #2796
- Add unevaluatedProperties support by @koxudaxi in #2797
- Expose schema $id and path to template context by @koxudaxi in #2798
- Improve CLI startup time with lazy imports by @koxudaxi in #2799
- Use UTF-8 as default encoding instead of locale-preferred by @koxudaxi in #2802
- Add model-level json_schema_extra support for Pydantic v2 by @koxudaxi in #2803
- Add input_model field support to cli_doc marker by @koxudaxi in #2805
- Add dict input support for generate() function by @koxudaxi in #2806
- Optimize extra_template_data copy in DataModel init by @koxudaxi in #2811
- Add LRU cache for file loading and path existence checks by @koxudaxi in #2810
- Optimize JSON/YAML loading with auto-detection and json.load by @koxudaxi in #2809
- Migrate docs deployment to Cloudflare Pages by @koxudaxi in #2812
- Optimize CI workflow with tox cache and remove dev check by @koxudaxi in #2815
- Fix superfluous None when using $ref with nullable type aliases by @koxudaxi in #2814
- Remove tox cache that breaks Windows CI by @koxudaxi in #2816
- Add --input-model option for Pydantic models and dicts by @koxudaxi in #2804
- Add ReadOnly support for TypedDict with --use-frozen-field by @koxudaxi in #2813
- Exclude perf tests from regular test runs by @koxudaxi in #2817
- Add extreme-scale performance tests with dynamic schema generation by @koxudaxi in #2818
- Add ULID type support by @ahmetveburak in #2820
- Add --enum-field-as-literal-map option and x-enum-field-as-literal schema extension by @koxudaxi in #2821
- Fix propertyNames constraint ignored when using $ref to enum definition by @koxudaxi in #2824
- Reduce CodSpeed benchmark tests for faster CI by @koxudaxi in #2826
- Optimize propertyNames $ref handling by calling get_ref_data_type directly by @koxudaxi in #2825
- Add missing path and ulid type mappings to TypedDict type manager by @koxudaxi in #2828
- Fix --check to use output path's pyproject.toml settings by @koxudaxi in #2831
New Contributors
- @ahmetveburak made their first contribution in #2820
Full Changelog: 0.49.0...0.50.0
0.49.0
Breaking Changes
SchemaParseError for Invalid Schema Data
- Schema validation errors now raise
SchemaParseErrorinstead of PydanticValidationError- When parsing invalid schema data (e.g.,"minimum": "not_a_number"), the library now raisesSchemaParseErrorinstead of passing through Pydantic'sValidationError. Users catchingpydantic.ValidationErrorfor schema validation failures should update to catchSchemaParseError. The original error is preserved in theoriginal_errorattribute. (#2786)
Bug Fixes
CLI Now Correctly Outputs to stdout
- Fixed CLI to actually output to stdout when
--outputis not specified - The--outputargument has always documented(default: stdout)in--help, but previously no output was produced. Now works as documented. (#2787)
Other Notable Changes
generate()function now returnsstr | GeneratedModules | Noneinstead ofNone- Existing code ignoring the return value is unaffected. (#2787)- Error message for multi-module output without directory changed to be more descriptive. (#2787)
What's Changed
- Merge duplicate breaking change headings in release notes by @koxudaxi in #2776
- Optimize O(n²) algorithms and reduce redundant iterations by @koxudaxi in #2778
- Optimize performance with LRU caching and O(n) algorithms by @koxudaxi in #2777
- Optimize Jinja2 environment caching and ruff batch formatting by @koxudaxi in #2779
- Remove YAML parsing cache and deepcopy overhead by @koxudaxi in #2781
- Add performance e2e tests with large schema fixtures by @koxudaxi in #2782
- Convert Import class from Pydantic to dataclass for performance by @koxudaxi in #2783
- Add schema path context to error messages by @koxudaxi in #2786
- Return str or dict when output=None in generate() by @koxudaxi in #2787
- Add --http-timeout CLI option by @koxudaxi in #2788
- Pass schema extensions to templates by @koxudaxi in #2790
- Add propertyNames and x-propertyNames support by @koxudaxi in #2789
- Add support for additional_imports in extra-template-data JSON by @koxudaxi in #2793
- Update zensical to 0.0.15 by @koxudaxi in #2794
- Add --use-field-description-example option by @koxudaxi in #2792
- Add --collapse-root-models-name-strategy option by @koxudaxi in #2791
Full Changelog: 0.48.0...0.49.0
0.48.0
Breaking Changes
Code Generation Changes
-
Custom class name generator now applied consistently during duplicate name resolution - Previously, when using
custom_class_name_generator, the default PascalCase naming was incorrectly applied during duplicate name resolution. Now the custom generator is respected throughout, which may change generated class names. For example, a class name likenested_object_resultwith a custom generatorf"Custom{name}"will now produceCustomNested_object_resultinstead ofCustomNestedObjectResult. Users relying on the previous behavior should update their code to expect the new, correct class names. (#2757) -
YAML 1.1 boolean keywords now preserved as strings in enums - Values like
YES,NO,on,off,y,nthat were previously converted to Python booleans are now preserved as their original string values. This fixes issues where string enum values were incorrectly converted but may change generated output for schemas that relied on the previous behavior. For example, a YAML enum withYESwill now generateYES = 'YES'instead of being converted toTrue. (#2767)
Default Behavior Changes
- YAML boolean parsing restricted to YAML 1.2 semantics - Only
true,false,True,False,TRUE,FALSEare now recognized as boolean values. YAML 1.1 boolean aliases (yes,no,on,off,y,n, etc.) are no longer parsed as booleans and will be treated as strings. Non-lowercase forms (True,False,TRUE,FALSE) now emit a deprecation warning indicating future versions will only support lowercasetrue/false. (#2767)
What's Changed
- Fix Google Analytics config for Zensical by @koxudaxi in #2748
- ci: add release draft workflow with Claude Code Action by @koxudaxi in #2749
- fix: improve release-draft workflow configuration by @koxudaxi in #2750
- fix: quote JSON schema in claude_args to preserve double quotes by @koxudaxi in #2751
- fix: remove redundant --output-format json from claude_args by @koxudaxi in #2752
- fix: increase max-turns from 5 to 10 for structured output by @koxudaxi in #2753
- chore: increase max-turns to 20 for better margin by @koxudaxi in #2754
- Add pydantic_v2.dataclass output type and remove pydantic v1 dataclass by @koxudaxi in #2746
- Fix Pydantic v2 deprecation warnings by @koxudaxi in #2747
- Add --use-tuple-for-fixed-items option by @koxudaxi in #2756
- Fix custom_class_name_generator not applied consistently by @koxudaxi in #2757
- Add --base-class-map option for model-specific base classes by @koxudaxi in #2759
- Support 'timestamp with time zone' format by @koxudaxi in #2762
- Add --type-overrides option to replace schema types with custom Python types by @koxudaxi in #2758
- Add --use-root-model-type-alias option by @koxudaxi in #2763
- Add --class-decorators option for custom model decorators by @koxudaxi in #2760
- Add --naming-strategy and --duplicate-name-suffix CLI options by @koxudaxi in #2761
- Add --generate-prompt option for LLM consultation by @koxudaxi in #2764
- Add pydantic_v2.dataclass to output model types documentation by @koxudaxi in #2765
- Clarify --input-file-type help text and CLI documentation by @koxudaxi in #2768
- Support boolean values in patternProperties for JSON Schema 2020-12 by @koxudaxi in #2766
- Use YAML 1.2-like bool semantics to fix YES/NO/on/off enum issues by @koxudaxi in #2767
- Add InvalidFileFormatError with detailed error messages by @koxudaxi in #2771
- Merge multiple patternProperties with same value type into single regex pattern by @koxudaxi in #2770
- Sync Common Recipes and badges between README and docs by @koxudaxi in #2773
- Fix primary-first naming for multi-file schemas with same-named definitions by @koxudaxi in #2772
- Optimize performance for large schema processing by @koxudaxi in #2774
Full Changelog: 0.47.0...0.48.0
0.47.0
Breaking Changes
Code Generation Changes
-
RootModel defaults use direct instantiation - RootModel fields with default values now generate
ClassName(value)instead ofClassName.model_validate(value). This produces cleaner code but changes the generated output (#2714) -
--strict-nullablenow applies to JSON Schema - The--strict-nullableoption is no longer OpenAPI-only and has been moved to Field customization options. It now also correctly respectsnullableon array items (#2713, #2727)
Custom Template Update Required
-
If you use custom Jinja2 templates that check
field.nullable, you may need to update them. Thenullablefield on JsonSchemaObject now defaults toNoneinstead ofFalse. Templates should checkfield.nullable is Trueinstead of justif field.nullable(#2715)Example change:
{# Before #} {%- if field.nullable %}...{% endif %} {# After #} {%- if field.nullable is true %}...{% endif %}
Error Handling Changes
- Formatting failures emit warning instead of error - When code formatting fails (e.g., due to black errors), the generator now emits an unformatted output with a warning instead of raising an exception. This allows code generation to succeed even when formatting tools encounter issues (#2737)
What's Changed
- Require @cli_doc marker for all CLI options by @koxudaxi in #2712
- fix: respect nullable on array items with --strict-nullable by @koxudaxi in #2713
- fix: wrap RootModel primitive defaults with default_factory by @koxudaxi in #2714
- Add --use-default-factory-for-optional-nested-models option by @koxudaxi in #2711
- Fix nullable field access in custom templates with strict_nullable by @koxudaxi in #2715
- fix: skip non-model types in __change_field_name by @koxudaxi in #2717
- Add requestBodies scope support for OpenAPI by @koxudaxi in #2716
- Fix test data backspace escape by @koxudaxi in #2718
- fix: quote forward references in recursive RootModel generic parameters by @koxudaxi in #2720
- Add force_exec_validation option to catch runtime errors across Python versions by @koxudaxi in #2719
- Add validation for extra_args in test helper functions by @koxudaxi in #2723
- Fix discriminator with allOf without Literal type for Pydantic v2 by @koxudaxi in #2722
- Fix regex_engine config not applied to RootModel generic by @koxudaxi in #2721
- Fix hostname format with field_constraints to use Field(pattern=...) by @koxudaxi in #2724
- Move --strict-nullable from OpenAPI-only to Field customization by @koxudaxi in #2727
- Run CLI doc coverage test in CI without --collect-cli-docs by @koxudaxi in #2728
- Add --use-generic-base-class option for DRY model config by @koxudaxi in #2726
- Refactor parser base post-processing for DRY and type-safe implementation by @koxudaxi in #2730
- Add --collapse-reuse-models option by @koxudaxi in #2731
- Add --field-type-collision-strategy option by @koxudaxi in #2733
- Revert "Add --field-type-collision-strategy option" by @koxudaxi in #2734
- Add --no-treat-dot-as-module option for flat output structure by @koxudaxi in #2732
- Add --field-type-collision-strategy option by @koxudaxi in #2735
- Add --use-standard-primitive-types option by @koxudaxi in #2736
- Emit unformatted output when formatting fails by @koxudaxi in #2737
- Fix aliasing of builtin type field names by @koxudaxi in #2738
- Add path filters to optimize CodeRabbit reviews by @koxudaxi in #2742
- Add --output-date-class option and date-time-local format support by @koxudaxi in #2739
- Fix custom template directory not working for included templates by @koxudaxi in #2740
- Remove unnecessary model_config from RootModel subclasses by @koxudaxi in #2741
- Fix incorrect --type-mappings examples in documentation by @koxudaxi in #2744
- Add Python 3.13 deprecation warning documentation by @koxudaxi in #2743
- Add admonition support to CLI docs and document --use-default nullable behavior by @koxudaxi in #2745
Full Changelog: 0.46.0...0.47.0