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_description errors in tests and build script by @koxudaxi in #2846
- Add --unsafe-fixes to ruff-check formatter by @koxudaxi in #2847
- Add support for multiple aliases using Pydantic v2 AliasChoices by @koxudaxi in #2845
- Handle types.UnionType in _serialize_python_type for Python 3.10-3.13 by @koxudaxi in #2848
- Fix set/frozenset duplicate output in x-python-type serialization by @koxudaxi in #2849
- Add --input-model-ref-strategy option for controlling type reuse by @koxudaxi in #2850
- Fix DataType deepcopy infinite recursion with circular references by @koxudaxi in #2852
- Add automatic handling of unserializable types in --input-model by @koxudaxi in #2851
- Fix reuse-foreign to compare with output type instead of input type by @koxudaxi in #2854
- Fix reuse-model generating type aliases instead of class inheritance by @koxudaxi in #2853
- Add AST-based type string parsing helpers by @koxudaxi in #2856
- Fix x-python-type qualified name imports using AST helper by @koxudaxi in #2857
- Fix generic type import with module path by @koxudaxi in #2858
- Use qualname for nested class support and add DefaultPutDict test by @koxudaxi in #2859
- fix: handle type definitions from grand(grand...) parent schemas by @simontaurus in #2861
- Add defaultdict and Any to PYTHON_TYPE_IMPORTS by @koxudaxi in #2860
- Add defaultdict to preserved type origins for TypedDict generation by @koxudaxi in #2866
- Handle Annotated types in _serialize_python_type for TypedDict generation by @koxudaxi in #2867
- Remove WithJsonSchema from ExtraTemplateDataType by @koxudaxi in #2868
- Fix const in anyOf/oneOf to generate Literal type by @koxudaxi in #2864
- Optimize deepcopy for empty lists by @koxudaxi in #2862
- Fix pre-commit hooks and pytest for Windows environments by @koxudaxi in #2871
- Fix _normalize_union_str to handle nested generic types by @koxudaxi in #2875
- Fix _normalize_union_str to recursively normalize nested unions by @koxudaxi in #2876
- feat: add --allof-class-hierarchy option by @simontaurus in #2869
- Simplify generate() function signature using Unpack[GenerateConfigDict] by @koxudaxi in #2874
- Simplify Parser.init signature using Unpack[ParserConfigDict] by @koxudaxi in #2877
- Refactor generate() and Parser to use config directly by @koxudaxi in #2878
- Update using_as_module.md to document config parameter by @koxudaxi in #2879
- fix: Always merge multiple GraphQL schemas before parsing by @siminn-arnorgj in #2873
- Refactor: Use model_validate/parse_obj for Parser config initialization by @koxudaxi in #2880
- Add multiple --input-model support with inheritance preservation by @koxudaxi in #2881
- Exclude OpenAPI/JSON Schema extension fields (x-*) by @ahmetveburak in #2801
- Add pre-commit hook setup instructions to contributing guide by @koxudaxi in #2882
- Consolidate ParserConfig TypedDict profiles with inheritance preservation by @koxudaxi in #2883
- Add release notification workflow by @koxudaxi in #2884
New Contributors
- @simontaurus made their first contribution in #2861
Full Changelog: 0.50.0...0.51.0