Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/datamodel_code_generator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@
GraphQLScope,
InputFileType,
InputModelRefStrategy,
JsonSchemaVersion,
ModuleSplitMode,
NamingStrategy,
OpenAPIScope,
OpenAPIVersion,
ReadOnlyWriteOnlyModelType,
ReuseScope,
TargetPydanticVersion,
Expand Down Expand Up @@ -966,10 +968,12 @@ def __getattr__(name: str) -> Any:
"InputModelRefStrategy",
"InvalidClassNameError",
"InvalidFileFormatError",
"JsonSchemaVersion",
"LiteralType",
"ModuleSplitMode",
"NamingStrategy",
"OpenAPIScope",
"OpenAPIVersion",
"PythonVersion",
"PythonVersionMin",
"ReadOnlyWriteOnlyModelType",
Expand Down
6 changes: 6 additions & 0 deletions src/datamodel_code_generator/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@
InputFileType,
InputModelRefStrategy,
InvalidClassNameError,
JsonSchemaVersion,
ModuleSplitMode,
NamingStrategy,
OpenAPIScope,
OpenAPIVersion,
ReadOnlyWriteOnlyModelType,
ReuseScope,
TargetPydanticVersion,
Expand Down Expand Up @@ -491,6 +493,8 @@ def validate_class_name_affix_scope(cls, v: str | ClassNameAffixScope | None) ->
input_model: Optional[list[str]] = None # noqa: UP045
input_model_ref_strategy: Optional[InputModelRefStrategy] = None # noqa: UP045
input_file_type: InputFileType = InputFileType.Auto
jsonschema_version: JsonSchemaVersion = JsonSchemaVersion.Auto
openapi_version: OpenAPIVersion = OpenAPIVersion.Auto
output_model_type: DataModelType = DataModelType.PydanticBaseModel
output: Optional[Path] = None # noqa: UP045
check: bool = False
Expand Down Expand Up @@ -938,6 +942,8 @@ def run_generate_from_config( # noqa: PLR0913, PLR0917
result = generate(
input_=input_,
input_file_type=config.input_file_type,
jsonschema_version=config.jsonschema_version,
openapi_version=config.openapi_version,
output=output,
output_model_type=config.output_model_type,
target_python_version=config.target_python_version,
Expand Down
4 changes: 4 additions & 0 deletions src/datamodel_code_generator/_types/generate_config_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
FieldTypeCollisionStrategy,
GraphQLScope,
InputFileType,
JsonSchemaVersion,
ModuleSplitMode,
NamingStrategy,
OpenAPIScope,
OpenAPIVersion,
ReadOnlyWriteOnlyModelType,
ReuseScope,
StrictTypes,
Expand All @@ -41,6 +43,8 @@
class GenerateConfigDict(TypedDict):
input_filename: NotRequired[str | None]
input_file_type: NotRequired[InputFileType]
jsonschema_version: NotRequired[JsonSchemaVersion]
openapi_version: NotRequired[OpenAPIVersion]
output: NotRequired[Path | None]
output_model_type: NotRequired[DataModelType]
target_python_version: NotRequired[PythonVersion]
Expand Down
4 changes: 4 additions & 0 deletions src/datamodel_code_generator/_types/parser_config_dicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
CollapseRootModelsNameStrategy,
DataclassArguments,
FieldTypeCollisionStrategy,
JsonSchemaVersion,
NamingStrategy,
OpenAPIScope,
OpenAPIVersion,
ReadOnlyWriteOnlyModelType,
ReuseScope,
StrictTypes,
Expand All @@ -45,6 +47,8 @@ class ParserConfigDict(TypedDict):
data_model_root_type: NotRequired[type[DataModel]]
data_type_manager_type: NotRequired[type[DataTypeManager]]
data_model_field_type: NotRequired[type[DataModelFieldBase]]
jsonschema_version: NotRequired[JsonSchemaVersion]
openapi_version: NotRequired[OpenAPIVersion]
base_class: NotRequired[str | None]
base_class_map: NotRequired[dict[str, str] | None]
additional_imports: NotRequired[list[str] | None]
Expand Down
20 changes: 20 additions & 0 deletions src/datamodel_code_generator/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
FieldTypeCollisionStrategy,
InputFileType,
InputModelRefStrategy,
JsonSchemaVersion,
ModuleSplitMode,
NamingStrategy,
OpenAPIScope,
OpenAPIVersion,
ReadOnlyWriteOnlyModelType,
ReuseScope,
StrictTypes,
Expand Down Expand Up @@ -146,6 +148,24 @@ def start_section(self, heading: str | None) -> None:
),
choices=[i.value for i in InputFileType],
)
base_options.add_argument(
"--jsonschema-version",
help=(
"JSON Schema version (default: auto). "
"When 'auto', version is detected from $schema field. "
"Specify explicitly to override detection or when $schema is missing."
),
choices=[v.value for v in JsonSchemaVersion],
)
base_options.add_argument(
"--openapi-version",
help=(
"OpenAPI version (default: auto). "
"When 'auto', version is detected from openapi/swagger field. "
"Specify explicitly to override detection."
),
choices=[v.value for v in OpenAPIVersion],
)
base_options.add_argument(
"--output",
help="Output file (default: stdout)",
Expand Down
3 changes: 3 additions & 0 deletions src/datamodel_code_generator/cli_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ class CLIOptionMeta:
"--profile",
"--no-color",
"--generate-prompt",
# Schema version options - placeholders for future version-specific behavior
"--jsonschema-version",
"--openapi-version",
})

# Backward compatibility alias
Expand Down
6 changes: 6 additions & 0 deletions src/datamodel_code_generator/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
FieldTypeCollisionStrategy,
GraphQLScope,
InputFileType,
JsonSchemaVersion,
ModuleSplitMode,
NamingStrategy,
OpenAPIScope,
OpenAPIVersion,
ReadOnlyWriteOnlyModelType,
ReuseScope,
TargetPydanticVersion,
Expand Down Expand Up @@ -78,6 +80,8 @@ class Config:

input_filename: str | None = None
input_file_type: InputFileType = InputFileType.Auto
jsonschema_version: JsonSchemaVersion = JsonSchemaVersion.Auto
openapi_version: OpenAPIVersion = OpenAPIVersion.Auto
output: Path | None = None
output_model_type: DataModelType = DataModelType.PydanticBaseModel
target_python_version: PythonVersion = PythonVersionMin
Expand Down Expand Up @@ -224,6 +228,8 @@ class Config:
data_model_root_type: type[DataModel] = pydantic_model.CustomRootType
data_type_manager_type: type[DataTypeManager] = pydantic_model.DataTypeManager
data_model_field_type: type[DataModelFieldBase] = pydantic_model.DataModelField
jsonschema_version: JsonSchemaVersion = JsonSchemaVersion.Auto
openapi_version: OpenAPIVersion = OpenAPIVersion.Auto
base_class: str | None = None
base_class_map: dict[str, str] | None = None
additional_imports: list[str] | None = None
Expand Down
29 changes: 29 additions & 0 deletions src/datamodel_code_generator/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,33 @@ class StrictTypes(Enum):
bool = "bool"


class JsonSchemaVersion(Enum):
"""JSON Schema draft versions.

Used to specify which JSON Schema draft to use for parsing and validation.
Different drafts have different features and semantics.
"""

Auto = "auto"
Draft04 = "draft-04"
Draft07 = "draft-07"
Draft201909 = "2019-09"
Draft202012 = "2020-12"


class OpenAPIVersion(Enum):
"""OpenAPI specification versions.

Used to specify which OpenAPI version to use for parsing.
Different versions have different schema semantics (e.g., nullable handling).
"""

Auto = "auto"
V20 = "2.0"
V30 = "3.0"
V31 = "3.1"


__all__ = [
"DEFAULT_SHARED_MODULE_NAME",
"MAX_VERSION",
Expand All @@ -256,9 +283,11 @@ class StrictTypes(Enum):
"GraphQLScope",
"InputFileType",
"InputModelRefStrategy",
"JsonSchemaVersion",
"ModuleSplitMode",
"NamingStrategy",
"OpenAPIScope",
"OpenAPIVersion",
"ReadOnlyWriteOnlyModelType",
"ReuseScope",
"StrictTypes",
Expand Down
2 changes: 2 additions & 0 deletions src/datamodel_code_generator/parser/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,8 @@ def __init__( # noqa: PLR0912, PLR0915
self.use_subclass_enum: bool = config.use_subclass_enum
self.use_specialized_enum: bool = config.use_specialized_enum
self.strict_nullable: bool = config.strict_nullable
self.jsonschema_version = config.jsonschema_version
self.openapi_version = config.openapi_version
self.use_generic_container_types: bool = config.use_generic_container_types
self.use_union_operator: bool = config.use_union_operator
self.enable_faux_immutability: bool = config.enable_faux_immutability
Expand Down
1 change: 1 addition & 0 deletions src/datamodel_code_generator/parser/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ def get_data_type(self, obj: JsonSchemaObject) -> DataType:
# https://swagger.io/docs/specification/data-models/data-types/#null
# OpenAPI 3.1 does allow `null` in the `type` field and is equivalent to
# a `nullable` flag on the property itself
# For backward compatibility, process nullable the same way for all versions
if obj.nullable and self.strict_nullable and isinstance(obj.type, str):
obj.type = [obj.type, "null"]

Expand Down
10 changes: 10 additions & 0 deletions tests/data/expected/main/input_model/config_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ class DataclassArguments(TypedDict):
]


JsonSchemaVersion: TypeAlias = Literal[
'auto', 'draft-04', 'draft-07', '2019-09', '2020-12'
]


LiteralType: TypeAlias = Literal['all', 'one', 'none']


Expand All @@ -93,6 +98,9 @@ class DataclassArguments(TypedDict):
]


OpenAPIVersion: TypeAlias = Literal['auto', '2.0', '3.0', '3.1']


PythonVersion: TypeAlias = Literal['3.10', '3.11', '3.12', '3.13', '3.14']


Expand All @@ -117,6 +125,8 @@ class DataclassArguments(TypedDict):
class GenerateConfig(TypedDict):
input_filename: NotRequired[str | None]
input_file_type: NotRequired[InputFileType]
jsonschema_version: NotRequired[JsonSchemaVersion]
openapi_version: NotRequired[OpenAPIVersion]
output: NotRequired[str | None]
output_model_type: NotRequired[DataModelType]
target_python_version: NotRequired[PythonVersion]
Expand Down
12 changes: 12 additions & 0 deletions tests/data/expected/main/openapi/openapi_version_nullable_v31.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# generated by datamodel-codegen:
# filename: openapi_version_nullable.yaml
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from pydantic import BaseModel


class Pet(BaseModel):
name: str | None = None
age: int | None = None
15 changes: 15 additions & 0 deletions tests/data/openapi/openapi_version_nullable.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
openapi: "3.1.0"
info:
title: Test API
version: "1.0.0"
paths: {}
components:
schemas:
Pet:
type: object
properties:
name:
type: string
nullable: true
age:
type: integer
19 changes: 19 additions & 0 deletions tests/main/openapi/test_main_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -4869,3 +4869,22 @@ def test_main_openapi_deprecated_field(output_file: Path) -> None:
expected_file="deprecated_field.py",
extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
)


@SKIP_PYDANTIC_V1
def test_main_openapi_version_nullable_v31(output_file: Path) -> None:
"""Test that nullable is processed correctly in OpenAPI 3.1 mode with --openapi-version."""
run_main_and_assert(
input_path=OPEN_API_DATA_PATH / "openapi_version_nullable.yaml",
output_path=output_file,
input_file_type="openapi",
assert_func=assert_file_content,
expected_file="openapi_version_nullable_v31.py",
extra_args=[
"--output-model-type",
"pydantic_v2.BaseModel",
"--strict-nullable",
"--openapi-version",
"3.1",
],
)
6 changes: 6 additions & 0 deletions tests/main/test_public_api_signature_baseline.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
FieldTypeCollisionStrategy,
GraphQLScope,
InputFileType,
JsonSchemaVersion,
ModuleSplitMode,
NamingStrategy,
OpenAPIScope,
OpenAPIVersion,
ReadOnlyWriteOnlyModelType,
ReuseScope,
TargetPydanticVersion,
Expand Down Expand Up @@ -60,6 +62,8 @@ def _baseline_generate(
config: GenerateConfig | None = None,
input_filename: str | None = None,
input_file_type: InputFileType = InputFileType.Auto,
jsonschema_version: JsonSchemaVersion = JsonSchemaVersion.Auto,
openapi_version: OpenAPIVersion = OpenAPIVersion.Auto,
output: Path | None = None,
output_model_type: DataModelType = DataModelType.PydanticBaseModel,
target_python_version: PythonVersion = PythonVersionMin,
Expand Down Expand Up @@ -200,6 +204,8 @@ def __init__(
data_model_root_type: type[DataModel] = pydantic_model.CustomRootType,
data_type_manager_type: type[DataTypeManager] = pydantic_model.DataTypeManager,
data_model_field_type: type[DataModelFieldBase] = pydantic_model.DataModelField,
jsonschema_version: JsonSchemaVersion = JsonSchemaVersion.Auto,
openapi_version: OpenAPIVersion = OpenAPIVersion.Auto,
base_class: str | None = None,
base_class_map: dict[str, str] | None = None,
additional_imports: list[str] | None = None,
Expand Down
Loading