Skip to content

Releases: datamodel-code-generator/datamodel-code-generator

0.86.0

Choose a tag to compare

@github-actions github-actions released this 18 Oct 15:31
361e17d

Breaking Changes

Code Generation Changes

  • Base64 bytes with length constraints now use EncodedBytes - With --type-mappings string+byte=binary, a format: byte string with minLength or maxLength used to generate conbytes(...), which did not decode base64. With --field-constraints it used to generate Base64Bytes with a Field(max_length=...). It now generates Annotated[bytes, Field(max_length=8), EncodedBytes(encoder=Base64Encoder)], which decodes the base64 text and checks the lengths on the encoded text, as JSON Schema counts them. Field values are now the decoded bytes instead of the raw text. Under --field-constraints, inputs whose base64 text is longer than the limit are now rejected, even when the decoded bytes fit. Generated modules now import Annotated, Field, EncodedBytes and Base64Encoder. Pydantic checks these lengths on the text only from 2.10 on, and an explicit --target-pydantic-version 2 now warns about each such field (#4798)
  • Strict bytes no longer skip base64 decoding for mapped byte strings - With --type-mappings string+byte=binary and --strict-types bytes, format: byte fields used to generate StrictBytes, or conbytes(..., strict=True) when they had lengths, and held the raw text. They now generate Base64Bytes, or the EncodedBytes form when they have lengths, and decode the base64 text, so validated values change. FastAPI path, query, header and cookie parameters with this combination no longer stop generation with an error (#4798)
  • Mapped byte formats now decode base64 when mapped without constraints - With --type-mappings string+byte=binary, code paths that map a type and format without constraints now pass base64 decoding through. For example, a format: byte string member of a multi-type type list now generates Base64Bytes, as in Base64Bytes | list[int], instead of plain bytes. Its runtime value becomes the decoded bytes (#4798)

Other Output Changes

These change generated output compared with 0.85.0, but only where 0.85.0 output failed or for options that are new in this release.

  • Integer and subclass enums that list null no longer get a None member - An enum with a value type base (the IntEnum written for type: integer, or a --use-subclass-enum class such as (int, Enum)) that lists null used to get a member such as integer_None = None, which made the generated module raise TypeError at import. That member is no longer generated, and an enum whose only value is null becomes a RootModel[None]. Class names and field defaults are unchanged (#4884, #4889)
  • Discriminator tags keep no pattern their values already satisfy - Pydantic discriminator tag fields written with Field() constraints no longer carry a pattern that every tag value satisfies. Such output failed at import in 0.85.0 because the tag was no longer a usable discriminator. Tags whose values contradict their own pattern are unchanged (#4888)
  • Optional one-value Literal discriminator tags default to their value - A tag such as kind: Literal['card'] | None = None is now written as kind: Literal['card'] = 'card'. The discriminated union failed at import before; a standalone model using such a tag now defaults to and dumps 'card' instead of None (#4895)
  • The builtin formatter wraps long Annotated[...] = None assignments like black - Where 0.85.0 put the None default in parentheses on its own lines, the line is now split inside the annotation, as black does. The code is equivalent, but --check runs with a dated preset (which uses the builtin formatter) report the reformatting (#4794)
  • Refinements of options new in this release - --use-tag-as-union-variant-name no longer repeats the key in prefixed names of models inside key-named branches (#4903), and --use-single-value-enum-as-const types nullable single-value unions as Literal[...] | None keeps mixed-type single-value branches apart instead of merging them into one Enum (#4926, #4927), and leaves nullable: true single-value enums as enums so they still accept null (#4944). Both options were added after 0.85.0, so output without them is unchanged

What's Changed

Read more

0.85.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 09:24
723f9e3

Breaking Changes

Error Handling Changes

  • Output encoding failures now raise Error instead of raw codec exceptions - Generated output, model metadata from --emit-model-metadata, and API target package files are now encoded before anything is written. If the chosen --encoding / encoding cannot encode the text, generate() raises datamodel_code_generator.Error with the message Unable to encode output using encoding '...': ... instead of letting UnicodeEncodeError escape. The CLI now prints this as a single error line and exits with code 2 instead of showing a traceback. Python API callers that caught UnicodeEncodeError or UnicodeError need to catch Error instead. Also, when one module of a modular output fails to encode, no files are written; before, earlier modules could be written before the failure (#4615)
  • Unknown or non-text encodings are rejected up front with Error - generate() now checks the encoding option during config preparation, before any input is read or any output path is created. An unknown encoding such as nosuch, or a codec that is not a text encoding such as hex, now raises datamodel_code_generator.Error (CLI exit code 2, one-line message) instead of a raw LookupError raised later. Code that caught LookupError for a bad encoding must catch Error instead (#4615)
  • Explicit names are now checked as identifiers of the target Python version - This applies to --class-name, --class-name-prefix, --class-name-suffix, --special-field-name-prefix, --model-name-map values and explicit field aliases. It also applies to client and FastAPI generation settings such as package, model_package, resource namespaces, operation, parameter and router names, and protocol helper names. These are now checked against the identifier rules of --target-python-version, not the running interpreter, so a name that uses characters only a newer Python accepts is now rejected (for example with E_CONFIG_VALUE or an invalid identifier error) when targeting an older Python, even if the running interpreter accepts it (#4790)
  • Generation now fails when an override or schema import names a standard-library type the target lacks - A standard-library type named by --type-overrides, customTypePath or x-python-import (including its qualname and native-field paths) that does not exist in the target Python version now stops generation with TYPE is unavailable for target Python X.Y. Examples are enum.StrEnum for 3.10, pathlib.types.PathInfo for 3.13, builtins.frozendict for 3.14 and pathlib.DirEntryInfo for 3.15. Before this change the import was written as given and generation succeeded. The check runs only when the final output imports the type. To fix this, raise --target-python-version or use --import-overrides to point the import at a backport module, for example --import-overrides '{"StrEnum": "backports.strenum"}'. (#4788)

Code Generation Changes

  • Non-ASCII identifiers now follow the target Python version's Unicode rules - Field names, class names, enum member names, module names, alias generator output (to_camel, to_pascal, to_snake), OpenAPI operation model names and the builtin formatter's import ordering are now worked out from Unicode tables for --target-python-version. Those tables cover identifier characters, \w, numeric characters, case mappings and NFKC normalization. Before, these names came from the str, re and unicodedata behaviour of the interpreter running datamodel-codegen. ASCII names are unaffected, but output for non-ASCII schema names can change when the running interpreter's Unicode version differs from the target's. For example, a property 京都 (U+4EAC is numeric from Python 3.13) used to become field_京都 when generated on Python 3.13 or later. It is now emitted as 京都 for targets up to 3.12, and the reverse happens when generating on an older Python for a 3.13 or later target. Likewise, --capitalise-enum-members no longer turns ɤ into Ɤ for targets older than 3.14, even when running on Python 3.14. Regenerate and review models whose names contain non-ASCII characters, because renamed fields and members break code that refers to them (#4790)
  • Newer typing names from type overrides and schema import paths now come from typing_extensions - Standard-library typing or collections.abc names given through --type-overrides, customTypePath or x-python-import are now resolved against --target-python-version. If the name is newer than the target, the import now comes from typing_extensions. For example, typing.LiteralString and collections.abc.Buffer with target 3.10 now produce from typing_extensions import Buffer, LiteralString instead of importing them from typing and collections.abc as before. Projects that compare generated output, or that do not have typing_extensions installed, will see different imports. (#4788)
  • Builtin formatter groups standard library imports by target Python version - With --formatters builtin, whether an import goes in the standard library section or the third party section now depends on the standard library modules of --target-python-version. When no target is given, the minimum supported target, 3.10, is used. Before this change, the formatter used sys.stdlib_module_names of the running interpreter. When the running Python differs from the target, imports in generated files can move to a different section. For example, tomllib is grouped as third party when targeting 3.10, distutils is grouped as standard library when targeting 3.10 even on a newer interpreter, and sre_parse becomes third party while profiling becomes standard library when targeting 3.15 (#4791)
  • Builtin formatter places each module of a multi-module import in its own section - With --formatters builtin, an import a, b, c statement that names modules from different categories (standard library, third party, first party) now puts each module in its own section, as isort does. Before, all modules from that statement went into the section of the highest-priority module among them, so the order of generated imports can change (#4791)
  • Reuse model now deduplicates models whose referenced names start with non-ASCII letters - With --reuse-model, model names that start with characters beyond ASCII (for example Ñumber or Ölen) are now masked as whole identifiers when models are compared. Models that are identical except for such references are now reused as subclasses (for example class Distance(Speed): pass) instead of being generated as separate duplicate classes. This matches how ASCII names already worked. As a result, some previously generated duplicate classes and their numbered names may no longer appear in the output (#4791)

What's Changed

New Contributors

Full Changelog: 0.84.0...0.85.0

0.84.0

Choose a tag to compare

@github-actions github-actions released this 15 Oct 16:00
213ff2c

Highlights

Experimental: FastAPI server and Python client generation

Besides the models, datamodel-code-generator can now generate a FastAPI server package and a typed HTTPX2 client from one OpenAPI document. Both are new in 0.84.0 and experimental: options, templates and the generated packages may change in later releases (they are listed in the experimental registry with since_version 0.84.0). The generated code uses your generated models as its runtime contract: they validate requests and responses, and the generated server reads its packaged copy of the OpenAPI document only to serve it.

Get started:

# FastAPI server package in app/server/ (mkdir app && touch app/__init__.py first)
datamodel-codegen \
  --input openapi.yaml \
  --input-file-type openapi \
  --openapi-scopes schemas api \
  --output-model-type pydantic_v2.BaseModel \
  --preset standard-py312-20260909 \
  --output app/models.py \
  --generate-server fastapi \
  --server-output app/server \
  --server-package app.server \
  --server-model-package app.models

# HTTPX2 client package in app/client/
datamodel-codegen \
  --input openapi.yaml \
  --input-file-type openapi \
  --openapi-scopes schemas api \
  --output-model-type pydantic_v2.BaseModel \
  --preset standard-py312-20260909 \
  --output app/models.py \
  --generate-client httpx2 \
  --client-output app/client \
  --client-package app.client \
  --client-model-package app.models
  • --generate-server / --generate-client require --server-output / --client-output, --server-package / --client-package (the import path of the package) and --server-model-package / --client-model-package (the import path of the models). --output names the model file (models.py above), not a package. The other --server-* / --client-* options are in the CLI reference.
  • A client needs a base URL: an operation without servers in the document needs --client-default-base-url, otherwise generation stops with an error
  • --generate-server and --generate-client require an explicit --input-file-type openapi: an input type left to detection, auto, or any other type stops with Error: --generate-server requires --input-file-type openapi, not auto and exit code 2 before the input is read (#4639)
  • Every server and client setting is a CLI option, a [tool.datamodel-codegen] key, and a generate() option; both targets reuse the model output options (formatters, file header, line length, and so on) (#4401, #4406, #4430)
  • --check, --diff-against, --watch and --output-format json work as for models, and so do batch jobs (#4438, #4442, #4443, #4517)
  • Target files are written like model files: edited files are overwritten, nothing is ever deleted, and --check lists leftover files as EXTRA (#4167, #4456, #4461). A generation that leaves .py files it no longer generates in the package, such as the router module of a removed tag, names them in one LeftoverFilesWarning (#4626). The required runtime dependencies are printed as a uv add ... command instead of being written to a requirements file (#4166); datamodel-code-generator does not edit pyproject.toml. --output-format json reports the package and the same dependencies as target.package and target.dependencies (#4680). The line asks for pydantic>=2.14.0, as does the client README (#4601, #4651). An explicit --target-pydantic-version older than that minimum (2, 2.11 or 2.12) is refused with Error: and exit code 2 for a server, and for a client whose models use pydantic; leave it unset or use 2.14 (#4627). A warning is shown when an existing package directory shadows a generated module, for server and client packages and also for plain model output (#4470).
  • Python API: pass generate_server="fastapi" or generate_client="httpx2" (plus server_* / client_* options) to generate(); with output=None it returns GeneratedModules with every model and package file (#4507)
  • Errors and warnings from the targets are regular dcg errors and Python warnings (#4421, #4434, #4516)
  • Groups of untagged operations: operations without a tag form the group default on both targets: routers/default.py, DefaultService, create_app(default=...) and the --server-router-names key default on the server, client.default on the client. A tag that itself derives default is numbered apart in declaration order (default, default_1) the same way on both (#4608).
  • Form bodies follow one rule on both targets: a multipart member, declared, inherited through allOf, or typed by additionalProperties, whose type has no single scalar kind (an object, a mapping, an array of objects, a mixed enum, or a union such as Filter | string or string | string[]) travels as application/json parts, one per item only for an array, and the server validates it with the member's model type. A member with a scalar kind travels as text, one value per array item. An explicit encoding.<name>.contentType naming one JSON media type is honoured by both targets (#4602, #4611). A member without a type ({}), each item of an array whose items have none, and other properties that the schema allows without a type travel as application/json parts too, a string included; the server keeps such a part that is not JSON as it arrives, text for a field and bytes for a file (#4728). The server parses a JSON part with the member's model field, so the field's constraints and discriminator apply; a model that populates by name also takes a part or field named like the Python field (max_age beside maxAge); typed other properties are checked by their type also when --reuse-model makes the form's model inherit an equal one; a form body whose schema is one schema or null is read and written as the form of that schema, and a multipart body that is a union of several schemas stops generation on both targets (needs an object schema to be sent as parts) (#4727). A client multipart body left without parts is sent as its closing boundary with a multipart/form-data Content-Type, which a server reads as an empty form (#4727).
  • Form encodings follow the OpenAPI version: an encoding's style, explode and allowReserved apply to URL-encoded bodies, and to multipart/form-data only in OpenAPI 3.1 and 3.2, whose parts carry the style's text without percent-encoding (the client refuses a delimited value that holds its delimiter); OpenAPI 3.0 ignores them for multipart, so the member is written and read as its contentType says. The server now reads styled members (form, deepObject, spaceDelimited, pipeDelimited), a URL-encoded member's contentType, and multipart members whose encoding names one UTF-8 or US-ASCII text media type or any media type for files, as the client writes them; a form whose exploded objects that allow other properties would take the same names stops generation on both targets, as such query parameters do (#4733), and so does such an exploded object next to other properties the form keeps, whether the schema declares them (additionalProperties true or a schema) or the model configuration keeps them (--extra-fields allow) (#4737).
  • Binary strings: a format: binary string is raw bytes that only a whole body (application/octet-stream, image/png, text/csv) or a multipart file part can carry; a text/* body whose model type is one binary string travels as raw bytes, also when the schema reaches it through $ref or a single allOf / anyOf / oneOf branch, beside null, a length limit or other binary strings (#4669), and the client method takes it as a binary body (bytes, a file object or a path). A binary string that would be sent as text (inside a JSON or URL-encoded body or response, a text/* body that is an object or array, a multipart part that is not a file, an item of a streamed response, a parameter or a header) stops generation on both targets with an error that names the operation and the schema location. Declare such a value format: byte for base64 text, optionally with --type-mappings string+byte=binary to hold the decoded bytes (#4658). The model type decides, so allOf: [{type: string}, {format: binary}] is a binary body too; bytes the models constrain (maxLength: 16 as conbytes(max_length=16)) are validated by the model: the server answers 422 to a request body and does not send a response body the model refuses, and the client raises DecodeError (unencodable for a bytes request body, invalid_value for a response) while files, paths and iterables stream unchecked. An enum or const of binary strings is text in the models and travels as text, and a binary body whose model types it as bytes only in part (type: [string, integer], bytes | int) stops generation (#4729)
  • Both targets stop on what no HTTP peer can carry (#4730): a header parameter, response header or part header whose name is not an RFC 9110 token (X Sp, X-Näme); a content key that is no media type (multipart/form data) or whose text is not ASCII header text (text/plain; label="café"); a URL-encoded body with patternProperties or that is not an object (the server reads such a body only with --server-body-mode request); a --server-router-names group key that matches no group (it was ignored) and a --client-resource-names tag that is the first tag of no operation. The server routes literal path text by its decoded value (/files/a%20b registers /files/a b, which the client's request /files/a%20b matches); literal %2F, %7B or %7D, and two operations of one method whose paths decode alike, stop generation. Client resources take their place among the derived names from their first operation, as server groups do.
  • Settings a target needs are set for you when you leave them unset: --openapi-scopes gets api added to the default scopes, `--target-python-vers...
Read more

0.83.0

Choose a tag to compare

@github-actions github-actions released this 24 Sep 08:55
f59ac9f

Breaking Changes

Code Generation Changes

  • Deduplication now compares referenced models - Models that render identically are only merged when the models they reference also match structurally, so schemas whose same-named references resolve to different definitions in other files no longer collapse into a single class; such models are now emitted separately and may gain suffixed names such as LeafModel or Values1, changing class names and module contents compared with previous output (#4115)
  • Generic schemas using $dynamicRef are now specialized per dynamic scope - A plain-name $dynamicRef previously resolved lexically within its own schema resource, so every referrer shared one model of the referenced generic. The parser now tracks the dynamic scope of the schemas that referenced a model, binds each $dynamicAnchor name to its outermost declaration in that scope, and re-parses a referenced schema as an additional specialized model whenever its bindings differ. Schemas that combine $dynamicRef with $dynamicAnchor, including remote, embedded, recursive, root-level and tuple additionalItems generics, now emit extra model classes with suffixed names and different field types than before, so generated module contents, class names and import sites can change for existing users (#4117)
  • Specialized dynamic-scope models shift generated class names - Specialized models are registered under the referencing model and named like the shared model before parsing, so the shared model keeps naming priority and specializations receive derived names such as numeric or Model suffixes. Output ordering and names of surrounding models can therefore differ from previous releases, and this applies under --naming-strategy full-path and --collapse-root-models as well (#4117)
  • OpenAPI specifications referencing external JSON Schema generics gain specialized models - parse_raw now drains pending dynamic-scope specializations after every specification has been read, so components that reference external schemas rebinding a $dynamicAnchor produce additional specialized models instead of reusing a single shared model (#4117)
  • Dynamic ref resolution scope for inline resources and merged refs - $dynamicRef is now resolved within the schema resource a subschema stands for, namely an inline subschema declaring its own $id and the target of a $ref merged with sibling keywords, with enclosing and referencing resources still able to override the binding so the outermost $dynamicAnchor declaration wins; schemas combining $dynamicRef with embedded $id resources or with $ref plus sibling keywords may now generate different field types and model shapes than before, for example a resource-local anchor type such as RootModel[str] where the previous output used the anchor visible in the outer scope (#4119)
  • Model reuse no longer merges models whose same-named references point at different models - Deduplication for --reuse-model, --collapse-reuse-models and --reuse-scope=tree previously matched models on rendered output and imports alone, so models that rendered identically but referenced different models sharing a name were incorrectly collapsed into one. Duplicate detection within a module and across modules now also requires the referenced models to match, so these models stay distinct. Affected schemas now generate additional or differently named classes, such as a suffixed FieldModel alongside Field, extra per-module classes, and inheritance from a shared base instead of a single merged model, which can change class names and module layout that downstream code imports (#4121)
  • Merged $ref targets now resolve through aliases - A referenced schema that is itself a $ref is merged with its own target, so a $ref with sibling keywords pointing at an alias now generates the aliased definition instead of the alias node, changing model names and field types in generated output for JSON Schema 2020-12 documents that use $ref alongside other keywords (#4122)
  • References inside merged targets from other documents are rebased - When a $ref with sibling keywords points into another document, nested $ref and plain-name $dynamicRef values are rewritten to absolute locations before merging, so schemas whose relative references previously resolved against the wrong base now produce different models, imports, and field types (#4122)
  • Resolve $recursiveRef in merged cross-document $ref targets - A self-pointing $recursiveRef inside a schema merged in from another document now resolves to the nearest enclosing $recursiveAnchor of its defining document, or that document's root, instead of resolving against the merging document; schemas that rely on the previous resolution generate different recursive model references and field annotations, so regenerated models may name different classes than before, and when the merging document itself declares $recursiveAnchor the reference is instead left to extend the dynamic scope (#4127)
  • OpenAPI component schemas that declare an $id are now registered as schema resources - References naming an $id declared inside a component schema now resolve to that embedded schema when the document itself has no matching target, instead of only being treated as document-relative or external document references, and the OpenAPI parser now parses the normalized specification returned by _prepare_schema_resources rather than the original raw specification, so specifications that use $id inside component schemas can produce different models, model names, and field types than before (#4128)
  • Merged schema copies now collapse into shared models - Object models parsed from a schema merged in from another document are now recognized as duplicates of an identically named model when their rendered shapes match, even if their dedup keys differ because nested referenced names only converge later in the same pass; schemas that previously produced extra suffixed classes such as a second Tree, Node, or Branch now reuse the single shared model, so generated class names, class counts, and field annotations can change and code importing the previously emitted duplicate class names will need updating (#4133)
  • Recursive references inside embedded $id resources resolve to the resource root - A $recursiveRef located in a subschema that declares its own $id but no $recursiveAnchor now targets the root of that embedded resource instead of the nearest enclosing $recursiveAnchor or the document root, both when the document is parsed directly and when its schemas are merged into another document, so affected fields are annotated with the embedded resource model instead of the outer model, and targets reachable only through $recursiveRef are now loaded and can add generated models (#4130)
  • Root models of documents referenced only by JSON pointer are named after the document - When an external document is reached only through pointer references into it and its own subschemas refer back to the document root, that root model was previously generated as Field or FieldModel; it is now named after the file stem, for example Tree or Node, so class names and field annotations such as list[Field] change for such schemas (#4132)

Error Handling Changes

  • New warning for allOf base classes that cannot follow dynamic scope bindings - When an allOf base class is referenced from a schema whose dynamic scope would rebind its $dynamicAnchor, the parser now emits a UserWarning stating that the base class resolves its $dynamicRef within its own schema resources. Generation still succeeds, but builds that treat warnings as errors or assert on captured warnings can now fail (#4117)
  • New SchemaResourceRefWarning raised for compatibility-resolved references - A reference that keeps resolving against the document, or keeps loading the referenced document, for compatibility while JSON Schema would resolve it to an embedded $id resource now emits the newly added SchemaResourceRefWarning, so generation runs that previously completed silently can now surface warnings and can fail where warnings are configured as errors (#4128)

Default Behavior Changes

  • External reference mapping keys are matched against the defining document - Because merged target references are rebased to absolute locations, --external-ref-mapping entries must now name the path of the document the reference actually comes from rather than a path relative to the merged target, so existing mappings that relied on the previous matching may silently stop applying (#4122)

What's Changed

Read more

0.82.0

Choose a tag to compare

@github-actions github-actions released this 16 Sep 08:52
77f28e5

Breaking Changes

Code Generation Changes

  • Constraints are now emitted on recursive container fields - Previously any field that was a self reference had all of its constraints suppressed. The new _can_apply_constraints property in the base field model only suppresses constraints for direct self references, so a self referencing field whose type is a container such as list, Sequence, dict, Mapping, set, frozenset, or tuple now keeps its size constraints. Pydantic output gains Field(min_length=..., max_length=...) and msgspec output gains Meta(min_length=..., max_length=...) where the generated code previously had a bare default, for example friends: list[Pet] | None = None becomes friends: list[Pet] | None = Field(None, min_length=1) (#4071)
  • Stricter runtime validation of recursive container payloads - Because the recovered size constraints are now present in the generated models, input data that violates minItems or maxItems on a recursive array is rejected at validation time instead of being accepted. Existing code that relied on the previously unconstrained generated models may start raising pydantic ValidationError or msgspec.ValidationError for payloads that used to pass (#4071)

What's Changed

New Contributors

Full Changelog: 0.81.0...0.82.0

0.81.0

Choose a tag to compare

@github-actions github-actions released this 14 Sep 00:01
be170f7

Breaking Changes

Code Generation Changes

  • Undeclared required keys now retained in variant runtime validation - When combining --read-only-write-only-model-type request/response variants with --schema-validator-type runtime validators, generated models now keep runtime validation rules for required keys that are not declared as properties (for example keys required via additionalProperties or if/then/else), whereas these rules were previously dropped from the variant models. Regenerated Request and Response models therefore enforce stricter validation and may reject payloads that the previously generated validators accepted (#4055)

What's Changed

Full Changelog: 0.80.0...0.81.0

0.80.0

Choose a tag to compare

@github-actions github-actions released this 12 Sep 23:36
708810e

Breaking Changes

Code Generation Changes

  • Python union annotations preserve unsupported member types - When generating from Python input models, unions containing members such as Callable retain each member type and the original union order instead of repeating the first non-null type. Regenerated annotations change for the affected unions and nested containers. (#3947)
  • Optional nested factories require a callable empty constructor - With --use-default-factory-for-optional-nested-models, an optional child with required constructor fields, including inherited fields, keeps its normal None or UNSET default instead of receiving a factory that fails when called without arguments. (#3953)
  • Safe frozen set items use native Pydantic hash generation - Safely hashable frozen Pydantic v2 models used as set or frozenset items rely on the native hash in place of the explicit identity-hash assignment. Mutable models and models outside the supported safe cases retain the previous generated hash behavior. (#4025)
  • GraphQL msgspec typename fields retain separate inherited slots - For affected GraphQL inheritance collisions, generated models keep inherited synthetic typename slots separate from colliding child user fields, which receive distinct Python names while retaining their wire aliases. Python attribute names in these affected hierarchies can differ from earlier output. (#4045)
  • Constrained root aliases retain their validation - With --use-root-model-type-alias, constraints are retained in the alias where supported; affected roots otherwise use a RootModel class instead of an unconstrained alias. Unconstrained aliases retain their prior form, and an existing custom root-alias template keeps its selection and remains responsible for its own constraints. (#3962)
  • Eligible compound property names use string key types - Eligible string alternatives in propertyNames generate inline constrained string key types instead of nested-model keys. This changes ordinary key annotations for affected schemas; custom schema runtime validators remain conditional on --schema-validator-type pydantic-v2. (#3970)
  • Opt-in schema validators enforce undeclared required names - With --schema-validator-type pydantic-v2, required property names absent from generated fields are checked against the raw object input, including applicable pattern intersections. Payloads missing those required names are rejected where the previous validators accepted them; this fix does not add a validator when the option is disabled. (#3988)
  • Mapped allOf roots retain compatible constraints - For affected allOf roots with an outer format or type mapping, compatible numeric or string constraints are preserved while constraints incompatible with the mapped runtime type are omitted. Regenerated annotations change for these roots and avoid applying incompatible constraints to date, UUID, or other mapped values. (#4043)
  • Eligible property-name alternatives use constrained string keys - For supported propertyNames alternatives combining ordinary string constraints with non-string-only branches, generated dictionary keys use the applicable constrained string type instead of retaining the non-string alternatives. References, complex patterns, and unsupported cases retain the existing fallback. (#4046)
  • Mapped Avro logical defaults use their physical values - When --type-mappings or --type-overrides remap Avro logical types to physical types such as int or bytes, affected defaults use the physical Avro value instead of a logical constructor such as a datetime, decimal, or duration expression. (#4031)
  • Combined XSD patterns require Pydantic 2.8 at runtime - Pydantic v2 output for distinct sibling pattern facets or combined inherited and derived facets uses compiled Python regular expressions in place of string patterns. Affected generated models require Pydantic 2.8 or later at runtime; the generator dependency minimum and ordinary single-pattern output are unchanged, and this fix adds no custom runtime validator. (#4048)

Default Behavior Changes

  • Safe frozen set items use value-based hashing - Affected frozen Pydantic v2 set items use native value-based hashing, so equal values can deduplicate where identity-based hashing previously kept them distinct. Mutable models and unsupported field combinations retain the prior behavior; nested models are not required to become frozen. (#4025)

Error Handling Changes

  • XML Schema collision generation is restored - Inputs with unused type-name collisions or collisions between XML field names can generate again instead of triggering the additional blanket collision errors. Existing limitations in representing colliding XML fields remain; this restores earlier handling and does not add complete collision support. (#4034)
  • Warning-only Pydantic aliases are accepted - Explicit aliases that merely match a protected prefix and do not conflict with an actual base-model attribute retain the existing warning-only behavior instead of being rejected during generation. Actual attribute conflicts remain subject to the configured conflict checks. (#4038)

What's Changed

Read more

0.79.0

Choose a tag to compare

@github-actions github-actions released this 10 Sep 17:43
e0dcdf1

Breaking Changes

Error Handling Changes

  • Reject unsupported msgspec enum members - When generating msgspec.Struct output, enums that would render as a plain Enum and contain bool or float members now raise an error such as msgspec.Struct does not support bool Enum members, instead of producing code; schemas that previously generated output will now fail and no file is written (#3928)
  • msgspec Struct multiple inheritance now aborts generation on layout conflicts - When generating msgspec.Struct models with more than one base class, the generator now validates the inherited slot layouts and raises an Error refusing to write any output when the generated bases would produce incompatible instance layouts, whereas previously it emitted a module that only failed later at Python import time; both the CLI (which now exits with an error and writes no file) and the Python API (which now raises Error) are affected for such schemas (#3929)
  • Aliases now abort generation on conflicts or invalid names - When the aliases option is used, an alias value that collides with another field, is not a valid Python identifier, is a Python keyword, or conflicts with a reserved Pydantic or msgspec attribute name now raises an Error and stops generation instead of being silently sanitized or deduplicated (#3936)
  • Missing embedded resource anchors now raise an error - A reference to an anchor that does not exist within an embedded schema resource now raises an Error with the message that the embedded schema resource has no such anchor, instead of falling back to a physical document fetch (#3977)

Default Behavior Changes

  • Fail msgspec.Struct generation for bool and float enum aliases - By default, without any new opt-in flag, msgspec.Struct generation now aborts for enums whose bool or float members are not represented as an int or str subclass enum, changing behavior for existing schemas that formerly succeeded (#3928)
  • Aliases are treated as explicit field names and validated - Values supplied through the aliases option are now preserved as the chosen field names and checked against each output backend's naming rules, so configurations that previously produced auto-adjusted output for dataclasses, msgspec, or TypedDict backends may now fail instead of quietly renaming the field (#3936)
  • Runtime schema validators now validate Mapping inputs - Generated pydantic v2 runtime validators previously skipped validation and returned the value unchanged whenever the input was not a plain dict, so non-dict mapping objects such as UserDict or MappingProxyType bypassed pattern-property, required-group, conditional-required, property-count, and unique-items checks; they are now treated like dictionaries and validated, which can raise a ValidationError for mapping inputs that previously passed through silently (#3975)
  • Embedded schema resources now resolve in-document first - References to schemas declared with a nested $id are now resolved within the containing document before any file or HTTP lookup, and resource-scoped anchors and JSON pointers are honored, so schemas that previously resolved such references to physical files or remote URLs (including cases where an embedded resource shares a physical filename) can now produce different generated models and different fetch behavior (#3977)

Code Generation Changes

  • Dotted module exports now follow the final package layout - When treat-dot-as-module is combined with all-exports-scope, export depth and collision prefixes are recomputed against the final package layout and the empty-package re-export __init__.py files are emitted after module post-processing, so projects generated with both options together will see different import paths and __init__.py contents than before (#3926)
  • Validator method naming now accounts for more collisions - Generated pydantic v2 validator method names now receive incrementing _1, _2 suffixes when they collide with another validator, an existing field name, or an inherited validator, so models using the validators feature with inheritance or overlapping field names can produce method names that differ from previously generated output (#3942)
  • Same-named external validator functions now aliased - Validator functions that share a name across different modules now receive stable import aliases and repeated uses of one function share a single import binding, changing the generated import statements and call expressions compared with prior output (#3942)
  • Automatic aliasing of imports shadowed by field names - When a property name matches an imported symbol that is used inside that model's own annotations or defaults, the generator now emits an aliased import such as Optional as Optional_aliased, Field as Field_aliased, or list as list_aliased_2 and rewrites the affected annotations and defaults to reference the alias; this runs by default with no opt-in flag, so generated output changes for any schema whose field names collide with imported names (#3943)
  • Conditional required validation now uses strict JSON equality - Generated pydantic v2 runtime validators emit a new _json_schema_conditional_equal helper and stop relying on Python in membership, so when an if then else conditional required rule has const or enum values that include booleans or the integers 0 and 1 (or nested objects and arrays containing them) the generated code now distinguishes booleans from numbers and compares objects and arrays deeply, changing both the generated output and the runtime acceptance of payloads for schemas built with schema-validator-type pydantic-v2 (#3960)
  • Generated schema validators now import Mapping - When generating pydantic v2 schema validators with the bundled template, generated modules now include a new from collections.abc import Mapping as _Mapping import and every isinstance(data, dict) guard becomes isinstance(data, dict) or isinstance(data, _Mapping), while structural match cases change from case dict() to case dict() or _Mapping(); users who compare regenerated output against committed golden files will see these differences (#3975)

What's Changed

Read more

0.78.0

Choose a tag to compare

@github-actions github-actions released this 09 Sep 09:32
fca3f7a

Breaking Changes

Code Generation Changes

  • MCP tools schema-instance values preserved verbatim - Converting MCP tools inputs now keeps values under default, const, enum, and examples unchanged instead of rewriting internal definition references contained within them, so generated model defaults for MCP tools schemas that embed ref-like values inside these instance keywords can differ from previously generated output (#3891)
  • JSON Pointer decoding order changed - URI-fragment JSON Pointer tokens are now percent-decoded before the ~0 and ~1 tilde escapes are unescaped, following RFC evaluation order, so a $ref fragment that combines percent-encoding with tilde escapes such as a%7E1b now resolves to a different definition than before and can change the generated models for those schemas (#3893)
  • Exact numeric bounds in allOf intersection - The _intersect_constraint logic no longer coerces minimum, maximum, exclusiveMinimum, and exclusiveMaximum operands to float before comparing them, so integer bounds beyond the 2^53 exact-float precision limit are now intersected exactly instead of after lossy float rounding, changing the generated ge, le, gt, and lt values for schemas that merge such large integer bounds via allOf (#3900)
  • Numeric constraint precision preserved for large integers - Constraint keywords multipleOf, exclusiveMinimum, and exclusiveMaximum now retain exact integer values instead of coercing them to floats, so regenerated models for schemas containing integers beyond float precision emit different constraint literals and validate differently than before (#3903)
  • allOf merge modes now intersect bounds and enums - When allof_merge_mode is any value other than none, overlapping numeric, length, item, and property-count bounds from allOf subschemas are intersected to the tightest value and enum values are reduced to their intersection, instead of the previous deep-merge or concatenation behavior, so generated constraints and enum members differ from prior releases (#3961)
  • Retain string length bounds on root-array pattern property adapters - With field_constraints and generate_schema_validators both enabled and builtin types in use, a root-level array whose items declare a single ASCII-alphanumeric patternProperties key mapping to a length-bounded string now generates a constr adapter carrying min_length/max_length instead of a plain str, so regenerated models change output and enforce those bounds, rejecting values that previously passed validation (#3992)
  • GraphQL root operation types referenced by unions are now emitted - When a GraphQL schema defines a union that references the query, mutation, or subscription root operation type, the parser now removes that type from the excluded root set and emits it as a concrete model so the union resolves correctly, changing the generated output for such schemas across all output backends (#3910)
  • XML Schema occurrence bound combination corrected - When generating models from XSD inputs with nested or combined maxOccurs, an unbounded maximum is now propagated instead of computing a finite upper bound, so an unbounded parent combined with a finite child (and similar nesting) no longer emits a max_length constraint, changing the generated output for affected schemas (#3939)

Error Handling Changes

  • Empty allOf enum intersections now raise an error - When merging allOf subschemas whose enum values do not overlap, generation now raises a SchemaParseError and aborts instead of producing a widened or concatenated enum, so schemas that previously generated successfully can now fail (#3961)

Default Behavior Changes

  • New FutureWarning when Black or isort formatters are explicitly selected - Choosing black or isort formatters through the CLI, resolved configuration, or the Python API now emits a FutureWarning announcing that Black/isort will become optional in a future release; previously explicit formatter selection produced no warning. Black and isort remain required dependencies and generated output is unchanged, but strict setups that treat FutureWarning as an error may now fail unless the warning is filtered or disable-warnings is used (#4009)

What's Changed

Full Changelog: 0.77.0...0.78.0

0.77.0

Choose a tag to compare

@github-actions github-actions released this 08 Sep 06:55
ca1db14

Breaking Changes

Code Generation Changes

  • Nested Avro bytes and fixed defaults are now decoded to bytes - Avro bytes and fixed defaults nested inside arrays, maps, and records are now converted to Python bytes literals such as b'\xff', whereas previously only top-level bytes and fixed defaults were converted and nested ones remained as strings, so regenerating affected Avro schemas produces different default values across the pydantic v2, dataclass, and msgspec backends (#3896)
  • Dictionary defaults now render as factories for pydantic v2 dataclasses - Fields with a dictionary default now emit Field(default_factory=...) and add a from pydantic import Field import instead of the previous plain default rendering, so regenerating existing schemas produces different output; ClassVar and plain required dictionary fields are additionally kept free of Field statements (#3883)
  • Ruff formatter output changed by pinned ruff upgrade - The pinned ruff dependency was bumped from 0.14.10 to 0.16.6, which changes how generated code is formatted when the ruff formatters are used: blank lines are now inserted between import groups, import statements are regrouped and reordered, and combined aliased imports such as a single line importing several names with an as alias are split onto separate lines, so files regenerated from the same input will differ from output produced by earlier versions (#3874)
  • Avro decimal defaults now emit Decimal values - Avro bytes and fixed fields carrying a decimal logical type with a default now decode the default as a signed big-endian coefficient scaled by the declared scale and emit a Decimal(...) expression for backends that support deserialized defaults, replacing the previous raw bytes literal output, so regenerating existing models yields different default values (#3902)
  • Avro temporal defaults now emit temporal values - For Avro fields with a temporal logical type such as date, time-millis, time-micros, timestamp-millis, timestamp-micros, timestamp-nanos, or the local-timestamp variants, an integer default is now rendered as a temporal constructor like datetime_module.date.fromisoformat or an ISO-8601 string for backends that map these formats to str, instead of the previous raw integer literal, so generated models change for any schema using these defaults; timestamp defaults use UTC while local-timestamp and time defaults are naive (#3901)
  • Avro duration defaults now decode to timedelta - Avro fixed fields using the duration logical type with a default now emit timedelta(milliseconds=N) values instead of the raw bytes previously produced, changing generated output for any schema that relies on such defaults (#3907)
  • XML Schema nillable now recognizes the 1 lexical value - Elements declared with nillable="1" are now treated the same as nillable="true" and generate a nullable field, so schemas that previously produced a required field for nillable="1" will now produce an optional (nullable) field (#3884)
  • Avro unqualified name resolution now prefers the enclosing namespace - When resolving an unqualified Avro reference to a record, enum, or fixed type, the parser previously returned the first matching globally registered name; it now first looks for a matching type in the current enclosing namespace and only falls back to the global name when none exists. For Avro schemas that reuse the same unqualified name in both the null namespace and a nested namespace, generated models now reference the namespaced type instead of the global one, changing the generated output. (#3898)
  • Enum list defaults now preserve unmatched values - Previously, when a field default was a list, only values that matched an enum member were kept and any unmatched entries were silently dropped from the generated default; now unmatched entries such as plain strings, None, nested lists, and dictionaries are retained in place while matching entries are converted to enum members, so generated default lists can differ for the same input (#3887)
  • Multiple enum sources in a union are converted - The generator now inspects every enum branch of a union data type rather than only a single reference, so a value matching any enum member in the union is converted to that member and unmatched values remain literals, changing generated defaults for union-typed fields (#3887)
  • Combined XSD pattern facets change generated regexes - XML Schema restrictions with multiple sibling xs:pattern facets previously kept only the last pattern value, and now the parser merges all supported sibling patterns into a single anchored alternation and intersects any inherited base pattern through a nested lookahead, so regenerating from such schemas produces different pattern strings than before (#3908)
  • Pydantic outputs gain a python-re engine setting - Pydantic model output for these combined-pattern types now emits a regex_engine="python-re" configuration entry that was not present before, because the generated anchored lookahead requires Pydantic's Python regex engine, changing the generated model configuration for affected fields (#3908)
  • Type-alias defaults for msgspec Struct now build real Struct instances - When generating msgspec Struct models with type aliases enabled, a field whose default is an object or array and whose declared type is an alias that resolves through chained aliases or unions to a Struct model now emits a default factory that calls convert to construct the referenced Struct instance, whereas previously such defaults were emitted as plain literal values because only direct Struct references were converted; empty collections, mapping and dict defaults, primitive aliases, and recursive non-model aliases keep their prior output (#3906)

Error Handling Changes

  • Referenced boolean false definitions now rejected - MCP tool input or output schemas that reference a definition whose value is the boolean false now raise an Error reading "Referenced MCP boolean false definition is not supported" instead of emitting a permissive model, so schemas that previously generated code may now fail during conversion (#3890)
  • Invalid Avro decimal defaults now raise errors - Decimal defaults whose scale falls outside Python's Decimal range, whose coefficient exceeds the declared precision, or whose fixed encoding length does not match the declared size now raise an error during generation instead of being emitted, so schemas that previously generated successfully may now fail (#3902)
  • Unrepresentable Avro temporal defaults now raise errors - Temporal defaults outside the Python date or datetime range, time defaults not within a single day, and nanosecond defaults that cannot be represented exactly at microsecond precision now raise an error and abort generation, whereas these schemas previously generated successfully by emitting the raw integer default (#3901)
  • Avro duration defaults with calendar components or wrong size now error - Duration defaults with nonzero months or days, or that are not encoded as exactly 12 bytes with a fixed size of 12, now raise an error during generation instead of decoding to bytes (#3907)

Default Behavior Changes

  • RootModel metadata now reports the field name and alias as root - When emitting model metadata for RootModel classes, the emitted field entry now uses root for both its name and alias instead of the previously emitted synthetic field name, so consumers of the emitted metadata for root models will see different name and alias values (#3897)
  • Dynamic extraction now returns root-model type aliases - The generate_dynamic_models runtime helper now includes specialized RootModel type-alias classes that are assigned at the top level of the generated code, whereas it previously omitted any class whose defining module was not the generated module, so existing callers receive additional entries in the returned mapping while genuine imported dependencies remain excluded (#3904)

What's Changed

Read more