diff --git a/docs/cli-reference/index.md b/docs/cli-reference/index.md index 308231f36..1b983d30b 100644 --- a/docs/cli-reference/index.md +++ b/docs/cli-reference/index.md @@ -9,7 +9,7 @@ This documentation is auto-generated from test cases. | Category | Options | Description | |----------|---------|-------------| | 📁 [Base Options](base-options.md) | 5 | Input/output configuration | -| 🔧 [Typing Customization](typing-customization.md) | 17 | Type annotation and import behavior | +| 🔧 [Typing Customization](typing-customization.md) | 18 | Type annotation and import behavior | | 🏷️ [Field Customization](field-customization.md) | 21 | Field naming and docstring behavior | | 🏗️ [Model Customization](model-customization.md) | 29 | Model generation behavior | | 🎨 [Template Customization](template-customization.md) | 16 | Output formatting and custom rendering | @@ -178,6 +178,7 @@ This documentation is auto-generated from test cases. - [`--use-pendulum`](typing-customization.md#use-pendulum) - [`--use-schema-description`](field-customization.md#use-schema-description) - [`--use-serialize-as-any`](model-customization.md#use-serialize-as-any) +- [`--use-standard-primitive-types`](typing-customization.md#use-standard-primitive-types) - [`--use-status-code-in-response-name`](openapi-only-options.md#use-status-code-in-response-name) - [`--use-subclass-enum`](model-customization.md#use-subclass-enum) - [`--use-title-as-name`](field-customization.md#use-title-as-name) diff --git a/docs/cli-reference/quick-reference.md b/docs/cli-reference/quick-reference.md index b558408ac..f13c6e847 100644 --- a/docs/cli-reference/quick-reference.md +++ b/docs/cli-reference/quick-reference.md @@ -41,6 +41,7 @@ datamodel-codegen [OPTIONS] | [`--use-generic-container-types`](typing-customization.md#use-generic-container-types) | Use typing.Dict/List instead of dict/list for container types. | | [`--use-non-positive-negative-number-constrained-types`](typing-customization.md#use-non-positive-negative-number-constrained-types) | Use NonPositive/NonNegative types for number constraints. | | [`--use-pendulum`](typing-customization.md#use-pendulum) | Use pendulum types for date/time fields instead of datetime module. | +| [`--use-standard-primitive-types`](typing-customization.md#use-standard-primitive-types) | Use Python standard library types for string formats instead of str. | | [`--use-type-alias`](typing-customization.md#use-type-alias) | Use TypeAlias instead of root models for type definitions (experimental). | | [`--use-unique-items-as-set`](typing-customization.md#use-unique-items-as-set) | Generate set types for arrays with uniqueItems constraint. | @@ -274,6 +275,7 @@ All options sorted alphabetically: - [`--use-pendulum`](typing-customization.md#use-pendulum) - Use pendulum types for date/time fields instead of datetime ... - [`--use-schema-description`](field-customization.md#use-schema-description) - Use schema description as class docstring. - [`--use-serialize-as-any`](model-customization.md#use-serialize-as-any) - Wrap fields with subtypes in Pydantic's SerializeAsAny. +- [`--use-standard-primitive-types`](typing-customization.md#use-standard-primitive-types) - Use Python standard library types for string formats instead... - [`--use-status-code-in-response-name`](openapi-only-options.md#use-status-code-in-response-name) - Include HTTP status code in response model names. - [`--use-subclass-enum`](model-customization.md#use-subclass-enum) - Generate typed Enum subclasses for enums with specific field... - [`--use-title-as-name`](field-customization.md#use-title-as-name) - Use schema title as the generated class name. diff --git a/docs/cli-reference/typing-customization.md b/docs/cli-reference/typing-customization.md index 932613abd..7e2f57d35 100644 --- a/docs/cli-reference/typing-customization.md +++ b/docs/cli-reference/typing-customization.md @@ -19,6 +19,7 @@ | [`--use-generic-container-types`](#use-generic-container-types) | Use typing.Dict/List instead of dict/list for container type... | | [`--use-non-positive-negative-number-constrained-types`](#use-non-positive-negative-number-constrained-types) | Use NonPositive/NonNegative types for number constraints. | | [`--use-pendulum`](#use-pendulum) | Use pendulum types for date/time fields instead of datetime ... | +| [`--use-standard-primitive-types`](#use-standard-primitive-types) | Use Python standard library types for string formats instead... | | [`--use-type-alias`](#use-type-alias) | Use TypeAlias instead of root models for type definitions (e... | | [`--use-unique-items-as-set`](#use-unique-items-as-set) | Generate set types for arrays with uniqueItems constraint. | @@ -2955,6 +2956,73 @@ working with the pendulum library for enhanced timezone and date handling. --- +## `--use-standard-primitive-types` {#use-standard-primitive-types} + +Use Python standard library types for string formats instead of str. + +The `--use-standard-primitive-types` flag configures the code generation to use +Python standard library types (UUID, IPv4Address, IPv6Address, Path) for corresponding +string formats instead of plain str. This affects dataclass, msgspec, and TypedDict +output types. Pydantic already uses these types by default. + +**Related:** [`--output-datetime-class`](typing-customization.md#output-datetime-class), [`--output-model-type`](model-customization.md#output-model-type) + +!!! tip "Usage" + + ```bash + datamodel-codegen --input schema.json --output-model-type dataclasses.dataclass --use-standard-primitive-types # (1)! + ``` + + 1. :material-arrow-left: `--use-standard-primitive-types` - the option documented here + +??? example "Examples" + + **Input Schema:** + + ```json + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "ip_address": { + "type": "string", + "format": "ipv4" + }, + "config_path": { + "type": "string", + "format": "path" + } + } + } + ``` + + **Output:** + + ```python + # generated by datamodel-codegen: + # filename: use_standard_primitive_types.json + + from __future__ import annotations + + from dataclasses import dataclass + from ipaddress import IPv4Address + from pathlib import Path + from uuid import UUID + + + @dataclass + class Model: + id: UUID | None = None + ip_address: IPv4Address | None = None + config_path: Path | None = None + ``` + +--- + ## `--use-type-alias` {#use-type-alias} Use TypeAlias instead of root models for type definitions (experimental). diff --git a/src/datamodel_code_generator/__init__.py b/src/datamodel_code_generator/__init__.py index 168e8ad89..b1c2a6e80 100644 --- a/src/datamodel_code_generator/__init__.py +++ b/src/datamodel_code_generator/__init__.py @@ -475,6 +475,7 @@ def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915 custom_formatters: list[str] | None = None, custom_formatters_kwargs: dict[str, Any] | None = None, use_pendulum: bool = False, + use_standard_primitive_types: bool = False, http_query_parameters: Sequence[tuple[str, str]] | None = None, treat_dot_as_module: bool | None = None, use_exact_imports: bool = False, @@ -726,6 +727,7 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]: custom_formatters=custom_formatters, custom_formatters_kwargs=custom_formatters_kwargs, use_pendulum=use_pendulum, + use_standard_primitive_types=use_standard_primitive_types, http_query_parameters=http_query_parameters, treat_dot_as_module=treat_dot_as_module, use_exact_imports=use_exact_imports, diff --git a/src/datamodel_code_generator/__main__.py b/src/datamodel_code_generator/__main__.py index 0864e9c78..2c226c9db 100644 --- a/src/datamodel_code_generator/__main__.py +++ b/src/datamodel_code_generator/__main__.py @@ -452,6 +452,7 @@ def validate_all_exports_collision_strategy(cls, values: dict[str, Any]) -> dict custom_formatters: Optional[list[str]] = None # noqa: UP045 custom_formatters_kwargs: Optional[TextIOBase] = None # noqa: UP045 use_pendulum: bool = False + use_standard_primitive_types: bool = False http_query_parameters: Optional[Sequence[tuple[str, str]]] = None # noqa: UP045 treat_dot_as_module: Optional[bool] = None # noqa: UP045 use_exact_imports: bool = False @@ -759,6 +760,7 @@ def run_generate_from_config( # noqa: PLR0913, PLR0917 custom_formatters=config.custom_formatters, custom_formatters_kwargs=custom_formatters_kwargs, use_pendulum=config.use_pendulum, + use_standard_primitive_types=config.use_standard_primitive_types, http_query_parameters=config.http_query_parameters, treat_dot_as_module=config.treat_dot_as_module, use_exact_imports=config.use_exact_imports, diff --git a/src/datamodel_code_generator/arguments.py b/src/datamodel_code_generator/arguments.py index 718ff4e69..e4a5c913e 100644 --- a/src/datamodel_code_generator/arguments.py +++ b/src/datamodel_code_generator/arguments.py @@ -303,6 +303,14 @@ def start_section(self, heading: str | None) -> None: action="store_true", default=None, ) +model_options.add_argument( + "--use-standard-primitive-types", + help="Use Python standard library types for string formats (UUID, IPv4Address, etc.) " + "instead of str. Affects dataclass, msgspec, TypedDict output. " + "Pydantic already uses these types by default.", + action="store_true", + default=None, +) model_options.add_argument( "--use-exact-imports", help='import exact types instead of modules, for example: "from .foo import Bar" instead of ' diff --git a/src/datamodel_code_generator/cli_options.py b/src/datamodel_code_generator/cli_options.py index cd608dcd6..7d4c6459c 100644 --- a/src/datamodel_code_generator/cli_options.py +++ b/src/datamodel_code_generator/cli_options.py @@ -155,6 +155,9 @@ class CLIOptionMeta: "--ignore-enum-constraints": CLIOptionMeta(name="--ignore-enum-constraints", category=OptionCategory.TYPING), "--disable-future-imports": CLIOptionMeta(name="--disable-future-imports", category=OptionCategory.TYPING), "--use-pendulum": CLIOptionMeta(name="--use-pendulum", category=OptionCategory.TYPING), + "--use-standard-primitive-types": CLIOptionMeta( + name="--use-standard-primitive-types", category=OptionCategory.TYPING + ), "--output-datetime-class": CLIOptionMeta(name="--output-datetime-class", category=OptionCategory.TYPING), "--use-decimal-for-multiple-of": CLIOptionMeta( name="--use-decimal-for-multiple-of", category=OptionCategory.TYPING diff --git a/src/datamodel_code_generator/imports.py b/src/datamodel_code_generator/imports.py index 593f17dd8..93cd6a964 100644 --- a/src/datamodel_code_generator/imports.py +++ b/src/datamodel_code_generator/imports.py @@ -224,6 +224,10 @@ def remove_unused(self, used_names: set[str]) -> None: IMPORT_PATH = Import.from_full_path("pathlib.Path") IMPORT_TIME = Import.from_full_path("datetime.time") IMPORT_UUID = Import.from_full_path("uuid.UUID") +IMPORT_IPV4ADDRESS = Import.from_full_path("ipaddress.IPv4Address") +IMPORT_IPV6ADDRESS = Import.from_full_path("ipaddress.IPv6Address") +IMPORT_IPV4NETWORK = Import.from_full_path("ipaddress.IPv4Network") +IMPORT_IPV6NETWORK = Import.from_full_path("ipaddress.IPv6Network") IMPORT_PENDULUM_DATE = Import.from_full_path("pendulum.Date") IMPORT_PENDULUM_DATETIME = Import.from_full_path("pendulum.DateTime") IMPORT_PENDULUM_DURATION = Import.from_full_path("pendulum.Duration") diff --git a/src/datamodel_code_generator/model/dataclass.py b/src/datamodel_code_generator/model/dataclass.py index 836313c2e..ccd05d4aa 100644 --- a/src/datamodel_code_generator/model/dataclass.py +++ b/src/datamodel_code_generator/model/dataclass.py @@ -20,7 +20,7 @@ from datamodel_code_generator.model.imports import IMPORT_DATACLASS, IMPORT_FIELD from datamodel_code_generator.model.pydantic.base_model import Constraints # noqa: TC001 # needed for pydantic from datamodel_code_generator.model.types import DataTypeManager as _DataTypeManager -from datamodel_code_generator.model.types import type_map_factory +from datamodel_code_generator.model.types import standard_primitive_type_map_factory, type_map_factory from datamodel_code_generator.reference import Reference from datamodel_code_generator.types import DataType, StrictTypes, Types, chain_as_tuple @@ -217,6 +217,7 @@ def __init__( # noqa: PLR0913, PLR0917 use_decimal_for_multiple_of: bool = False, # noqa: FBT001, FBT002 use_union_operator: bool = False, # noqa: FBT001, FBT002 use_pendulum: bool = False, # noqa: FBT001, FBT002 + use_standard_primitive_types: bool = False, # noqa: FBT001, FBT002 target_datetime_class: DatetimeClassType = DatetimeClassType.Datetime, treat_dot_as_module: bool | None = None, # noqa: FBT001 use_serialize_as_any: bool = False, # noqa: FBT001, FBT002 @@ -231,6 +232,7 @@ def __init__( # noqa: PLR0913, PLR0917 use_decimal_for_multiple_of, use_union_operator, use_pendulum, + use_standard_primitive_types, target_datetime_class, treat_dot_as_module, use_serialize_as_any, @@ -247,7 +249,12 @@ def __init__( # noqa: PLR0913, PLR0917 else {} ) + standard_primitive_map = ( + standard_primitive_type_map_factory(self.data_type) if use_standard_primitive_types else {} + ) + self.type_map: dict[Types, DataType] = { **type_map_factory(self.data_type), **datetime_map, + **standard_primitive_map, } diff --git a/src/datamodel_code_generator/model/msgspec.py b/src/datamodel_code_generator/model/msgspec.py index 47f01a4ad..b8d44fe44 100644 --- a/src/datamodel_code_generator/model/msgspec.py +++ b/src/datamodel_code_generator/model/msgspec.py @@ -34,7 +34,7 @@ ) from datamodel_code_generator.model.type_alias import TypeAliasBase from datamodel_code_generator.model.types import DataTypeManager as _DataTypeManager -from datamodel_code_generator.model.types import type_map_factory +from datamodel_code_generator.model.types import standard_primitive_type_map_factory, type_map_factory from datamodel_code_generator.types import ( NONE, OPTIONAL_PREFIX, @@ -503,6 +503,7 @@ def __init__( # noqa: PLR0913, PLR0917 use_decimal_for_multiple_of: bool = False, # noqa: FBT001, FBT002 use_union_operator: bool = False, # noqa: FBT001, FBT002 use_pendulum: bool = False, # noqa: FBT001, FBT002 + use_standard_primitive_types: bool = False, # noqa: FBT001, FBT002 target_datetime_class: DatetimeClassType | None = None, treat_dot_as_module: bool | None = None, # noqa: FBT001 use_serialize_as_any: bool = False, # noqa: FBT001, FBT002 @@ -517,6 +518,7 @@ def __init__( # noqa: PLR0913, PLR0917 use_decimal_for_multiple_of, use_union_operator, use_pendulum, + use_standard_primitive_types, target_datetime_class, treat_dot_as_module, use_serialize_as_any, @@ -533,7 +535,12 @@ def __init__( # noqa: PLR0913, PLR0917 else {} ) + standard_primitive_map = ( + standard_primitive_type_map_factory(self.data_type) if use_standard_primitive_types else {} + ) + self.type_map: dict[Types, DataType] = { **type_map_factory(self.data_type), **datetime_map, + **standard_primitive_map, } diff --git a/src/datamodel_code_generator/model/pydantic/types.py b/src/datamodel_code_generator/model/pydantic/types.py index c2c1edb02..6cc5a4037 100644 --- a/src/datamodel_code_generator/model/pydantic/types.py +++ b/src/datamodel_code_generator/model/pydantic/types.py @@ -180,23 +180,24 @@ def __init__( # noqa: PLR0913, PLR0917 use_decimal_for_multiple_of: bool = False, # noqa: FBT001, FBT002 use_union_operator: bool = False, # noqa: FBT001, FBT002 use_pendulum: bool = False, # noqa: FBT001, FBT002 + use_standard_primitive_types: bool = False, # noqa: FBT001, FBT002, ARG002 target_datetime_class: DatetimeClassType | None = None, treat_dot_as_module: bool | None = None, # noqa: FBT001 use_serialize_as_any: bool = False, # noqa: FBT001, FBT002 ) -> None: """Initialize the DataTypeManager with Pydantic v1 type mappings.""" super().__init__( - python_version, - use_standard_collections, - use_generic_container_types, - strict_types, - use_non_positive_negative_number_constrained_types, - use_decimal_for_multiple_of, - use_union_operator, - use_pendulum, - target_datetime_class, - treat_dot_as_module, - use_serialize_as_any, + python_version=python_version, + use_standard_collections=use_standard_collections, + use_generic_container_types=use_generic_container_types, + strict_types=strict_types, + use_non_positive_negative_number_constrained_types=use_non_positive_negative_number_constrained_types, + use_decimal_for_multiple_of=use_decimal_for_multiple_of, + use_union_operator=use_union_operator, + use_pendulum=use_pendulum, + target_datetime_class=target_datetime_class, + treat_dot_as_module=treat_dot_as_module, + use_serialize_as_any=use_serialize_as_any, ) self.type_map: dict[Types, DataType] = self.type_map_factory( diff --git a/src/datamodel_code_generator/model/pydantic_v2/types.py b/src/datamodel_code_generator/model/pydantic_v2/types.py index 3b0a7a6e0..8a9bdac83 100644 --- a/src/datamodel_code_generator/model/pydantic_v2/types.py +++ b/src/datamodel_code_generator/model/pydantic_v2/types.py @@ -79,6 +79,7 @@ def __init__( # noqa: PLR0913, PLR0917 use_decimal_for_multiple_of: bool = False, # noqa: FBT001, FBT002 use_union_operator: bool = False, # noqa: FBT001, FBT002 use_pendulum: bool = False, # noqa: FBT001, FBT002 + use_standard_primitive_types: bool = False, # noqa: FBT001, FBT002, ARG002 target_datetime_class: DatetimeClassType | None = None, treat_dot_as_module: bool | None = None, # noqa: FBT001 use_serialize_as_any: bool = False, # noqa: FBT001, FBT002 diff --git a/src/datamodel_code_generator/model/types.py b/src/datamodel_code_generator/model/types.py index ed4a41053..302553208 100644 --- a/src/datamodel_code_generator/model/types.py +++ b/src/datamodel_code_generator/model/types.py @@ -11,7 +11,13 @@ from datamodel_code_generator.imports import ( IMPORT_ANY, IMPORT_DECIMAL, + IMPORT_IPV4ADDRESS, + IMPORT_IPV4NETWORK, + IMPORT_IPV6ADDRESS, + IMPORT_IPV6NETWORK, + IMPORT_PATH, IMPORT_TIMEDELTA, + IMPORT_UUID, ) from datamodel_code_generator.types import DataType, StrictTypes, Types from datamodel_code_generator.types import DataTypeManager as _DataTypeManager @@ -68,6 +74,28 @@ def type_map_factory(data_type: type[DataType]) -> dict[Types, DataType]: } +def standard_primitive_type_map_factory(data_type: type[DataType]) -> dict[Types, DataType]: + """Create type mapping for standard library primitive types. + + Maps string formats to their corresponding Python standard library types + (UUID, IPv4Address, IPv6Address, Path, etc.) instead of plain str. + """ + uuid_type = data_type.from_import(IMPORT_UUID) + return { + Types.uuid: uuid_type, + Types.uuid1: uuid_type, + Types.uuid2: uuid_type, + Types.uuid3: uuid_type, + Types.uuid4: uuid_type, + Types.uuid5: uuid_type, + Types.ipv4: data_type.from_import(IMPORT_IPV4ADDRESS), + Types.ipv6: data_type.from_import(IMPORT_IPV6ADDRESS), + Types.ipv4_network: data_type.from_import(IMPORT_IPV4NETWORK), + Types.ipv6_network: data_type.from_import(IMPORT_IPV6NETWORK), + Types.path: data_type.from_import(IMPORT_PATH), + } + + class DataTypeManager(_DataTypeManager): """Base type manager for model modules.""" @@ -83,26 +111,34 @@ def __init__( # noqa: PLR0913, PLR0917 use_decimal_for_multiple_of: bool = False, # noqa: FBT001, FBT002 use_union_operator: bool = False, # noqa: FBT001, FBT002 use_pendulum: bool = False, # noqa: FBT001, FBT002 + use_standard_primitive_types: bool = False, # noqa: FBT001, FBT002 target_datetime_class: DatetimeClassType | None = None, treat_dot_as_module: bool | None = None, # noqa: FBT001 use_serialize_as_any: bool = False, # noqa: FBT001, FBT002 ) -> None: """Initialize type manager with basic type mapping.""" super().__init__( - python_version, - use_standard_collections, - use_generic_container_types, - strict_types, - use_non_positive_negative_number_constrained_types, - use_decimal_for_multiple_of, - use_union_operator, - use_pendulum, - target_datetime_class, - treat_dot_as_module, - use_serialize_as_any, + python_version=python_version, + use_standard_collections=use_standard_collections, + use_generic_container_types=use_generic_container_types, + strict_types=strict_types, + use_non_positive_negative_number_constrained_types=use_non_positive_negative_number_constrained_types, + use_decimal_for_multiple_of=use_decimal_for_multiple_of, + use_union_operator=use_union_operator, + use_pendulum=use_pendulum, + target_datetime_class=target_datetime_class, + treat_dot_as_module=treat_dot_as_module, + use_serialize_as_any=use_serialize_as_any, + ) + + standard_primitive_map = ( + standard_primitive_type_map_factory(self.data_type) if use_standard_primitive_types else {} ) - self.type_map: dict[Types, DataType] = type_map_factory(self.data_type) + self.type_map: dict[Types, DataType] = { + **type_map_factory(self.data_type), + **standard_primitive_map, + } def get_data_type( self, diff --git a/src/datamodel_code_generator/parser/base.py b/src/datamodel_code_generator/parser/base.py index 8d72f850d..b195ec5a4 100644 --- a/src/datamodel_code_generator/parser/base.py +++ b/src/datamodel_code_generator/parser/base.py @@ -756,6 +756,7 @@ def __init__( # noqa: PLR0912, PLR0913, PLR0915 custom_formatters: list[str] | None = None, custom_formatters_kwargs: dict[str, Any] | None = None, use_pendulum: bool = False, + use_standard_primitive_types: bool = False, http_query_parameters: Sequence[tuple[str, str]] | None = None, treat_dot_as_module: bool | None = None, use_exact_imports: bool = False, @@ -785,6 +786,7 @@ def __init__( # noqa: PLR0912, PLR0913, PLR0915 strict_types=strict_types, use_union_operator=use_union_operator, use_pendulum=use_pendulum, + use_standard_primitive_types=use_standard_primitive_types, target_datetime_class=target_datetime_class, treat_dot_as_module=treat_dot_as_module or False, use_serialize_as_any=use_serialize_as_any, diff --git a/src/datamodel_code_generator/parser/graphql.py b/src/datamodel_code_generator/parser/graphql.py index 45587ae4b..0d930a3d5 100644 --- a/src/datamodel_code_generator/parser/graphql.py +++ b/src/datamodel_code_generator/parser/graphql.py @@ -178,6 +178,7 @@ def __init__( # noqa: PLR0913 custom_formatters: list[str] | None = None, custom_formatters_kwargs: dict[str, Any] | None = None, use_pendulum: bool = False, + use_standard_primitive_types: bool = False, http_query_parameters: Sequence[tuple[str, str]] | None = None, treat_dot_as_module: bool | None = None, use_exact_imports: bool = False, @@ -276,6 +277,7 @@ def __init__( # noqa: PLR0913 custom_formatters=custom_formatters, custom_formatters_kwargs=custom_formatters_kwargs, use_pendulum=use_pendulum, + use_standard_primitive_types=use_standard_primitive_types, http_query_parameters=http_query_parameters, treat_dot_as_module=treat_dot_as_module, use_exact_imports=use_exact_imports, diff --git a/src/datamodel_code_generator/parser/jsonschema.py b/src/datamodel_code_generator/parser/jsonschema.py index 852747c62..434c1e728 100644 --- a/src/datamodel_code_generator/parser/jsonschema.py +++ b/src/datamodel_code_generator/parser/jsonschema.py @@ -591,6 +591,7 @@ def __init__( # noqa: PLR0913 custom_formatters: list[str] | None = None, custom_formatters_kwargs: dict[str, Any] | None = None, use_pendulum: bool = False, + use_standard_primitive_types: bool = False, http_query_parameters: Sequence[tuple[str, str]] | None = None, treat_dot_as_module: bool | None = None, use_exact_imports: bool = False, @@ -690,6 +691,7 @@ def __init__( # noqa: PLR0913 custom_formatters=custom_formatters, custom_formatters_kwargs=custom_formatters_kwargs, use_pendulum=use_pendulum, + use_standard_primitive_types=use_standard_primitive_types, http_query_parameters=http_query_parameters, treat_dot_as_module=treat_dot_as_module, use_exact_imports=use_exact_imports, diff --git a/src/datamodel_code_generator/parser/openapi.py b/src/datamodel_code_generator/parser/openapi.py index ccabb1399..36732f705 100644 --- a/src/datamodel_code_generator/parser/openapi.py +++ b/src/datamodel_code_generator/parser/openapi.py @@ -262,6 +262,7 @@ def __init__( # noqa: PLR0913 custom_formatters: list[str] | None = None, custom_formatters_kwargs: dict[str, Any] | None = None, use_pendulum: bool = False, + use_standard_primitive_types: bool = False, http_query_parameters: Sequence[tuple[str, str]] | None = None, treat_dot_as_module: bool | None = None, use_exact_imports: bool = False, @@ -362,6 +363,7 @@ def __init__( # noqa: PLR0913 custom_formatters=custom_formatters, custom_formatters_kwargs=custom_formatters_kwargs, use_pendulum=use_pendulum, + use_standard_primitive_types=use_standard_primitive_types, http_query_parameters=http_query_parameters, treat_dot_as_module=treat_dot_as_module, use_exact_imports=use_exact_imports, diff --git a/src/datamodel_code_generator/types.py b/src/datamodel_code_generator/types.py index e1a6340f4..57740431a 100644 --- a/src/datamodel_code_generator/types.py +++ b/src/datamodel_code_generator/types.py @@ -824,6 +824,7 @@ def __init__( # noqa: PLR0913, PLR0917 use_decimal_for_multiple_of: bool = False, # noqa: FBT001, FBT002 use_union_operator: bool = False, # noqa: FBT001, FBT002 use_pendulum: bool = False, # noqa: FBT001, FBT002 + use_standard_primitive_types: bool = False, # noqa: FBT001, FBT002, ARG002 target_datetime_class: DatetimeClassType | None = None, treat_dot_as_module: bool | None = None, # noqa: FBT001 use_serialize_as_any: bool = False, # noqa: FBT001, FBT002 diff --git a/tests/data/expected/main/use_standard_primitive_types.py b/tests/data/expected/main/use_standard_primitive_types.py new file mode 100644 index 000000000..035e9eec2 --- /dev/null +++ b/tests/data/expected/main/use_standard_primitive_types.py @@ -0,0 +1,16 @@ +# generated by datamodel-codegen: +# filename: use_standard_primitive_types.json + +from __future__ import annotations + +from dataclasses import dataclass +from ipaddress import IPv4Address +from pathlib import Path +from uuid import UUID + + +@dataclass +class Model: + id: UUID | None = None + ip_address: IPv4Address | None = None + config_path: Path | None = None diff --git a/tests/data/jsonschema/use_standard_primitive_types.json b/tests/data/jsonschema/use_standard_primitive_types.json new file mode 100644 index 000000000..13ce6697d --- /dev/null +++ b/tests/data/jsonschema/use_standard_primitive_types.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "ip_address": { + "type": "string", + "format": "ipv4" + }, + "config_path": { + "type": "string", + "format": "path" + } + } +} diff --git a/tests/main/test_main_general.py b/tests/main/test_main_general.py index 19bf3accf..fa0446c54 100644 --- a/tests/main/test_main_general.py +++ b/tests/main/test_main_general.py @@ -1057,3 +1057,36 @@ def test_module_split_mode_single(output_dir: Path) -> None: ], expected_directory=EXPECTED_MAIN_PATH / "jsonschema" / "module_split_single", ) + + +@pytest.mark.cli_doc( + options=["--use-standard-primitive-types"], + input_schema="jsonschema/use_standard_primitive_types.json", + cli_args=[ + "--output-model-type", + "dataclasses.dataclass", + "--use-standard-primitive-types", + ], + golden_output="use_standard_primitive_types.py", + related_options=["--output-model-type", "--output-datetime-class"], +) +@freeze_time(TIMESTAMP) +def test_use_standard_primitive_types(output_file: Path) -> None: + """Use Python standard library types for string formats instead of str. + + The `--use-standard-primitive-types` flag configures the code generation to use + Python standard library types (UUID, IPv4Address, IPv6Address, Path) for corresponding + string formats instead of plain str. This affects dataclass, msgspec, and TypedDict + output types. Pydantic already uses these types by default. + """ + run_main_and_assert( + input_path=JSON_SCHEMA_DATA_PATH / "use_standard_primitive_types.json", + output_path=output_file, + input_file_type="jsonschema", + extra_args=[ + "--output-model-type", + "dataclasses.dataclass", + "--use-standard-primitive-types", + ], + expected_file=EXPECTED_MAIN_PATH / "use_standard_primitive_types.py", + )