Skip to content

Commit 5253417

Browse files
committed
Add pydantic missing sentinel option
1 parent 1870d87 commit 5253417

48 files changed

Lines changed: 813 additions & 59 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/architecture.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,16 +154,16 @@ classDiagram
154154

155155
| Config model | Field count | Purpose |
156156
| --- | ---: | --- |
157-
| `BaseGenerateConfig` | 138 | Shared generation options. |
158-
| `GenerateConfig` | 153 | Public `generate()` configuration. |
159-
| `ParserConfig` | 134 | Base parser dependency injection and parser options. |
160-
| `JSONSchemaParserConfig` | 136 | JSON Schema parser options. |
161-
| `OpenAPIParserConfig` | 142 | OpenAPI-specific parser options. |
162-
| `AsyncAPIParserConfig` | 143 | AsyncAPI-specific parser options. |
163-
| `XMLSchemaParserConfig` | 137 | XML Schema-specific parser options. |
164-
| `ProtobufParserConfig` | 137 | Protocol Buffers-specific parser options. |
165-
| `AvroParserConfig` | 136 | Avro-specific parser options. |
166-
| `GraphQLParserConfig` | 137 | GraphQL-specific parser options. |
157+
| `BaseGenerateConfig` | 139 | Shared generation options. |
158+
| `GenerateConfig` | 154 | Public `generate()` configuration. |
159+
| `ParserConfig` | 135 | Base parser dependency injection and parser options. |
160+
| `JSONSchemaParserConfig` | 137 | JSON Schema parser options. |
161+
| `OpenAPIParserConfig` | 143 | OpenAPI-specific parser options. |
162+
| `AsyncAPIParserConfig` | 144 | AsyncAPI-specific parser options. |
163+
| `XMLSchemaParserConfig` | 138 | XML Schema-specific parser options. |
164+
| `ProtobufParserConfig` | 138 | Protocol Buffers-specific parser options. |
165+
| `AvroParserConfig` | 137 | Avro-specific parser options. |
166+
| `GraphQLParserConfig` | 138 | GraphQL-specific parser options. |
167167

168168
### Formatter Names
169169

docs/cli-reference/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ This documentation is auto-generated from test cases.
1111
| 📁 [Base Options](base-options.md) | 12 | Input/output configuration |
1212
| 🔧 [Typing Customization](typing-customization.md) | 30 | Type annotation and import behavior |
1313
| 🏷️ [Field Customization](field-customization.md) | 27 | Field naming and docstring behavior |
14-
| 🏗️ [Model Customization](model-customization.md) | 43 | Model generation behavior |
14+
| 🏗️ [Model Customization](model-customization.md) | 44 | Model generation behavior |
1515
| 🎨 [Template Customization](template-customization.md) | 25 | Output formatting and custom rendering |
1616
| 📘 [OpenAPI-only Options](openapi-only-options.md) | 8 | OpenAPI-specific features |
1717
| 📋 [GraphQL-only Options](graphql-only-options.md) | 1 | |
@@ -225,6 +225,7 @@ This documentation is auto-generated from test cases.
225225
- [`--use-generic-base-class`](model-customization.md#use-generic-base-class)
226226
- [`--use-generic-container-types`](typing-customization.md#use-generic-container-types)
227227
- [`--use-inline-field-description`](field-customization.md#use-inline-field-description)
228+
- [`--use-missing-sentinel`](model-customization.md#use-missing-sentinel)
228229
- [`--use-non-positive-negative-number-constrained-types`](typing-customization.md#use-non-positive-negative-number-constrained-types)
229230
- [`--use-object-type`](typing-customization.md#use-object-type)
230231
- [`--use-one-literal-as-default`](model-customization.md#use-one-literal-as-default)

docs/cli-reference/model-customization.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
| [`--use-default-kwarg`](#use-default-kwarg) | Use default= keyword argument instead of positional argument... |
4444
| [`--use-frozen-field`](#use-frozen-field) | Generate frozen (immutable) field definitions for readOnly p... |
4545
| [`--use-generic-base-class`](#use-generic-base-class) | Generate a shared base class with model configuration to avo... |
46+
| [`--use-missing-sentinel`](#use-missing-sentinel) | Use Pydantic's MISSING sentinel for optional fields without ... |
4647
| [`--use-one-literal-as-default`](#use-one-literal-as-default) | Use single literal value as default when enum has only one o... |
4748
| [`--use-root-model-sequence-interface`](#use-root-model-sequence-interface) | Make non-null sequence-like Pydantic v2 RootModel classes im... |
4849
| [`--use-serialize-as-any`](#use-serialize-as-any) | Wrap fields with subtypes in Pydantic's SerializeAsAny. |
@@ -5246,6 +5247,7 @@ The `--target-pydantic-version` flag controls Pydantic version-specific config:
52465247

52475248
- **2**: Uses `populate_by_name=True` (compatible with Pydantic 2.0-2.10)
52485249
- **2.11**: Uses `validate_by_name=True` (for Pydantic 2.11+)
5250+
- **2.12**: Uses `validate_by_name=True` and allows features that require Pydantic 2.12+
52495251

52505252
This prevents breaking changes when generated code is used on older Pydantic versions.
52515253

@@ -6003,6 +6005,87 @@ Generate a shared base class with model configuration to avoid repetition (DRY).
60036005

60046006
---
60056007

6008+
## `--use-missing-sentinel` {#use-missing-sentinel}
6009+
6010+
Use Pydantic's MISSING sentinel for optional fields without defaults.
6011+
6012+
The `--use-missing-sentinel` flag generates `MISSING` as the default for optional
6013+
Pydantic v2 fields that do not define a schema default. This preserves the
6014+
difference between an omitted field and a nullable field set to `None`.
6015+
6016+
**Related:** [`--strict-nullable`](model-customization.md#strict-nullable), [`--target-pydantic-version`](model-customization.md#target-pydantic-version)
6017+
6018+
!!! tip "Usage"
6019+
6020+
```bash
6021+
datamodel-codegen --input schema.json --output-model-type pydantic_v2.BaseModel --use-missing-sentinel # (1)!
6022+
```
6023+
6024+
1. :material-arrow-left: `--use-missing-sentinel` - the option documented here
6025+
6026+
??? example "Examples"
6027+
6028+
**Input Schema:**
6029+
6030+
```json
6031+
{
6032+
"$schema": "http://json-schema.org/draft-07/schema#",
6033+
"title": "MissingSentinel",
6034+
"type": "object",
6035+
"required": ["required", "requiredNullable"],
6036+
"properties": {
6037+
"required": {
6038+
"type": "integer"
6039+
},
6040+
"requiredNullable": {
6041+
"type": ["integer", "null"]
6042+
},
6043+
"unrequired": {
6044+
"type": "integer"
6045+
},
6046+
"nullableUnrequired": {
6047+
"type": ["integer", "null"]
6048+
},
6049+
"withDefault": {
6050+
"type": "integer",
6051+
"default": 1
6052+
},
6053+
"nullDefault": {
6054+
"type": ["integer", "null"],
6055+
"default": null
6056+
},
6057+
"aliased-field": {
6058+
"type": "string"
6059+
}
6060+
}
6061+
}
6062+
```
6063+
6064+
**Output:**
6065+
6066+
```python
6067+
# generated by datamodel-codegen:
6068+
# filename: missing_sentinel.json
6069+
# timestamp: 2019-07-26T00:00:00+00:00
6070+
6071+
from __future__ import annotations
6072+
6073+
from pydantic import BaseModel, Field
6074+
from pydantic.experimental.missing_sentinel import MISSING
6075+
6076+
6077+
class MissingSentinel(BaseModel):
6078+
required: int
6079+
requiredNullable: int | None
6080+
unrequired: int | MISSING = MISSING
6081+
nullableUnrequired: int | None | MISSING = MISSING
6082+
withDefault: int | None = 1
6083+
nullDefault: int | None = None
6084+
aliased_field: str | MISSING = Field(MISSING, alias='aliased-field')
6085+
```
6086+
6087+
---
6088+
60066089
## `--use-one-literal-as-default` {#use-one-literal-as-default}
60076090

60086091
Use single literal value as default when enum has only one option.

docs/cli-reference/quick-reference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ datamodel-codegen [OPTIONS]
139139
| [`--use-default-kwarg`](model-customization.md#use-default-kwarg) | Use default= keyword argument instead of positional argument for fields with def... |
140140
| [`--use-frozen-field`](model-customization.md#use-frozen-field) | Generate frozen (immutable) field definitions for readOnly properties. |
141141
| [`--use-generic-base-class`](model-customization.md#use-generic-base-class) | Generate a shared base class with model configuration to avoid repetition (DRY). |
142+
| [`--use-missing-sentinel`](model-customization.md#use-missing-sentinel) | Use Pydantic's MISSING sentinel for optional fields without defaults. |
142143
| [`--use-one-literal-as-default`](model-customization.md#use-one-literal-as-default) | Use single literal value as default when enum has only one option. |
143144
| [`--use-root-model-sequence-interface`](model-customization.md#use-root-model-sequence-interface) | Make non-null sequence-like Pydantic v2 RootModel classes implement collections.... |
144145
| [`--use-serialize-as-any`](model-customization.md#use-serialize-as-any) | Wrap fields with subtypes in Pydantic's SerializeAsAny. |
@@ -383,6 +384,7 @@ All options sorted alphabetically:
383384
- [`--use-generic-base-class`](model-customization.md#use-generic-base-class) - Generate a shared base class with model configuration to avo...
384385
- [`--use-generic-container-types`](typing-customization.md#use-generic-container-types) - Use generic container types (Sequence, Mapping) for type hin...
385386
- [`--use-inline-field-description`](field-customization.md#use-inline-field-description) - Add field descriptions as inline comments.
387+
- [`--use-missing-sentinel`](model-customization.md#use-missing-sentinel) - Use Pydantic's MISSING sentinel for optional fields without ...
386388
- [`--use-non-positive-negative-number-constrained-types`](typing-customization.md#use-non-positive-negative-number-constrained-types) - Use NonPositive/NonNegative types for number constraints.
387389
- [`--use-object-type`](typing-customization.md#use-object-type) - Use object instead of Any for unspecified object and array v...
388390
- [`--use-one-literal-as-default`](model-customization.md#use-one-literal-as-default) - Use single literal value as default when enum has only one o...

docs/llms-full.txt

Lines changed: 97 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,7 +1118,7 @@ This documentation is auto-generated from test cases.
11181118
| 📁 [Base Options](base-options.md) | 12 | Input/output configuration |
11191119
| 🔧 [Typing Customization](typing-customization.md) | 30 | Type annotation and import behavior |
11201120
| 🏷️ [Field Customization](field-customization.md) | 27 | Field naming and docstring behavior |
1121-
| 🏗️ [Model Customization](model-customization.md) | 43 | Model generation behavior |
1121+
| 🏗️ [Model Customization](model-customization.md) | 44 | Model generation behavior |
11221122
| 🎨 [Template Customization](template-customization.md) | 25 | Output formatting and custom rendering |
11231123
| 📘 [OpenAPI-only Options](openapi-only-options.md) | 8 | OpenAPI-specific features |
11241124
| 📋 [GraphQL-only Options](graphql-only-options.md) | 1 | |
@@ -1332,6 +1332,7 @@ This documentation is auto-generated from test cases.
13321332
- [`--use-generic-base-class`](model-customization.md#use-generic-base-class)
13331333
- [`--use-generic-container-types`](typing-customization.md#use-generic-container-types)
13341334
- [`--use-inline-field-description`](field-customization.md#use-inline-field-description)
1335+
- [`--use-missing-sentinel`](model-customization.md#use-missing-sentinel)
13351336
- [`--use-non-positive-negative-number-constrained-types`](typing-customization.md#use-non-positive-negative-number-constrained-types)
13361337
- [`--use-object-type`](typing-customization.md#use-object-type)
13371338
- [`--use-one-literal-as-default`](model-customization.md#use-one-literal-as-default)
@@ -2382,6 +2383,7 @@ Source: https://datamodel-code-generator.koxudaxi.dev/cli-reference/model-custom
23822383
| [`--use-default-kwarg`](#use-default-kwarg) | Use default= keyword argument instead of positional argument... |
23832384
| [`--use-frozen-field`](#use-frozen-field) | Generate frozen (immutable) field definitions for readOnly p... |
23842385
| [`--use-generic-base-class`](#use-generic-base-class) | Generate a shared base class with model configuration to avo... |
2386+
| [`--use-missing-sentinel`](#use-missing-sentinel) | Use Pydantic's MISSING sentinel for optional fields without ... |
23852387
| [`--use-one-literal-as-default`](#use-one-literal-as-default) | Use single literal value as default when enum has only one o... |
23862388
| [`--use-root-model-sequence-interface`](#use-root-model-sequence-interface) | Make non-null sequence-like Pydantic v2 RootModel classes im... |
23872389
| [`--use-serialize-as-any`](#use-serialize-as-any) | Wrap fields with subtypes in Pydantic's SerializeAsAny. |
@@ -7585,6 +7587,7 @@ The `--target-pydantic-version` flag controls Pydantic version-specific config:
75857587

75867588
- **2**: Uses `populate_by_name=True` (compatible with Pydantic 2.0-2.10)
75877589
- **2.11**: Uses `validate_by_name=True` (for Pydantic 2.11+)
7590+
- **2.12**: Uses `validate_by_name=True` and allows features that require Pydantic 2.12+
75887591

75897592
This prevents breaking changes when generated code is used on older Pydantic versions.
75907593

@@ -8342,6 +8345,87 @@ Generate a shared base class with model configuration to avoid repetition (DRY).
83428345

83438346
---
83448347

8348+
## `--use-missing-sentinel` {#use-missing-sentinel}
8349+
8350+
Use Pydantic's MISSING sentinel for optional fields without defaults.
8351+
8352+
The `--use-missing-sentinel` flag generates `MISSING` as the default for optional
8353+
Pydantic v2 fields that do not define a schema default. This preserves the
8354+
difference between an omitted field and a nullable field set to `None`.
8355+
8356+
**Related:** [`--strict-nullable`](model-customization.md#strict-nullable), [`--target-pydantic-version`](model-customization.md#target-pydantic-version)
8357+
8358+
!!! tip "Usage"
8359+
8360+
```bash
8361+
datamodel-codegen --input schema.json --output-model-type pydantic_v2.BaseModel --use-missing-sentinel # (1)!
8362+
```
8363+
8364+
1. :material-arrow-left: `--use-missing-sentinel` - the option documented here
8365+
8366+
??? example "Examples"
8367+
8368+
**Input Schema:**
8369+
8370+
```json
8371+
{
8372+
"$schema": "http://json-schema.org/draft-07/schema#",
8373+
"title": "MissingSentinel",
8374+
"type": "object",
8375+
"required": ["required", "requiredNullable"],
8376+
"properties": {
8377+
"required": {
8378+
"type": "integer"
8379+
},
8380+
"requiredNullable": {
8381+
"type": ["integer", "null"]
8382+
},
8383+
"unrequired": {
8384+
"type": "integer"
8385+
},
8386+
"nullableUnrequired": {
8387+
"type": ["integer", "null"]
8388+
},
8389+
"withDefault": {
8390+
"type": "integer",
8391+
"default": 1
8392+
},
8393+
"nullDefault": {
8394+
"type": ["integer", "null"],
8395+
"default": null
8396+
},
8397+
"aliased-field": {
8398+
"type": "string"
8399+
}
8400+
}
8401+
}
8402+
```
8403+
8404+
**Output:**
8405+
8406+
```python
8407+
# generated by datamodel-codegen:
8408+
# filename: missing_sentinel.json
8409+
# timestamp: 2019-07-26T00:00:00+00:00
8410+
8411+
from __future__ import annotations
8412+
8413+
from pydantic import BaseModel, Field
8414+
from pydantic.experimental.missing_sentinel import MISSING
8415+
8416+
8417+
class MissingSentinel(BaseModel):
8418+
required: int
8419+
requiredNullable: int | None
8420+
unrequired: int | MISSING = MISSING
8421+
nullableUnrequired: int | None | MISSING = MISSING
8422+
withDefault: int | None = 1
8423+
nullDefault: int | None = None
8424+
aliased_field: str | MISSING = Field(MISSING, alias='aliased-field')
8425+
```
8426+
8427+
---
8428+
83458429
## `--use-one-literal-as-default` {#use-one-literal-as-default}
83468430

83478431
Use single literal value as default when enum has only one option.
@@ -27967,6 +28051,7 @@ datamodel-codegen [OPTIONS]
2796728051
| [`--use-default-kwarg`](model-customization.md#use-default-kwarg) | Use default= keyword argument instead of positional argument for fields with def... |
2796828052
| [`--use-frozen-field`](model-customization.md#use-frozen-field) | Generate frozen (immutable) field definitions for readOnly properties. |
2796928053
| [`--use-generic-base-class`](model-customization.md#use-generic-base-class) | Generate a shared base class with model configuration to avoid repetition (DRY). |
28054+
| [`--use-missing-sentinel`](model-customization.md#use-missing-sentinel) | Use Pydantic's MISSING sentinel for optional fields without defaults. |
2797028055
| [`--use-one-literal-as-default`](model-customization.md#use-one-literal-as-default) | Use single literal value as default when enum has only one option. |
2797128056
| [`--use-root-model-sequence-interface`](model-customization.md#use-root-model-sequence-interface) | Make non-null sequence-like Pydantic v2 RootModel classes implement collections.... |
2797228057
| [`--use-serialize-as-any`](model-customization.md#use-serialize-as-any) | Wrap fields with subtypes in Pydantic's SerializeAsAny. |
@@ -28211,6 +28296,7 @@ All options sorted alphabetically:
2821128296
- [`--use-generic-base-class`](model-customization.md#use-generic-base-class) - Generate a shared base class with model configuration to avo...
2821228297
- [`--use-generic-container-types`](typing-customization.md#use-generic-container-types) - Use generic container types (Sequence, Mapping) for type hin...
2821328298
- [`--use-inline-field-description`](field-customization.md#use-inline-field-description) - Add field descriptions as inline comments.
28299+
- [`--use-missing-sentinel`](model-customization.md#use-missing-sentinel) - Use Pydantic's MISSING sentinel for optional fields without ...
2821428300
- [`--use-non-positive-negative-number-constrained-types`](typing-customization.md#use-non-positive-negative-number-constrained-types) - Use NonPositive/NonNegative types for number constraints.
2821528301
- [`--use-object-type`](typing-customization.md#use-object-type) - Use object instead of Any for unspecified object and array v...
2821628302
- [`--use-one-literal-as-default`](model-customization.md#use-one-literal-as-default) - Use single literal value as default when enum has only one o...
@@ -36680,16 +36766,16 @@ classDiagram
3668036766

3668136767
| Config model | Field count | Purpose |
3668236768
| --- | ---: | --- |
36683-
| `BaseGenerateConfig` | 138 | Shared generation options. |
36684-
| `GenerateConfig` | 153 | Public `generate()` configuration. |
36685-
| `ParserConfig` | 134 | Base parser dependency injection and parser options. |
36686-
| `JSONSchemaParserConfig` | 136 | JSON Schema parser options. |
36687-
| `OpenAPIParserConfig` | 142 | OpenAPI-specific parser options. |
36688-
| `AsyncAPIParserConfig` | 143 | AsyncAPI-specific parser options. |
36689-
| `XMLSchemaParserConfig` | 137 | XML Schema-specific parser options. |
36690-
| `ProtobufParserConfig` | 137 | Protocol Buffers-specific parser options. |
36691-
| `AvroParserConfig` | 136 | Avro-specific parser options. |
36692-
| `GraphQLParserConfig` | 137 | GraphQL-specific parser options. |
36769+
| `BaseGenerateConfig` | 139 | Shared generation options. |
36770+
| `GenerateConfig` | 154 | Public `generate()` configuration. |
36771+
| `ParserConfig` | 135 | Base parser dependency injection and parser options. |
36772+
| `JSONSchemaParserConfig` | 137 | JSON Schema parser options. |
36773+
| `OpenAPIParserConfig` | 143 | OpenAPI-specific parser options. |
36774+
| `AsyncAPIParserConfig` | 144 | AsyncAPI-specific parser options. |
36775+
| `XMLSchemaParserConfig` | 138 | XML Schema-specific parser options. |
36776+
| `ProtobufParserConfig` | 138 | Protocol Buffers-specific parser options. |
36777+
| `AvroParserConfig` | 137 | Avro-specific parser options. |
36778+
| `GraphQLParserConfig` | 138 | GraphQL-specific parser options. |
3669336779

3669436780
### Formatter Names
3669536781

0 commit comments

Comments
 (0)