Repository navigation
Releases: datamodel-code-generator/datamodel-code-generator
Release list
0.86.0
Breaking Changes
Code Generation Changes
- Base64 bytes with length constraints now use
EncodedBytes- With--type-mappings string+byte=binary, aformat: bytestring withminLengthormaxLengthused to generateconbytes(...), which did not decode base64. With--field-constraintsit used to generateBase64Byteswith aField(max_length=...). It now generatesAnnotated[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 importAnnotated,Field,EncodedBytesandBase64Encoder. Pydantic checks these lengths on the text only from 2.10 on, and an explicit--target-pydantic-version 2now warns about each such field (#4798) - Strict bytes no longer skip base64 decoding for mapped byte strings - With
--type-mappings string+byte=binaryand--strict-types bytes,format: bytefields used to generateStrictBytes, orconbytes(..., strict=True)when they had lengths, and held the raw text. They now generateBase64Bytes, or theEncodedBytesform 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, aformat: bytestring member of a multi-typetypelist now generatesBase64Bytes, as inBase64Bytes | list[int], instead of plainbytes. 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
Nonemember - An enum with a value type base (theIntEnumwritten fortype: integer, or a--use-subclass-enumclass such as(int, Enum)) that listsnullused to get a member such asinteger_None = None, which made the generated module raiseTypeErrorat import. That member is no longer generated, and an enum whose only value isnullbecomes aRootModel[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 apatternthat 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 = Noneis now written askind: Literal['card'] = 'card'. The discriminated union failed at import before; a standalone model using such a tag now defaults to and dumps'card'instead ofNone(#4895) - The builtin formatter wraps long
Annotated[...] = Noneassignments like black - Where 0.85.0 put theNonedefault in parentheses on its own lines, the line is now split inside the annotation, as black does. The code is equivalent, but--checkruns 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-nameno longer repeats the key in prefixed names of models inside key-named branches (#4903), and--use-single-value-enum-as-consttypes nullable single-value unions asLiteral[...] | Nonekeeps mixed-type single-value branches apart instead of merging them into one Enum (#4926, #4927), and leavesnullable: truesingle-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
- Bump ty to 0.0.89 by Koudai Aono (@koxudaxi) in #4817
- Add 20261017 presets rendering TypedDict and dataclass enums as Literal by Koudai Aono (@koxudaxi) in #4815
- Add a script that compares generated output with a release by Koudai Aono (@koxudaxi) in #4861
- Refresh the generation index incrementally by Koudai Aono (@koxudaxi) in #4862
- Split Annotated and Union annotations whose value opening exceeds the line length by Koudai Aono (@koxudaxi) in #4794
- Release the stale name of a renamed type re-imported in a package init by Koudai Aono (@koxudaxi) in #4795
- Keep number constraints off fields whose generated type does not take them by Koudai Aono (@koxudaxi) in #4797
- Keep bytes mapped from base64 text decoded under strict types and constraints by Koudai Aono (@koxudaxi) in #4798
- Import what holds the enum members of x-python-type Literals by Koudai Aono (@koxudaxi) in #4799
- Add --use-tag-as-union-variant-name by Koudai Aono (@koxudaxi) in #4793
- Add --use-single-value-enum-as-const by Koudai Aono (@koxudaxi) in #4857
- Warn when --model-name-map keys match no generated model by Kratos (@wanjinhao1) in #4856
- Decide msgspec tagged unions over every module by Koudai Aono (@koxudaxi) in #4806
- Print msgspec 0.22 as the client's msgspec floor by Koudai Aono (@koxudaxi) in #4808
- Keep generation index model facts current and recover from failed refreshes by Koudai Aono (@koxudaxi) in #4866
- Make the model name map baseline scope public and note GraphQL input by Koudai Aono (@koxudaxi) in #4867
- Record the expected output each test compares by Koudai Aono (@koxudaxi) in #4870
- Compare expected output through per-module .refs indexes by Koudai Aono (@koxudaxi) in #4871
- Write null in an IntEnum or subclass enum as nullability instead of a None member by Koudai Aono (@koxudaxi) in #4884
- Warn about msgspec unions that hold an Enum beside another member of its JSON type by Koudai Aono (@koxudaxi) in #4885
- Keep an aliased import only when its alias is used by Koudai Aono (@koxudaxi) in #4887
- Keep patterns off Pydantic discriminator tags by Koudai Aono (@koxudaxi) in #4888
- Define the models a patternProperties rule names before the class that holds it by Koudai Aono (@koxudaxi) in #4889
- Drop support for recorded expected labels outside tests/data/expected by Koudai Aono (@koxudaxi) in #4898
- Explain --from-ci failures for runs whose logs gh cannot read yet by Koudai Aono (@koxudaxi) in #4899
- Add --recommend-options with schema analysis and rule-based recommendations by Koudai Aono (@koxudaxi) in #4883
- Compare candidate configurations by trial naming by Koudai Aono (@koxudaxi) in #4890
- Add a maintainer command that stores each expected output once by Koudai Aono (@koxudaxi) in #4902
- Store the remaining expected outputs once by Koudai Aono (@koxudaxi) in #4908
- Name the value of a tag-named variant without the variant under parent-prefixed naming by Koudai Aono (@koxudaxi) in #4903
- Add the recommender calibration script and corpus manifest by Koudai Aono (@koxudaxi) in #4911
- Compare generated packages as trees instead of listing expected directories by Koudai Aono (@koxudaxi) in #4912
- Default optional one-value Literal discriminator tags to their value by Koudai Aono (@koxudaxi) in #4895
- Flatten msgspec Structs whose generated bases have conflicting layouts by Koudai Aono (@koxudaxi) in #4913
- Fail when stored expected outputs or test inputs hold the same content twice by Koudai Aono (@koxudaxi) in #4921
- Document the model processing passes and when each fact becomes final by Koudai Aono (@koxudaxi) in h...
0.85.0
Breaking Changes
Error Handling Changes
- Output encoding failures now raise
Errorinstead 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/encodingcannot encode the text,generate()raisesdatamodel_code_generator.Errorwith the messageUnable to encode output using encoding '...': ...instead of lettingUnicodeEncodeErrorescape. The CLI now prints this as a single error line and exits with code 2 instead of showing a traceback. Python API callers that caughtUnicodeEncodeErrororUnicodeErrorneed to catchErrorinstead. 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 theencodingoption during config preparation, before any input is read or any output path is created. An unknown encoding such asnosuch, or a codec that is not a text encoding such ashex, now raisesdatamodel_code_generator.Error(CLI exit code 2, one-line message) instead of a rawLookupErrorraised later. Code that caughtLookupErrorfor a bad encoding must catchErrorinstead (#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-mapvalues and explicit field aliases. It also applies to client and FastAPI generation settings such aspackage,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 withE_CONFIG_VALUEor 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,customTypePathorx-python-import(including itsqualnameandnative-fieldpaths) that does not exist in the target Python version now stops generation withTYPE is unavailable for target Python X.Y. Examples areenum.StrEnumfor 3.10,pathlib.types.PathInfofor 3.13,builtins.frozendictfor 3.14 andpathlib.DirEntryInfofor 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-versionor use--import-overridesto 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 thestr,reandunicodedatabehaviour 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 becomefield_京都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-membersno longer turnsɤintofor 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-librarytypingorcollections.abcnames given through--type-overrides,customTypePathorx-python-importare now resolved against--target-python-version. If the name is newer than the target, the import now comes fromtyping_extensions. For example,typing.LiteralStringandcollections.abc.Bufferwith target 3.10 now producefrom typing_extensions import Buffer, LiteralStringinstead of importing them fromtypingandcollections.abcas before. Projects that compare generated output, or that do not havetyping_extensionsinstalled, 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 usedsys.stdlib_module_namesof the running interpreter. When the running Python differs from the target, imports in generated files can move to a different section. For example,tomllibis grouped as third party when targeting 3.10,distutilsis grouped as standard library when targeting 3.10 even on a newer interpreter, andsre_parsebecomes third party whileprofilingbecomes standard library when targeting 3.15 (#4791) - Builtin formatter places each module of a multi-module import in its own section - With
--formatters builtin, animport a, b, cstatement 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ÑumberorÖ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 exampleclass 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
- fix: report output encoding failures without truncating the file by Matt Van Horn (@mvanhorn) in #4615
- Add Python 3.15 as a target version by Botond Kalmár (@Kaboka22) in #4609
- Lock dependencies with Python 3.15 wheels by Koudai Aono (@koxudaxi) in #4766
- Run the test suite on Python 3.15 in CI by Koudai Aono (@koxudaxi) in #4768
- Keep target names and type imports independent of the host for Python 3.15 by Koudai Aono (@koxudaxi) in #4769
- Use the newest target when the interpreter is newer than every target by Koudai Aono (@koxudaxi) in #4770
- Name the black requirement when black cannot format the target by Koudai Aono (@koxudaxi) in #4771
- Add 20261015 presets with Python 3.15 targets by Koudai Aono (@koxudaxi) in #4772
- Document Python 3.15 targets by Koudai Aono (@koxudaxi) in #4773
- Read builtin names for field collisions from the static target table by Koudai Aono (@koxudaxi) in #4786
- Follow the target Python's Unicode version for generated names by Koudai Aono (@koxudaxi) in #4790
- Group the builtin formatter's imports by the target Python's standard library by Koudai Aono (@koxudaxi) in #4791
- Take the target Python of model type decisions from the field configuration by Koudai Aono (@koxudaxi) in #4787
- Check type overrides, customTypePath and x-python-import against the target Python by Koudai Aono (@koxudaxi) in #4788
New Contributors
- Botond Kalmár (@Kaboka22) made their first contribution in #4609
Full Changelog: 0.84.0...0.85.0
0.84.0
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-clientrequire--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).--outputnames the model file (models.pyabove), not a package. The other--server-*/--client-*options are in the CLI reference.- A client needs a base URL: an operation without
serversin the document needs--client-default-base-url, otherwise generation stops with an error --generate-serverand--generate-clientrequire an explicit--input-file-type openapi: an input type left to detection,auto, or any other type stops withError: --generate-server requires --input-file-type openapi, not autoand exit code 2 before the input is read (#4639)- Every server and client setting is a CLI option, a
[tool.datamodel-codegen]key, and agenerate()option; both targets reuse the model output options (formatters, file header, line length, and so on) (#4401, #4406, #4430) --check,--diff-against,--watchand--output-format jsonwork 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
--checklists leftover files as EXTRA (#4167, #4456, #4461). A generation that leaves.pyfiles it no longer generates in the package, such as the router module of a removed tag, names them in oneLeftoverFilesWarning(#4626). The required runtime dependencies are printed as auv add ...command instead of being written to a requirements file (#4166); datamodel-code-generator does not editpyproject.toml.--output-format jsonreports the package and the same dependencies astarget.packageandtarget.dependencies(#4680). The line asks forpydantic>=2.14.0, as does the client README (#4601, #4651). An explicit--target-pydantic-versionolder than that minimum (2,2.11or2.12) is refused withError:and exit code 2 for a server, and for a client whose models use pydantic; leave it unset or use2.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"orgenerate_client="httpx2"(plusserver_*/client_*options) togenerate(); withoutput=Noneit returnsGeneratedModuleswith 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
defaulton both targets:routers/default.py,DefaultService,create_app(default=...)and the--server-router-nameskeydefaulton the server,client.defaulton the client. A tag that itself derivesdefaultis 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 byadditionalProperties, whose type has no single scalar kind (an object, a mapping, an array of objects, a mixed enum, or a union such asFilter | stringorstring | string[]) travels asapplication/jsonparts, 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 explicitencoding.<name>.contentTypenaming 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 asapplication/jsonparts 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 anddiscriminatorapply; a model that populates by name also takes a part or field named like the Python field (max_agebesidemaxAge); typed other properties are checked by their type also when--reuse-modelmakes the form's model inherit an equal one; a form body whose schema is one schema ornullis 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 amultipart/form-dataContent-Type, which a server reads as an empty form (#4727). - Form encodings follow the OpenAPI version: an encoding's
style,explodeandallowReservedapply to URL-encoded bodies, and tomultipart/form-dataonly 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 itscontentTypesays. The server now reads styled members (form,deepObject,spaceDelimited,pipeDelimited), a URL-encoded member'scontentType, 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 (additionalPropertiestrueor a schema) or the model configuration keeps them (--extra-fields allow) (#4737). - Binary strings: a
format: binarystring is raw bytes that only a whole body (application/octet-stream,image/png,text/csv) or a multipart file part can carry; atext/*body whose model type is one binary string travels as raw bytes, also when the schema reaches it through$refor a singleallOf/anyOf/oneOfbranch, besidenull, 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, atext/*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 valueformat: bytefor base64 text, optionally with--type-mappings string+byte=binaryto hold the decoded bytes (#4658). The model type decides, soallOf: [{type: string}, {format: binary}]is a binary body too; bytes the models constrain (maxLength: 16asconbytes(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 raisesDecodeError(unencodablefor abytesrequest body,invalid_valuefor a response) while files, paths and iterables stream unchecked. Anenumorconstof 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); acontentkey that is no media type (multipart/form data) or whose text is not ASCII header text (text/plain; label="café"); a URL-encoded body withpatternPropertiesor that is not an object (the server reads such a body only with--server-body-mode request); a--server-router-namesgroup key that matches no group (it was ignored) and a--client-resource-namestag that is the first tag of no operation. The server routes literal path text by its decoded value (/files/a%20bregisters/files/a b, which the client's request/files/a%20bmatches); literal%2F,%7Bor%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-scopesgetsapiadded to the default scopes, `--target-python-vers...
0.83.0
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
LeafModelorValues1, changing class names and module contents compared with previous output (#4115) - Generic schemas using
$dynamicRefare now specialized per dynamic scope - A plain-name$dynamicRefpreviously 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$dynamicAnchorname 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$dynamicRefwith$dynamicAnchor, including remote, embedded, recursive, root-level and tupleadditionalItemsgenerics, 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
Modelsuffixes. Output ordering and names of surrounding models can therefore differ from previous releases, and this applies under--naming-strategy full-pathand--collapse-root-modelsas well (#4117) - OpenAPI specifications referencing external JSON Schema generics gain specialized models -
parse_rawnow drains pending dynamic-scope specializations after every specification has been read, so components that reference external schemas rebinding a$dynamicAnchorproduce additional specialized models instead of reusing a single shared model (#4117) - Dynamic ref resolution scope for inline resources and merged refs -
$dynamicRefis now resolved within the schema resource a subschema stands for, namely an inline subschema declaring its own$idand the target of a$refmerged with sibling keywords, with enclosing and referencing resources still able to override the binding so the outermost$dynamicAnchordeclaration wins; schemas combining$dynamicRefwith embedded$idresources or with$refplus sibling keywords may now generate different field types and model shapes than before, for example a resource-local anchor type such asRootModel[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-modelsand--reuse-scope=treepreviously 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 suffixedFieldModelalongsideField, 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
$reftargets now resolve through aliases - A referenced schema that is itself a$refis merged with its own target, so a$refwith 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$refalongside other keywords (#4122) - References inside merged targets from other documents are rebased - When a
$refwith sibling keywords points into another document, nested$refand plain-name$dynamicRefvalues 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
$recursiveRefin merged cross-document$reftargets - A self-pointing$recursiveRefinside a schema merged in from another document now resolves to the nearest enclosing$recursiveAnchorof 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$recursiveAnchorthe 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_resourcesrather 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, orBranchnow 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
$idresources resolve to the resource root - A$recursiveReflocated in a subschema that declares its own$idbut no$recursiveAnchornow targets the root of that embedded resource instead of the nearest enclosing$recursiveAnchoror 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$recursiveRefare 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
FieldorFieldModel; it is now named after the file stem, for exampleTreeorNode, so class names and field annotations such aslist[Field]change for such schemas (#4132)
Error Handling Changes
- New warning for allOf base classes that cannot follow dynamic scope bindings - When an
allOfbase class is referenced from a schema whose dynamic scope would rebind its$dynamicAnchor, the parser now emits aUserWarningstating that the base class resolves its$dynamicRefwithin 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-mappingentries 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
- Rebalance CI test shard weights by Koudai Aono (@koxudaxi) in #4085
- Run measured slow tests first by Koudai Aono (@koxudaxi) in #4086
- Balance Windows and macOS test shards by Koudai Aono (@koxudaxi) in #4087
- Fix release draft validation for stacked pull requests by Koudai Aono (@koxudaxi) in #4088
- Record per-version CI test shard weights by Koudai Aono (@koxudaxi) in #4089
- Add Ubuntu coverage test shards by Koudai Aono (@koxudaxi) in #4090
- Run payload validation only with coverage by Koudai Aono (@koxudaxi) in #4092
- Bump the github-actions group with 7 updates by Dependabot (@dependabot)[bot] in #4096
- [pre-commit.ci] pre-commit autoupdate by pre-commit.ci (@pre-commit-ci)[bot] in https://github.com/datamodel-code-ge...
0.82.0
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_constraintsproperty in the base field model only suppresses constraints for direct self references, so a self referencing field whose type is a container such aslist,Sequence,dict,Mapping,set,frozenset, ortuplenow keeps its size constraints. Pydantic output gainsField(min_length=..., max_length=...)and msgspec output gainsMeta(min_length=..., max_length=...)where the generated code previously had a bare default, for examplefriends: list[Pet] | None = Nonebecomesfriends: 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
minItemsormaxItemson 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 pydanticValidationErrorormsgspec.ValidationErrorfor payloads that used to pass (#4071)
What's Changed
- Fix builtin formatter wrapping of boolean expressions by Koudai Aono (@koxudaxi) in #4073
- Fix collapse-root-models dropping array constraints on self-referencing unions by Yasser Sheikh (@ysheikh2) in #4071
- Generate validators for not-required groups and presence-only if conditions by Yasser Sheikh (@ysheikh2) in #4072
- Fix bare Mapping import dropped when schema validators alias it by Yasser Sheikh (@ysheikh2) in #4076
- Allow manual replay of failed release draft analysis by Koudai Aono (@koxudaxi) in #4079
New Contributors
- Yasser Sheikh (@ysheikh2) made their first contribution in #4071
Full Changelog: 0.81.0...0.82.0
0.81.0
Breaking Changes
Code Generation Changes
- Undeclared required keys now retained in variant runtime validation - When combining
--read-only-write-only-model-typerequest/response variants with--schema-validator-typeruntime 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
- Fix parser template data reuse by Koudai Aono (@koxudaxi) in #4054
- Fix required validation in request and response models by Koudai Aono (@koxudaxi) in #4055
- Share JSON Schema reference rewriting across inputs by Koudai Aono (@koxudaxi) in #4057
- Move output model decisions out of parsers by Koudai Aono (@koxudaxi) in #4058
- Move output configuration out of parsers by Koudai Aono (@koxudaxi) in #4059
- Guard output policy and private parser boundaries by Koudai Aono (@koxudaxi) in #4060
Full Changelog: 0.80.0...0.81.0
0.80.0
Breaking Changes
Code Generation Changes
- Python union annotations preserve unsupported member types - When generating from Python input models, unions containing members such as
Callableretain 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 normalNoneorUNSETdefault 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 aRootModelclass 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
propertyNamesgenerate 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
allOfroots 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
propertyNamesalternatives 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-mappingsor--type-overridesremap Avro logical types to physical types such asintorbytes, 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
- Fix nightly payload validation by Koudai Aono (@koxudaxi) in #4028
- Preserve unsupported Python union members by Koudai Aono (@koxudaxi) in #3947
- Preserve inherited Python field overrides by Koudai Aono (@koxudaxi) in #3948
- Disambiguate Python model definitions by Koudai Aono (@koxudaxi) in #3949
- Preserve Python alias field types by Koudai Aono (@koxudaxi) in #3950
- Fix nested Python type imports by Koudai Aono (@koxudaxi) in #3979
- Fix specialized Python model reuse by Koudai Aono (@koxudaxi) in #3980
- Avoid invalid nested default factories by Koudai Aono (@koxudaxi) in #3953
- Preserve nested Python type metadata by Koudai Aono (@koxudaxi) in #3981
- Preserve Python RootModel inputs by Koudai Aono (@koxudaxi) in #3954
- Preserve safe native Pydantic set hashes by Koudai Aono (@koxudaxi) in #4025
- Fix final RootModel sequence types by Koudai Aono (@koxudaxi) in #3956
- Preserve XSD generation with unused type collisions by Koudai Aono (@koxudaxi) in #4034
- Preserve MCP boolean definition references by Koudai Aono (@koxudaxi) in #4033
- Preserve custom dataclass alias templates by Koudai Aono (@koxudaxi) in #4032
- Preserve warning-only Pydantic aliases by Koudai Aono (@koxudaxi) in #4038
- Preserve aliased discriminator generation by Koudai Aono (@koxudaxi) in #4039
- Limit GraphQL typename collision renaming by Koudai Aono (@koxudaxi) in #4042
- Preserve Python generic reuse fallback by Koudai Aono (@koxudaxi) in #4037
- Fix msgspec typename inheritance by Koudai Aono (@koxudaxi) in #4045
- Preserve generation with unused msgspec enums by Koudai Aono (@koxudaxi) in #4035
- Fix root model alias constraints by Koudai Aono (@koxudaxi) in #3962
- Fix numeric allOf intersections by Koudai Aono (@koxudaxi) in #3963
- Fix compound property name constraints by Koudai Aono (@koxudaxi) in #3970
- Apply union constraints to matching types by Koudai Aono (@koxudaxi) in #3971
- Preserve raw pattern property inputs by Koudai Aono (@koxudaxi) in #3972
- Preserve additional pattern intersections by Koudai Aono (@koxudaxi) in #3984
- Enforce undeclared required properties by Koudai Aono (@koxudaxi) in #3988
- Preserve pattern validation with root annotations by Koudai Aono (@koxudaxi) in #3990
- Respect nested schema resource drafts by Koudai Aono (@koxudaxi) in #4041
- Preserve compatible allOf format constraints by Koudai Aono (@koxudaxi) in #4043
- Fix allOf property key constraints by Koudai Aono (@koxudaxi) in #4044
- Fix non-string property name alternatives by Koudai Aono (@koxudaxi) in #4046
- Preserve mapped Avro defaults by Koudai Aono (@koxudaxi) in https://github.com/datamodel-code-gen...
0.79.0
Breaking Changes
Error Handling Changes
- Reject unsupported msgspec enum members - When generating
msgspec.Structoutput, enums that would render as a plainEnumand 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.Structmodels with more than one base class, the generator now validates the inherited slot layouts and raises anErrorrefusing 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 raisesError) are affected for such schemas (#3929) - Aliases now abort generation on conflicts or invalid names - When the
aliasesoption 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
aliasesoption 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 asUserDictorMappingProxyTypebypassed pattern-property, required-group, conditional-required, property-count, and unique-items checks; they are now treated like dictionaries and validated, which can raise aValidationErrorfor mapping inputs that previously passed through silently (#3975) - Embedded schema resources now resolve in-document first - References to schemas declared with a nested
$idare 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-moduleis combined withall-exports-scope, export depth and collision prefixes are recomputed against the final package layout and the empty-package re-export__init__.pyfiles are emitted after module post-processing, so projects generated with both options together will see different import paths and__init__.pycontents than before (#3926)
- Validator method naming now accounts for more collisions - Generated pydantic v2 validator method names now receive incrementing
_1,_2suffixes 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, orlist as list_aliased_2and 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_equalhelper and stop relying on Pythoninmembership, 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 withschema-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 _Mappingimport and everyisinstance(data, dict)guard becomesisinstance(data, dict) or isinstance(data, _Mapping), while structuralmatchcases change fromcase dict()tocase dict() or _Mapping(); users who compare regenerated output against committed golden files will see these differences (#3975)
What's Changed
- Fix release benchmark PR lookup by Koudai Aono (@koxudaxi) in #4017
- Resolve referenced Protobuf standard types by Koudai Aono (@koxudaxi) in #3934
- Reject unsupported msgspec enums by Koudai Aono (@koxudaxi) in #3928
- Reject incompatible msgspec bases by Koudai Aono (@koxudaxi) in #3929
- Fix concurrent constraint initialization by Koudai Aono (@koxudaxi) in #4015
- Fix reused model module imports by Koudai Aono (@koxudaxi) in #3924
- Fix recursive model reexports by Koudai Aono (@koxudaxi) in #3926
- Validate explicit field aliases by Koudai Aono (@koxudaxi) in #3936
- Resolve aliases using final model names by Koudai Aono (@koxudaxi) in #3937
- Resolve validator name collisions by Koudai Aono (@koxudaxi) in #3942
- Avoid field type name collisions by Koudai Aono (@koxudaxi) in #3943
- Fix literal allOf pattern intersections by Koudai Aono (@koxudaxi) in #3945
- Preserve outer scalar allOf constraints by Koudai Aono (@koxudaxi) in #3946
- Use JSON equality in conditional validators by Koudai Aono (@koxudaxi) in #3960
- Preserve inline allOf schema validators by Koudai Aono (@koxudaxi) in #3974
- Accept mapping inputs in schema validators by Koudai Aono (@koxudaxi) in #3975
- Preserve input changes during watched generation by Koudai Aono (@koxudaxi) in #3989
- Resolve embedded schema resources by Koudai Aono (@koxudaxi) in https://github.com/datamodel-code-generator/datamodel-c...
0.78.0
Breaking Changes
Code Generation Changes
- MCP tools schema-instance values preserved verbatim - Converting MCP tools inputs now keeps values under
default,const,enum, andexamplesunchanged 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
~0and~1tilde escapes are unescaped, following RFC evaluation order, so a$reffragment that combines percent-encoding with tilde escapes such asa%7E1bnow 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_constraintlogic no longer coercesminimum,maximum,exclusiveMinimum, andexclusiveMaximumoperands 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 generatedge,le,gt, andltvalues for schemas that merge such large integer bounds via allOf (#3900)
- Numeric constraint precision preserved for large integers - Constraint keywords
multipleOf,exclusiveMinimum, andexclusiveMaximumnow 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_modeis 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_constraintsandgenerate_schema_validatorsboth 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 aconstradapter carryingmin_length/max_lengthinstead of a plainstr, 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 amax_lengthconstraint, 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
SchemaParseErrorand 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
blackorisortformatters 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
- Update CHANGELOG for 0.77.0 by @dcg-generated-docs[bot] in #3998
- Preserve MCP schema values by Koudai Aono (@koxudaxi) in #3891
- Fix JSON Pointer decoding by Koudai Aono (@koxudaxi) in #3893
- Fix schema reference cycle detection by Koudai Aono (@koxudaxi) in #3894
- Preserve integer bound precision by Koudai Aono (@koxudaxi) in #3900
- Preserve numeric constraint precision by Koudai Aono (@koxudaxi) in #3903
- Fix scalar and array allOf roots by Koudai Aono (@koxudaxi) in #3905
- Intersect allOf bounds and enums by Koudai Aono (@koxudaxi) in #3961
- Preserve string bounds in pattern adapters by Koudai Aono (@koxudaxi) in #3992
- Fix GraphQL root union references by Koudai Aono (@koxudaxi) in #3910
- Fix GraphQL typename collisions by Koudai Aono (@koxudaxi) in #3911
- Avoid unused template dependency scans by Koudai Aono (@koxudaxi) in #3919
- Isolate inflection type checks by Koudai Aono (@koxudaxi) in #3920
- Fix dataclass aliases on older Pydantic runtimes by Koudai Aono (@koxudaxi) in #4005
- Clarify formatter selection and default warning by Koudai Aono (@koxudaxi) in #4006
- Add builtin formatter presets by Koudai Aono (@koxudaxi) in #4008
- Warn about legacy dependency support by Koudai Aono (@koxudaxi) in #4002
- Prepare optional Black and isort dependencies by Koudai Aono (@koxudaxi) in #4009
- Reject conflicting XSD field names by Koudai Aono (@koxudaxi) in #3933
- Preserve unbounded XSD occurrences by Koudai Aono (@koxudaxi) in #3939
- Preserve XSD simple content inheritance by Koudai Aono (@koxudaxi) in #3940
- Fix discriminator field aliases by Koudai Aono (@koxudaxi) in #3922
- Fix external discriminator mappings by Koudai Aono (@koxudaxi) in #3923
Full Changelog: 0.77.0...0.78.0
0.77.0
Breaking Changes
Code Generation Changes
- Nested Avro bytes and fixed defaults are now decoded to bytes - Avro
bytesandfixeddefaults nested inside arrays, maps, and records are now converted to Python bytes literals such asb'\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 afrom pydantic import Fieldimport instead of the previous plain default rendering, so regenerating existing schemas produces different output;ClassVarand 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
asalias 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
bytesandfixedfields carrying adecimallogical type with a default now decode the default as a signed big-endian coefficient scaled by the declared scale and emit aDecimal(...)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 thelocal-timestampvariants, an integerdefaultis now rendered as a temporal constructor likedatetime_module.date.fromisoformator an ISO-8601 string for backends that map these formats tostr, 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
fixedfields using thedurationlogical type with a default now emittimedelta(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
1lexical value - Elements declared withnillable="1"are now treated the same asnillable="true"and generate a nullable field, so schemas that previously produced a required field fornillable="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:patternfacets 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
convertto 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
falsenow 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 usesrootfor 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_modelsruntime helper now includes specializedRootModeltype-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
- Ignore black and isort updates in Dependabot by Koudai Aono (@koxudaxi) in #3855
- Refresh PyPI metadata in Docker builds by Koudai Aono (@koxudaxi) in #3857
- Improve release benchmark charts by Koudai Aono (@koxudaxi) in #3860
- Fix msgspec payload test options by Koudai Aono (@koxudaxi) in #3863
- Fix generation cache and loader regressions by Koudai Aono (@koxudaxi) in #3865
- Guard shared model boundaries by Koudai Aono (@koxudaxi) in #3866
- Test dynamic model cache ordering by Koudai Aono (@koxudaxi) in #3871
- Update Ruff to 0.16.6 by Koudai Aono (@koxudaxi) in #3874
- Exclude maintenance from release notes by Koudai Aono (@koxudaxi) in #3877
- Clarify OpenAPI parameter scopes by Koudai Aono (@koxudaxi) in #3879
- Fix relative YAML cache paths by Koudai Aono (@koxudaxi) in #3880
- Fix Avro container defaults by Koudai Aono (@koxudaxi) in #3896
- Fix MCP definition references by Koudai Aono (@koxudaxi) in #3890
- Fix float configuration output by Koudai Aono (@koxudaxi) in https://github.com/datamodel-code-generator/datamodel-code-generator/pul...