diff --git a/.github/workflows/config-types.yaml b/.github/workflows/config-types.yaml new file mode 100644 index 000000000..0368a8190 --- /dev/null +++ b/.github/workflows/config-types.yaml @@ -0,0 +1,73 @@ +name: Update Config Types + +on: + push: + branches: [main] + paths: + - 'src/datamodel_code_generator/config.py' + pull_request: + branches: [main] + paths: + - 'src/datamodel_code_generator/config.py' + pull_request_target: + types: [labeled] + paths: + - 'src/datamodel_code_generator/config.py' + +permissions: + contents: write + +jobs: + update-config-types: + if: | + github.event_name == 'push' || + !github.event.pull_request.head.repo.fork || + github.actor == 'koxudaxi' || + github.actor == 'gaborbernat' || + github.actor == 'ilovelinux' || + (github.event_name == 'pull_request_target' && github.event.label.name == 'safe-to-fix' && + (github.event.sender.login == 'koxudaxi' || + github.event.sender.login == 'gaborbernat' || + github.event.sender.login == 'ilovelinux')) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + - uses: actions/checkout@v4 + if: github.event_name == 'push' || github.event_name == 'pull_request_target' || github.event.pull_request.head.repo.full_name == github.repository + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.ref || github.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} + token: ${{ secrets.PAT }} + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v5 + - name: Install tox + run: uv tool install --python-preference only-managed --python 3.13 tox --with tox-uv + - name: Setup environment + run: tox run -vv --notest --skip-missing-interpreters false -e config-types + env: + UV_PYTHON_PREFERENCE: "only-managed" + - name: Generate TypedDict from Config models + run: | + .tox/config-types/bin/datamodel-codegen --profile base-config-dict + .tox/config-types/bin/datamodel-codegen --profile generate-config-dict + .tox/config-types/bin/datamodel-codegen --profile parser-config-dict + .tox/config-types/bin/datamodel-codegen --profile parse-config-dict + - name: Format generated files + run: | + uv tool run ruff format src/datamodel_code_generator/_types/ + - name: Commit and push if changed + if: github.event_name == 'push' || github.event_name == 'pull_request_target' || github.event.pull_request.head.repo.full_name == github.repository + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add src/datamodel_code_generator/_types/ + git diff --staged --quiet || git commit -m "chore: update TypedDict from Config models + + 🤖 Generated by GitHub Actions" + git push diff --git a/pyproject.toml b/pyproject.toml index 43018f3b8..92736debe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -181,6 +181,9 @@ lint.per-file-ignores."scripts/*.py" = [ "S", # subprocess security is fine for build scripts "T201", # print is fine for CLI scripts ] +lint.per-file-ignores."src/datamodel_code_generator/config.py" = [ + "TC", # Pydantic needs these imports at runtime, not just TYPE_CHECKING +] lint.per-file-ignores."tests/**/*.py" = [ "FBT", # don't care about booleans as positional arguments in tests "INP001", # no implicit namespace @@ -257,3 +260,31 @@ reportPrivateImportUsage = false [tool.pydantic-pycharm-plugin] ignore-init-method-arguments = true parsable-types.str = [ "int", "float" ] + +[tool.datamodel-codegen.profiles.base-config-dict] +input-model = "src/datamodel_code_generator/config.py:BaseConfig" +output = "src/datamodel_code_generator/_types/base_config_dict.py" +output-model-type = "typing.TypedDict" +disable-timestamp = true +type-overrides = { "path" = "pathlib.Path" } + +[tool.datamodel-codegen.profiles.generate-config-dict] +input-model = "src/datamodel_code_generator/config.py:GenerateConfig" +output = "src/datamodel_code_generator/_types/generate_config_dict.py" +output-model-type = "typing.TypedDict" +disable-timestamp = true +type-overrides = { "path" = "pathlib.Path" } + +[tool.datamodel-codegen.profiles.parser-config-dict] +input-model = "src/datamodel_code_generator/config.py:ParserConfig" +output = "src/datamodel_code_generator/_types/parser_config_dict.py" +output-model-type = "typing.TypedDict" +disable-timestamp = true +type-overrides = { "path" = "pathlib.Path" } + +[tool.datamodel-codegen.profiles.parse-config-dict] +input-model = "src/datamodel_code_generator/config.py:ParseConfig" +output = "src/datamodel_code_generator/_types/parse_config_dict.py" +output-model-type = "typing.TypedDict" +disable-timestamp = true +type-overrides = { "path" = "pathlib.Path" } diff --git a/src/datamodel_code_generator/__init__.py b/src/datamodel_code_generator/__init__.py index d127979d6..aa4007640 100644 --- a/src/datamodel_code_generator/__init__.py +++ b/src/datamodel_code_generator/__init__.py @@ -9,7 +9,8 @@ import contextlib import os import sys -from collections.abc import Callable, Iterator, Mapping, Sequence +from collections import defaultdict +from collections.abc import Callable, Iterator, Mapping from datetime import datetime, timezone from functools import lru_cache as _lru_cache from pathlib import Path @@ -58,8 +59,10 @@ from datamodel_code_generator.parser import DefaultPutDict, LiteralType if TYPE_CHECKING: - from collections import defaultdict - + from datamodel_code_generator._types.generate_config_dict import ( + GenerateConfig as GenerateConfigDict, + ) + from datamodel_code_generator.config import GenerateConfig from datamodel_code_generator.model.pydantic_v2 import UnionMode from datamodel_code_generator.parser.base import Parser from datamodel_code_generator.types import StrictTypes @@ -447,141 +450,171 @@ def _build_module_content( return "\n".join(lines) -def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915 +def generate( # noqa: PLR0912, PLR0914, PLR0915 input_: Path | str | ParseResult | Mapping[str, Any], *, - input_filename: str | None = None, - input_file_type: InputFileType = InputFileType.Auto, - output: Path | None = None, - output_model_type: DataModelType = DataModelType.PydanticBaseModel, - target_python_version: PythonVersion = PythonVersionMin, - target_pydantic_version: TargetPydanticVersion | None = None, - base_class: str = "", - base_class_map: dict[str, str] | None = None, - additional_imports: list[str] | None = None, - class_decorators: list[str] | None = None, - custom_template_dir: Path | None = None, - extra_template_data: defaultdict[str, dict[str, Any]] | None = None, - validation: bool = False, - field_constraints: bool = False, - snake_case_field: bool = False, - strip_default_none: bool = False, - aliases: Mapping[str, str] | None = None, - disable_timestamp: bool = False, - enable_version_header: bool = False, - enable_command_header: bool = False, - command_line: str | None = None, - allow_population_by_field_name: bool = False, - allow_extra_fields: bool = False, - extra_fields: str | None = None, - use_generic_base_class: bool = False, - apply_default_values_for_required_fields: bool = False, - force_optional_for_required_fields: bool = False, - class_name: str | None = None, - use_standard_collections: bool = True, - use_schema_description: bool = False, - use_field_description: bool = False, - use_field_description_example: bool = False, - use_attribute_docstrings: bool = False, - use_inline_field_description: bool = False, - use_default_kwarg: bool = False, - reuse_model: bool = False, - reuse_scope: ReuseScope = ReuseScope.Module, - shared_module_name: str = DEFAULT_SHARED_MODULE_NAME, - encoding: str = "utf-8", - enum_field_as_literal: LiteralType | None = None, - enum_field_as_literal_map: dict[str, str] | None = None, - ignore_enum_constraints: bool = False, - use_one_literal_as_default: bool = False, - use_enum_values_in_discriminator: bool = False, - set_default_enum_member: bool = False, - use_subclass_enum: bool = False, - use_specialized_enum: bool = True, - strict_nullable: bool = False, - use_generic_container_types: bool = False, - enable_faux_immutability: bool = False, - disable_appending_item_suffix: bool = False, - strict_types: Sequence[StrictTypes] | None = None, - empty_enum_field_name: str | None = None, - custom_class_name_generator: Callable[[str], str] | None = None, - field_extra_keys: set[str] | None = None, - field_include_all_keys: bool = False, - field_extra_keys_without_x_prefix: set[str] | None = None, - model_extra_keys: set[str] | None = None, - model_extra_keys_without_x_prefix: set[str] | None = None, - openapi_scopes: list[OpenAPIScope] | None = None, - include_path_parameters: bool = False, - graphql_scopes: list[GraphQLScope] | None = None, # noqa: ARG001 - wrap_string_literal: bool | None = None, - use_title_as_name: bool = False, - use_operation_id_as_name: bool = False, - use_unique_items_as_set: bool = False, - use_tuple_for_fixed_items: bool = False, - allof_merge_mode: AllOfMergeMode = AllOfMergeMode.Constraints, - http_headers: Sequence[tuple[str, str]] | None = None, - http_ignore_tls: bool = False, - http_timeout: float | None = None, - use_annotated: bool = False, - use_serialize_as_any: bool = False, - use_non_positive_negative_number_constrained_types: bool = False, - use_decimal_for_multiple_of: bool = False, - original_field_name_delimiter: str | None = None, - use_double_quotes: bool = False, - use_union_operator: bool = True, - collapse_root_models: bool = False, - collapse_root_models_name_strategy: CollapseRootModelsNameStrategy | None = None, - collapse_reuse_models: bool = False, - skip_root_model: bool = False, - use_type_alias: bool = False, - use_root_model_type_alias: bool = False, - special_field_name_prefix: str | None = None, - remove_special_field_name_prefix: bool = False, - capitalise_enum_members: bool = False, - keep_model_order: bool = False, - custom_file_header: str | None = None, - custom_file_header_path: Path | None = None, - 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, - union_mode: UnionMode | None = None, - output_datetime_class: DatetimeClassType | None = None, - output_date_class: DateClassType | None = None, - keyword_only: bool = False, - frozen_dataclasses: bool = False, - no_alias: bool = False, - use_frozen_field: bool = False, - use_default_factory_for_optional_nested_models: bool = False, - formatters: list[Formatter] = DEFAULT_FORMATTERS, - settings_path: Path | None = None, - parent_scoped_naming: bool = False, - naming_strategy: NamingStrategy | None = None, - duplicate_name_suffix: dict[str, str] | None = None, - dataclass_arguments: DataclassArguments | None = None, - disable_future_imports: bool = False, - type_mappings: list[str] | None = None, - type_overrides: dict[str, str] | None = None, - read_only_write_only_model_type: ReadOnlyWriteOnlyModelType | None = None, - use_status_code_in_response_name: bool = False, - all_exports_scope: AllExportsScope | None = None, - all_exports_collision_strategy: AllExportsCollisionStrategy | None = None, - field_type_collision_strategy: FieldTypeCollisionStrategy | None = None, - module_split_mode: ModuleSplitMode | None = None, + config: GenerateConfig | None = None, + **options: Any, ) -> str | GeneratedModules | None: """Generate Python data models from schema definitions or structured data. This is the main entry point for code generation. Supports OpenAPI, JSON Schema, GraphQL, and raw data formats (JSON, YAML, Dict, CSV) as input. + Args: + input_: The input source (Path, str, ParseResult, or dict). + config: A GenerateConfig object containing all generation options. + Cannot be used together with individual options. + options: Individual generation options (see GenerateConfig for details). + Cannot be used together with config parameter. + Returns: - When output is a Path: None (writes to file system) - When output is None and single module: str (generated code) - When output is None and multiple modules: GeneratedModules (dict mapping module path tuples to generated code strings) + + Raises: + ValueError: If both config and individual options are provided. """ + # Exclusivity check: config and options cannot be used together + if config is not None and options: + msg = "Cannot specify both 'config' and individual options" + raise ValueError(msg) + + # Create GenerateConfig from options or use default + if options: + from datamodel_code_generator.config import GenerateConfig as _GenerateConfig # noqa: PLC0415 + + config = _GenerateConfig(**options) + elif config is None: + from datamodel_code_generator.config import GenerateConfig as _GenerateConfig # noqa: PLC0415 + + config = _GenerateConfig() + + # Extract values from config + input_filename = config.input_filename + input_file_type = config.input_file_type + output = config.output + output_model_type = config.output_model_type + target_python_version = config.target_python_version + target_pydantic_version = config.target_pydantic_version + base_class = config.base_class + base_class_map = config.base_class_map + additional_imports = config.additional_imports + class_decorators = config.class_decorators + custom_template_dir = config.custom_template_dir + extra_template_data: defaultdict[str, dict[str, Any]] | None = ( + defaultdict(dict, config.extra_template_data) if config.extra_template_data else None + ) + validation = config.validation + field_constraints = config.field_constraints + snake_case_field = config.snake_case_field + strip_default_none = config.strip_default_none + aliases = config.aliases + disable_timestamp = config.disable_timestamp + enable_version_header = config.enable_version_header + enable_command_header = config.enable_command_header + command_line = config.command_line + allow_population_by_field_name = config.allow_population_by_field_name + allow_extra_fields = config.allow_extra_fields + extra_fields = config.extra_fields + use_generic_base_class = config.use_generic_base_class + apply_default_values_for_required_fields = config.apply_default_values_for_required_fields + force_optional_for_required_fields = config.force_optional_for_required_fields + class_name = config.class_name + use_standard_collections = config.use_standard_collections + use_schema_description = config.use_schema_description + use_field_description = config.use_field_description + use_field_description_example = config.use_field_description_example + use_attribute_docstrings = config.use_attribute_docstrings + use_inline_field_description = config.use_inline_field_description + use_default_kwarg = config.use_default_kwarg + reuse_model = config.reuse_model + reuse_scope = config.reuse_scope + shared_module_name = config.shared_module_name + encoding = config.encoding + enum_field_as_literal = config.enum_field_as_literal + enum_field_as_literal_map = config.enum_field_as_literal_map + ignore_enum_constraints = config.ignore_enum_constraints + use_one_literal_as_default = config.use_one_literal_as_default + use_enum_values_in_discriminator = config.use_enum_values_in_discriminator + set_default_enum_member = config.set_default_enum_member + use_subclass_enum = config.use_subclass_enum + use_specialized_enum = config.use_specialized_enum + strict_nullable = config.strict_nullable + use_generic_container_types = config.use_generic_container_types + enable_faux_immutability = config.enable_faux_immutability + disable_appending_item_suffix = config.disable_appending_item_suffix + strict_types = config.strict_types + empty_enum_field_name = config.empty_enum_field_name + custom_class_name_generator = config.custom_class_name_generator + field_extra_keys = config.field_extra_keys + field_include_all_keys = config.field_include_all_keys + field_extra_keys_without_x_prefix = config.field_extra_keys_without_x_prefix + model_extra_keys = config.model_extra_keys + model_extra_keys_without_x_prefix = config.model_extra_keys_without_x_prefix + openapi_scopes = config.openapi_scopes + include_path_parameters = config.include_path_parameters + wrap_string_literal = config.wrap_string_literal + use_title_as_name = config.use_title_as_name + use_operation_id_as_name = config.use_operation_id_as_name + use_unique_items_as_set = config.use_unique_items_as_set + use_tuple_for_fixed_items = config.use_tuple_for_fixed_items + allof_merge_mode = config.allof_merge_mode + http_headers = config.http_headers + http_ignore_tls = config.http_ignore_tls + http_timeout = config.http_timeout + use_annotated = config.use_annotated + use_serialize_as_any = config.use_serialize_as_any + use_non_positive_negative_number_constrained_types = config.use_non_positive_negative_number_constrained_types + use_decimal_for_multiple_of = config.use_decimal_for_multiple_of + original_field_name_delimiter = config.original_field_name_delimiter + use_double_quotes = config.use_double_quotes + use_union_operator = config.use_union_operator + collapse_root_models = config.collapse_root_models + collapse_root_models_name_strategy = config.collapse_root_models_name_strategy + collapse_reuse_models = config.collapse_reuse_models + skip_root_model = config.skip_root_model + use_type_alias = config.use_type_alias + use_root_model_type_alias = config.use_root_model_type_alias + special_field_name_prefix = config.special_field_name_prefix + remove_special_field_name_prefix = config.remove_special_field_name_prefix + capitalise_enum_members = config.capitalise_enum_members + keep_model_order = config.keep_model_order + custom_file_header = config.custom_file_header + custom_file_header_path = config.custom_file_header_path + custom_formatters = config.custom_formatters + custom_formatters_kwargs = config.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 + union_mode = cast("UnionMode | None", config.union_mode) + output_datetime_class = config.output_datetime_class + output_date_class = config.output_date_class + keyword_only = config.keyword_only + frozen_dataclasses = config.frozen_dataclasses + no_alias = config.no_alias + use_frozen_field = config.use_frozen_field + use_default_factory_for_optional_nested_models = config.use_default_factory_for_optional_nested_models + formatters = config.formatters + settings_path = config.settings_path + parent_scoped_naming = config.parent_scoped_naming + naming_strategy = config.naming_strategy + duplicate_name_suffix = config.duplicate_name_suffix + dataclass_arguments = config.dataclass_arguments + disable_future_imports = config.disable_future_imports + type_mappings = config.type_mappings + type_overrides = config.type_overrides + read_only_write_only_model_type = config.read_only_write_only_model_type + use_status_code_in_response_name = config.use_status_code_in_response_name + all_exports_scope = config.all_exports_scope + all_exports_collision_strategy = config.all_exports_collision_strategy + field_type_collision_strategy = config.field_type_collision_strategy + module_split_mode = config.module_split_mode + remote_text_cache: DefaultPutDict[str, str] = DefaultPutDict() match input_: case str(): @@ -600,7 +633,7 @@ def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915 input_text = None if dataclass_arguments is None: - dataclass_arguments = {} + dataclass_arguments = cast("DataclassArguments", {}) if frozen_dataclasses: dataclass_arguments["frozen"] = True if keyword_only: @@ -1045,24 +1078,47 @@ def infer_input_type(text: str) -> InputFileType: ) __all__ = [ + "DEFAULT_FORMATTERS", + "DEFAULT_SHARED_MODULE_NAME", "MAX_VERSION", "MIN_VERSION", "AllExportsCollisionStrategy", "AllExportsScope", + "AllOfMergeMode", + "CollapseRootModelsNameStrategy", + "DataclassArguments", "DateClassType", "DatetimeClassType", "DefaultPutDict", "Error", + "FieldTypeCollisionStrategy", + "GenerateConfig", + "GenerateConfigDict", "GeneratedModules", + "GraphQLScope", "InputFileType", "InvalidClassNameError", "InvalidFileFormatError", "LiteralType", "ModuleSplitMode", "NamingStrategy", + "OpenAPIScope", "PythonVersion", + "PythonVersionMin", "ReadOnlyWriteOnlyModelType", + "ReuseScope", "SchemaParseError", + "StrictTypes", "TargetPydanticVersion", "generate", ] + + +def __getattr__(name: str) -> type: + """Lazy import for GenerateConfig to avoid circular imports.""" + if name == "GenerateConfig": + from datamodel_code_generator.config import GenerateConfig # noqa: PLC0415 + + return GenerateConfig + msg = f"module {__name__!r} has no attribute {name!r}" + raise AttributeError(msg) diff --git a/src/datamodel_code_generator/_types/__init__.py b/src/datamodel_code_generator/_types/__init__.py new file mode 100644 index 000000000..69833cb71 --- /dev/null +++ b/src/datamodel_code_generator/_types/__init__.py @@ -0,0 +1,11 @@ +"""Generated TypedDict types for configuration models. + +This module contains TypedDict classes auto-generated from the Config Pydantic models +using datamodel-codegen itself (dogfooding). These types enable type-safe **kwargs +patterns with Unpack[]. + +The TypedDict files in this directory are generated by running: + tox -e config-types + +Do not edit the generated files manually. +""" diff --git a/src/datamodel_code_generator/_types/base_config_dict.py b/src/datamodel_code_generator/_types/base_config_dict.py new file mode 100644 index 000000000..92834e31e --- /dev/null +++ b/src/datamodel_code_generator/_types/base_config_dict.py @@ -0,0 +1,182 @@ +# generated by datamodel-codegen: +# filename: + +from __future__ import annotations + +from typing import Any, Literal, TypeAlias, TypedDict + +from typing_extensions import NotRequired + +AllOfMergeMode: TypeAlias = Literal["constraints", "all", "none"] + + +CollapseRootModelsNameStrategy: TypeAlias = Literal["child", "parent"] + + +DataModelType: TypeAlias = Literal[ + "pydantic.BaseModel", + "pydantic_v2.BaseModel", + "pydantic_v2.dataclass", + "dataclasses.dataclass", + "typing.TypedDict", + "msgspec.Struct", +] + + +class DataclassArguments(TypedDict): + init: NotRequired[bool] + repr: NotRequired[bool] + eq: NotRequired[bool] + order: NotRequired[bool] + unsafe_hash: NotRequired[bool] + frozen: NotRequired[bool] + match_args: NotRequired[bool] + kw_only: NotRequired[bool] + slots: NotRequired[bool] + weakref_slot: NotRequired[bool] + + +DateClassType: TypeAlias = Literal["date", "PastDate", "FutureDate"] + + +DatetimeClassType: TypeAlias = Literal["datetime", "AwareDatetime", "NaiveDatetime", "PastDatetime", "FutureDatetime"] + + +FieldTypeCollisionStrategy: TypeAlias = Literal["rename-field", "rename-type"] + + +Formatter: TypeAlias = Literal["black", "isort", "ruff-check", "ruff-format"] + + +GraphQLScope: TypeAlias = Literal["schema"] + + +LiteralType: TypeAlias = Literal["all", "one", "none"] + + +NamingStrategy: TypeAlias = Literal["numbered", "parent-prefixed", "full-path", "primary-first"] + + +OpenAPIScope: TypeAlias = Literal["schemas", "paths", "tags", "parameters", "webhooks", "requestbodies"] + + +PythonVersion: TypeAlias = Literal["3.10", "3.11", "3.12", "3.13", "3.14"] + + +ReadOnlyWriteOnlyModelType: TypeAlias = Literal["request-response", "all"] + + +ReuseScope: TypeAlias = Literal["module", "tree"] + + +StrictTypes: TypeAlias = Literal["str", "bytes", "int", "float", "bool"] + + +TargetPydanticVersion: TypeAlias = Literal["2", "2.11"] + + +UnionMode: TypeAlias = Literal["smart", "left_to_right"] + + +class BaseConfig(TypedDict): + output_model_type: NotRequired[DataModelType] + target_python_version: NotRequired[PythonVersion] + target_pydantic_version: NotRequired[TargetPydanticVersion | None] + base_class: NotRequired[str] + base_class_map: NotRequired[dict[str, str] | None] + additional_imports: NotRequired[list[str] | None] + class_decorators: NotRequired[list[str] | None] + custom_template_dir: NotRequired[str | None] + extra_template_data: NotRequired[dict[str, dict[str, Any]] | None] + validation: NotRequired[bool] + field_constraints: NotRequired[bool] + snake_case_field: NotRequired[bool] + strip_default_none: NotRequired[bool] + aliases: NotRequired[dict[str, str] | None] + allow_population_by_field_name: NotRequired[bool] + allow_extra_fields: NotRequired[bool] + extra_fields: NotRequired[str | None] + use_generic_base_class: NotRequired[bool] + apply_default_values_for_required_fields: NotRequired[bool] + force_optional_for_required_fields: NotRequired[bool] + class_name: NotRequired[str | None] + use_standard_collections: NotRequired[bool] + use_generic_container_types: NotRequired[bool] + use_union_operator: NotRequired[bool] + strict_nullable: NotRequired[bool] + strict_types: NotRequired[list[StrictTypes] | None] + use_schema_description: NotRequired[bool] + use_field_description: NotRequired[bool] + use_field_description_example: NotRequired[bool] + use_attribute_docstrings: NotRequired[bool] + use_inline_field_description: NotRequired[bool] + use_default_kwarg: NotRequired[bool] + reuse_model: NotRequired[bool] + reuse_scope: NotRequired[ReuseScope] + shared_module_name: NotRequired[str] + encoding: NotRequired[str] + enum_field_as_literal: NotRequired[LiteralType | None] + enum_field_as_literal_map: NotRequired[dict[str, str] | None] + ignore_enum_constraints: NotRequired[bool] + use_one_literal_as_default: NotRequired[bool] + use_enum_values_in_discriminator: NotRequired[bool] + set_default_enum_member: NotRequired[bool] + use_subclass_enum: NotRequired[bool] + use_specialized_enum: NotRequired[bool] + empty_enum_field_name: NotRequired[str | None] + capitalise_enum_members: NotRequired[bool] + enable_faux_immutability: NotRequired[bool] + disable_appending_item_suffix: NotRequired[bool] + special_field_name_prefix: NotRequired[str | None] + remove_special_field_name_prefix: NotRequired[bool] + field_extra_keys: NotRequired[list[str] | None] + field_include_all_keys: NotRequired[bool] + field_extra_keys_without_x_prefix: NotRequired[list[str] | None] + model_extra_keys: NotRequired[list[str] | None] + model_extra_keys_without_x_prefix: NotRequired[list[str] | None] + openapi_scopes: NotRequired[list[OpenAPIScope] | None] + include_path_parameters: NotRequired[bool] + graphql_scopes: NotRequired[list[GraphQLScope] | None] + wrap_string_literal: NotRequired[bool | None] + use_double_quotes: NotRequired[bool] + original_field_name_delimiter: NotRequired[str | None] + use_title_as_name: NotRequired[bool] + use_operation_id_as_name: NotRequired[bool] + use_unique_items_as_set: NotRequired[bool] + use_tuple_for_fixed_items: NotRequired[bool] + allof_merge_mode: NotRequired[AllOfMergeMode] + use_annotated: NotRequired[bool] + use_serialize_as_any: NotRequired[bool] + use_non_positive_negative_number_constrained_types: NotRequired[bool] + use_decimal_for_multiple_of: NotRequired[bool] + collapse_root_models: NotRequired[bool] + collapse_root_models_name_strategy: NotRequired[CollapseRootModelsNameStrategy | None] + collapse_reuse_models: NotRequired[bool] + skip_root_model: NotRequired[bool] + use_type_alias: NotRequired[bool] + use_root_model_type_alias: NotRequired[bool] + keep_model_order: NotRequired[bool] + custom_formatters: NotRequired[list[str] | None] + custom_formatters_kwargs: NotRequired[dict[str, Any] | None] + use_pendulum: NotRequired[bool] + use_standard_primitive_types: NotRequired[bool] + treat_dot_as_module: NotRequired[bool | None] + use_exact_imports: NotRequired[bool] + union_mode: NotRequired[UnionMode | Literal["smart", "left_to_right"] | None] + output_datetime_class: NotRequired[DatetimeClassType | None] + output_date_class: NotRequired[DateClassType | None] + keyword_only: NotRequired[bool] + frozen_dataclasses: NotRequired[bool] + dataclass_arguments: NotRequired[DataclassArguments | None] + no_alias: NotRequired[bool] + use_frozen_field: NotRequired[bool] + use_default_factory_for_optional_nested_models: NotRequired[bool] + formatters: NotRequired[list[Formatter]] + parent_scoped_naming: NotRequired[bool] + naming_strategy: NotRequired[NamingStrategy | None] + duplicate_name_suffix: NotRequired[dict[str, str] | None] + type_mappings: NotRequired[list[str] | None] + type_overrides: NotRequired[dict[str, str] | None] + read_only_write_only_model_type: NotRequired[ReadOnlyWriteOnlyModelType | None] + use_status_code_in_response_name: NotRequired[bool] + field_type_collision_strategy: NotRequired[FieldTypeCollisionStrategy | None] diff --git a/src/datamodel_code_generator/_types/generate_config_dict.py b/src/datamodel_code_generator/_types/generate_config_dict.py new file mode 100644 index 000000000..0539197ce --- /dev/null +++ b/src/datamodel_code_generator/_types/generate_config_dict.py @@ -0,0 +1,212 @@ +# generated by datamodel-codegen: +# filename: + +from __future__ import annotations + +from typing import Any, Literal, TypeAlias, TypedDict + +from typing_extensions import NotRequired + +AllExportsCollisionStrategy: TypeAlias = Literal["error", "minimal-prefix", "full-prefix"] + + +AllExportsScope: TypeAlias = Literal["children", "recursive"] + + +AllOfMergeMode: TypeAlias = Literal["constraints", "all", "none"] + + +CollapseRootModelsNameStrategy: TypeAlias = Literal["child", "parent"] + + +DataModelType: TypeAlias = Literal[ + "pydantic.BaseModel", + "pydantic_v2.BaseModel", + "pydantic_v2.dataclass", + "dataclasses.dataclass", + "typing.TypedDict", + "msgspec.Struct", +] + + +class DataclassArguments(TypedDict): + init: NotRequired[bool] + repr: NotRequired[bool] + eq: NotRequired[bool] + order: NotRequired[bool] + unsafe_hash: NotRequired[bool] + frozen: NotRequired[bool] + match_args: NotRequired[bool] + kw_only: NotRequired[bool] + slots: NotRequired[bool] + weakref_slot: NotRequired[bool] + + +DateClassType: TypeAlias = Literal["date", "PastDate", "FutureDate"] + + +DatetimeClassType: TypeAlias = Literal["datetime", "AwareDatetime", "NaiveDatetime", "PastDatetime", "FutureDatetime"] + + +FieldTypeCollisionStrategy: TypeAlias = Literal["rename-field", "rename-type"] + + +Formatter: TypeAlias = Literal["black", "isort", "ruff-check", "ruff-format"] + + +GraphQLScope: TypeAlias = Literal["schema"] + + +InputFileType: TypeAlias = Literal["auto", "openapi", "jsonschema", "json", "yaml", "dict", "csv", "graphql"] + + +LiteralType: TypeAlias = Literal["all", "one", "none"] + + +ModuleSplitMode: TypeAlias = Literal["single"] + + +NamingStrategy: TypeAlias = Literal["numbered", "parent-prefixed", "full-path", "primary-first"] + + +OpenAPIScope: TypeAlias = Literal["schemas", "paths", "tags", "parameters", "webhooks", "requestbodies"] + + +PythonVersion: TypeAlias = Literal["3.10", "3.11", "3.12", "3.13", "3.14"] + + +ReadOnlyWriteOnlyModelType: TypeAlias = Literal["request-response", "all"] + + +ReuseScope: TypeAlias = Literal["module", "tree"] + + +StrictTypes: TypeAlias = Literal["str", "bytes", "int", "float", "bool"] + + +TargetPydanticVersion: TypeAlias = Literal["2", "2.11"] + + +UnionMode: TypeAlias = Literal["smart", "left_to_right"] + + +class GenerateConfig(TypedDict): + output_model_type: NotRequired[DataModelType] + target_python_version: NotRequired[PythonVersion] + target_pydantic_version: NotRequired[TargetPydanticVersion | None] + base_class: NotRequired[str] + base_class_map: NotRequired[dict[str, str] | None] + additional_imports: NotRequired[list[str] | None] + class_decorators: NotRequired[list[str] | None] + custom_template_dir: NotRequired[str | None] + extra_template_data: NotRequired[dict[str, dict[str, Any]] | None] + validation: NotRequired[bool] + field_constraints: NotRequired[bool] + snake_case_field: NotRequired[bool] + strip_default_none: NotRequired[bool] + aliases: NotRequired[dict[str, str] | None] + allow_population_by_field_name: NotRequired[bool] + allow_extra_fields: NotRequired[bool] + extra_fields: NotRequired[str | None] + use_generic_base_class: NotRequired[bool] + apply_default_values_for_required_fields: NotRequired[bool] + force_optional_for_required_fields: NotRequired[bool] + class_name: NotRequired[str | None] + use_standard_collections: NotRequired[bool] + use_generic_container_types: NotRequired[bool] + use_union_operator: NotRequired[bool] + strict_nullable: NotRequired[bool] + strict_types: NotRequired[list[StrictTypes] | None] + use_schema_description: NotRequired[bool] + use_field_description: NotRequired[bool] + use_field_description_example: NotRequired[bool] + use_attribute_docstrings: NotRequired[bool] + use_inline_field_description: NotRequired[bool] + use_default_kwarg: NotRequired[bool] + reuse_model: NotRequired[bool] + reuse_scope: NotRequired[ReuseScope] + shared_module_name: NotRequired[str] + encoding: NotRequired[str] + enum_field_as_literal: NotRequired[LiteralType | None] + enum_field_as_literal_map: NotRequired[dict[str, str] | None] + ignore_enum_constraints: NotRequired[bool] + use_one_literal_as_default: NotRequired[bool] + use_enum_values_in_discriminator: NotRequired[bool] + set_default_enum_member: NotRequired[bool] + use_subclass_enum: NotRequired[bool] + use_specialized_enum: NotRequired[bool] + empty_enum_field_name: NotRequired[str | None] + capitalise_enum_members: NotRequired[bool] + enable_faux_immutability: NotRequired[bool] + disable_appending_item_suffix: NotRequired[bool] + special_field_name_prefix: NotRequired[str | None] + remove_special_field_name_prefix: NotRequired[bool] + field_extra_keys: NotRequired[list[str] | None] + field_include_all_keys: NotRequired[bool] + field_extra_keys_without_x_prefix: NotRequired[list[str] | None] + model_extra_keys: NotRequired[list[str] | None] + model_extra_keys_without_x_prefix: NotRequired[list[str] | None] + openapi_scopes: NotRequired[list[OpenAPIScope] | None] + include_path_parameters: NotRequired[bool] + graphql_scopes: NotRequired[list[GraphQLScope] | None] + wrap_string_literal: NotRequired[bool | None] + use_double_quotes: NotRequired[bool] + original_field_name_delimiter: NotRequired[str | None] + use_title_as_name: NotRequired[bool] + use_operation_id_as_name: NotRequired[bool] + use_unique_items_as_set: NotRequired[bool] + use_tuple_for_fixed_items: NotRequired[bool] + allof_merge_mode: NotRequired[AllOfMergeMode] + use_annotated: NotRequired[bool] + use_serialize_as_any: NotRequired[bool] + use_non_positive_negative_number_constrained_types: NotRequired[bool] + use_decimal_for_multiple_of: NotRequired[bool] + collapse_root_models: NotRequired[bool] + collapse_root_models_name_strategy: NotRequired[CollapseRootModelsNameStrategy | None] + collapse_reuse_models: NotRequired[bool] + skip_root_model: NotRequired[bool] + use_type_alias: NotRequired[bool] + use_root_model_type_alias: NotRequired[bool] + keep_model_order: NotRequired[bool] + custom_formatters: NotRequired[list[str] | None] + custom_formatters_kwargs: NotRequired[dict[str, Any] | None] + use_pendulum: NotRequired[bool] + use_standard_primitive_types: NotRequired[bool] + treat_dot_as_module: NotRequired[bool | None] + use_exact_imports: NotRequired[bool] + union_mode: NotRequired[UnionMode | Literal["smart", "left_to_right"] | None] + output_datetime_class: NotRequired[DatetimeClassType | None] + output_date_class: NotRequired[DateClassType | None] + keyword_only: NotRequired[bool] + frozen_dataclasses: NotRequired[bool] + dataclass_arguments: NotRequired[DataclassArguments | None] + no_alias: NotRequired[bool] + use_frozen_field: NotRequired[bool] + use_default_factory_for_optional_nested_models: NotRequired[bool] + formatters: NotRequired[list[Formatter]] + parent_scoped_naming: NotRequired[bool] + naming_strategy: NotRequired[NamingStrategy | None] + duplicate_name_suffix: NotRequired[dict[str, str] | None] + type_mappings: NotRequired[list[str] | None] + type_overrides: NotRequired[dict[str, str] | None] + read_only_write_only_model_type: NotRequired[ReadOnlyWriteOnlyModelType | None] + use_status_code_in_response_name: NotRequired[bool] + field_type_collision_strategy: NotRequired[FieldTypeCollisionStrategy | None] + input_filename: NotRequired[str | None] + input_file_type: NotRequired[InputFileType] + output: NotRequired[str | None] + disable_timestamp: NotRequired[bool] + enable_version_header: NotRequired[bool] + enable_command_header: NotRequired[bool] + command_line: NotRequired[str | None] + custom_file_header: NotRequired[str | None] + custom_file_header_path: NotRequired[str | None] + http_headers: NotRequired[list[tuple[str, str]] | None] + http_ignore_tls: NotRequired[bool] + http_timeout: NotRequired[float | None] + http_query_parameters: NotRequired[list[tuple[str, str]] | None] + settings_path: NotRequired[str | None] + disable_future_imports: NotRequired[bool] + all_exports_scope: NotRequired[AllExportsScope | None] + all_exports_collision_strategy: NotRequired[AllExportsCollisionStrategy | None] + module_split_mode: NotRequired[ModuleSplitMode | None] diff --git a/src/datamodel_code_generator/_types/parse_config_dict.py b/src/datamodel_code_generator/_types/parse_config_dict.py new file mode 100644 index 000000000..fffa1a4e4 --- /dev/null +++ b/src/datamodel_code_generator/_types/parse_config_dict.py @@ -0,0 +1,24 @@ +# generated by datamodel-codegen: +# filename: + +from __future__ import annotations + +from typing import Literal, TypeAlias, TypedDict + +from typing_extensions import NotRequired + +AllExportsCollisionStrategy: TypeAlias = Literal["error", "minimal-prefix", "full-prefix"] + + +AllExportsScope: TypeAlias = Literal["children", "recursive"] + + +ModuleSplitMode: TypeAlias = Literal["single"] + + +class ParseConfig(TypedDict): + settings_path: NotRequired[str | None] + disable_future_imports: NotRequired[bool] + all_exports_scope: NotRequired[AllExportsScope | None] + all_exports_collision_strategy: NotRequired[AllExportsCollisionStrategy | None] + module_split_mode: NotRequired[ModuleSplitMode | None] diff --git a/src/datamodel_code_generator/_types/parser_config_dict.py b/src/datamodel_code_generator/_types/parser_config_dict.py new file mode 100644 index 000000000..5247e8a2c --- /dev/null +++ b/src/datamodel_code_generator/_types/parser_config_dict.py @@ -0,0 +1,193 @@ +# generated by datamodel-codegen: +# filename: + +from __future__ import annotations + +from typing import Any, Literal, TypeAlias, TypedDict + +from typing_extensions import NotRequired + +AllOfMergeMode: TypeAlias = Literal["constraints", "all", "none"] + + +CollapseRootModelsNameStrategy: TypeAlias = Literal["child", "parent"] + + +DataModelType: TypeAlias = Literal[ + "pydantic.BaseModel", + "pydantic_v2.BaseModel", + "pydantic_v2.dataclass", + "dataclasses.dataclass", + "typing.TypedDict", + "msgspec.Struct", +] + + +class DataclassArguments(TypedDict): + init: NotRequired[bool] + repr: NotRequired[bool] + eq: NotRequired[bool] + order: NotRequired[bool] + unsafe_hash: NotRequired[bool] + frozen: NotRequired[bool] + match_args: NotRequired[bool] + kw_only: NotRequired[bool] + slots: NotRequired[bool] + weakref_slot: NotRequired[bool] + + +DateClassType: TypeAlias = Literal["date", "PastDate", "FutureDate"] + + +DatetimeClassType: TypeAlias = Literal["datetime", "AwareDatetime", "NaiveDatetime", "PastDatetime", "FutureDatetime"] + + +FieldTypeCollisionStrategy: TypeAlias = Literal["rename-field", "rename-type"] + + +Formatter: TypeAlias = Literal["black", "isort", "ruff-check", "ruff-format"] + + +GraphQLScope: TypeAlias = Literal["schema"] + + +LiteralType: TypeAlias = Literal["all", "one", "none"] + + +NamingStrategy: TypeAlias = Literal["numbered", "parent-prefixed", "full-path", "primary-first"] + + +OpenAPIScope: TypeAlias = Literal["schemas", "paths", "tags", "parameters", "webhooks", "requestbodies"] + + +PythonVersion: TypeAlias = Literal["3.10", "3.11", "3.12", "3.13", "3.14"] + + +ReadOnlyWriteOnlyModelType: TypeAlias = Literal["request-response", "all"] + + +ReuseScope: TypeAlias = Literal["module", "tree"] + + +StrictTypes: TypeAlias = Literal["str", "bytes", "int", "float", "bool"] + + +TargetPydanticVersion: TypeAlias = Literal["2", "2.11"] + + +UnionMode: TypeAlias = Literal["smart", "left_to_right"] + + +class ParserConfig(TypedDict): + output_model_type: NotRequired[DataModelType] + target_python_version: NotRequired[PythonVersion] + target_pydantic_version: NotRequired[TargetPydanticVersion | None] + base_class: NotRequired[str] + base_class_map: NotRequired[dict[str, str] | None] + additional_imports: NotRequired[list[str] | None] + class_decorators: NotRequired[list[str] | None] + custom_template_dir: NotRequired[str | None] + extra_template_data: NotRequired[dict[str, dict[str, Any]] | None] + validation: NotRequired[bool] + field_constraints: NotRequired[bool] + snake_case_field: NotRequired[bool] + strip_default_none: NotRequired[bool] + aliases: NotRequired[dict[str, str] | None] + allow_population_by_field_name: NotRequired[bool] + allow_extra_fields: NotRequired[bool] + extra_fields: NotRequired[str | None] + use_generic_base_class: NotRequired[bool] + apply_default_values_for_required_fields: NotRequired[bool] + force_optional_for_required_fields: NotRequired[bool] + class_name: NotRequired[str | None] + use_standard_collections: NotRequired[bool] + use_generic_container_types: NotRequired[bool] + use_union_operator: NotRequired[bool] + strict_nullable: NotRequired[bool] + strict_types: NotRequired[list[StrictTypes] | None] + use_schema_description: NotRequired[bool] + use_field_description: NotRequired[bool] + use_field_description_example: NotRequired[bool] + use_attribute_docstrings: NotRequired[bool] + use_inline_field_description: NotRequired[bool] + use_default_kwarg: NotRequired[bool] + reuse_model: NotRequired[bool] + reuse_scope: NotRequired[ReuseScope] + shared_module_name: NotRequired[str] + encoding: NotRequired[str] + enum_field_as_literal: NotRequired[LiteralType | None] + enum_field_as_literal_map: NotRequired[dict[str, str] | None] + ignore_enum_constraints: NotRequired[bool] + use_one_literal_as_default: NotRequired[bool] + use_enum_values_in_discriminator: NotRequired[bool] + set_default_enum_member: NotRequired[bool] + use_subclass_enum: NotRequired[bool] + use_specialized_enum: NotRequired[bool] + empty_enum_field_name: NotRequired[str | None] + capitalise_enum_members: NotRequired[bool] + enable_faux_immutability: NotRequired[bool] + disable_appending_item_suffix: NotRequired[bool] + special_field_name_prefix: NotRequired[str | None] + remove_special_field_name_prefix: NotRequired[bool] + field_extra_keys: NotRequired[list[str] | None] + field_include_all_keys: NotRequired[bool] + field_extra_keys_without_x_prefix: NotRequired[list[str] | None] + model_extra_keys: NotRequired[list[str] | None] + model_extra_keys_without_x_prefix: NotRequired[list[str] | None] + openapi_scopes: NotRequired[list[OpenAPIScope] | None] + include_path_parameters: NotRequired[bool] + graphql_scopes: NotRequired[list[GraphQLScope] | None] + wrap_string_literal: NotRequired[bool | None] + use_double_quotes: NotRequired[bool] + original_field_name_delimiter: NotRequired[str | None] + use_title_as_name: NotRequired[bool] + use_operation_id_as_name: NotRequired[bool] + use_unique_items_as_set: NotRequired[bool] + use_tuple_for_fixed_items: NotRequired[bool] + allof_merge_mode: NotRequired[AllOfMergeMode] + use_annotated: NotRequired[bool] + use_serialize_as_any: NotRequired[bool] + use_non_positive_negative_number_constrained_types: NotRequired[bool] + use_decimal_for_multiple_of: NotRequired[bool] + collapse_root_models: NotRequired[bool] + collapse_root_models_name_strategy: NotRequired[CollapseRootModelsNameStrategy | None] + collapse_reuse_models: NotRequired[bool] + skip_root_model: NotRequired[bool] + use_type_alias: NotRequired[bool] + use_root_model_type_alias: NotRequired[bool] + keep_model_order: NotRequired[bool] + custom_formatters: NotRequired[list[str] | None] + custom_formatters_kwargs: NotRequired[dict[str, Any] | None] + use_pendulum: NotRequired[bool] + use_standard_primitive_types: NotRequired[bool] + treat_dot_as_module: NotRequired[bool | None] + use_exact_imports: NotRequired[bool] + union_mode: NotRequired[UnionMode | Literal["smart", "left_to_right"] | None] + output_datetime_class: NotRequired[DatetimeClassType | None] + output_date_class: NotRequired[DateClassType | None] + keyword_only: NotRequired[bool] + frozen_dataclasses: NotRequired[bool] + dataclass_arguments: NotRequired[DataclassArguments | None] + no_alias: NotRequired[bool] + use_frozen_field: NotRequired[bool] + use_default_factory_for_optional_nested_models: NotRequired[bool] + formatters: NotRequired[list[Formatter]] + parent_scoped_naming: NotRequired[bool] + naming_strategy: NotRequired[NamingStrategy | None] + duplicate_name_suffix: NotRequired[dict[str, str] | None] + type_mappings: NotRequired[list[str] | None] + type_overrides: NotRequired[dict[str, str] | None] + read_only_write_only_model_type: NotRequired[ReadOnlyWriteOnlyModelType | None] + use_status_code_in_response_name: NotRequired[bool] + field_type_collision_strategy: NotRequired[FieldTypeCollisionStrategy | None] + base_path: NotRequired[str | None] + allow_responses_without_content: NotRequired[bool] + known_third_party: NotRequired[list[str] | None] + defer_formatting: NotRequired[bool] + target_datetime_class: NotRequired[DatetimeClassType | None] + target_date_class: NotRequired[DateClassType | None] + default_field_extras: NotRequired[dict[str, Any] | None] + http_headers: NotRequired[list[tuple[str, str]] | None] + http_ignore_tls: NotRequired[bool] + http_timeout: NotRequired[float | None] + http_query_parameters: NotRequired[list[tuple[str, str]] | None] diff --git a/src/datamodel_code_generator/config.py b/src/datamodel_code_generator/config.py new file mode 100644 index 000000000..6ef363faa --- /dev/null +++ b/src/datamodel_code_generator/config.py @@ -0,0 +1,235 @@ +"""Configuration models for datamodel-code-generator. + +Provides unified configuration classes for generate(), Parser, and parse() functions. +These models consolidate the 120+ parameters into structured, type-safe configurations. +""" + +from __future__ import annotations + +from collections.abc import Callable, Mapping, Sequence +from pathlib import Path +from typing import TYPE_CHECKING, Any, Literal + +from pydantic import BaseModel, ConfigDict + +if TYPE_CHECKING: + from pydantic.json_schema import SkipJsonSchema +else: + # SkipJsonSchema is pydantic v2 only - for v1 we use identity via __class_getitem__ + try: + from pydantic.json_schema import SkipJsonSchema + except ImportError: + + class SkipJsonSchema: # noqa: D101 + def __class_getitem__(cls, item: Any) -> Any: # noqa: D105 + return item + + +from datamodel_code_generator.enums import ( + DEFAULT_SHARED_MODULE_NAME, + AllExportsCollisionStrategy, + AllExportsScope, + AllOfMergeMode, + CollapseRootModelsNameStrategy, + DataclassArguments, + DataModelType, + FieldTypeCollisionStrategy, + GraphQLScope, + InputFileType, + ModuleSplitMode, + NamingStrategy, + OpenAPIScope, + ReadOnlyWriteOnlyModelType, + ReuseScope, + TargetPydanticVersion, + UnionMode, +) +from datamodel_code_generator.format import ( + DEFAULT_FORMATTERS, + DateClassType, + DatetimeClassType, + Formatter, + PythonVersion, + PythonVersionMin, +) +from datamodel_code_generator.parser import LiteralType +from datamodel_code_generator.types import StrictTypes + +# Accept both UnionMode enum and literal string values +UnionModeType = UnionMode | Literal["smart", "left_to_right"] | None + + +class BaseConfig(BaseModel): + """Base configuration with common parameters shared across generate() and Parser. + + Contains ~88 parameters that are used by both the generate() function and + the Parser class. These are the core code generation settings. + """ + + model_config = ConfigDict(extra="forbid") + + output_model_type: DataModelType = DataModelType.PydanticBaseModel + target_python_version: PythonVersion = PythonVersionMin + target_pydantic_version: TargetPydanticVersion | None = None + base_class: str = "" + base_class_map: dict[str, str] | None = None + additional_imports: list[str] | None = None + class_decorators: list[str] | None = None + custom_template_dir: Path | None = None + extra_template_data: dict[str, dict[str, Any]] | None = None + validation: bool = False + field_constraints: bool = False + snake_case_field: bool = False + strip_default_none: bool = False + aliases: Mapping[str, str] | None = None + allow_population_by_field_name: bool = False + allow_extra_fields: bool = False + extra_fields: str | None = None + use_generic_base_class: bool = False + apply_default_values_for_required_fields: bool = False + force_optional_for_required_fields: bool = False + class_name: str | None = None + use_standard_collections: bool = True + use_generic_container_types: bool = False + use_union_operator: bool = True + strict_nullable: bool = False + strict_types: Sequence[StrictTypes] | None = None + use_schema_description: bool = False + use_field_description: bool = False + use_field_description_example: bool = False + use_attribute_docstrings: bool = False + use_inline_field_description: bool = False + use_default_kwarg: bool = False + reuse_model: bool = False + reuse_scope: ReuseScope = ReuseScope.Module + shared_module_name: str = DEFAULT_SHARED_MODULE_NAME + encoding: str = "utf-8" + enum_field_as_literal: LiteralType | None = None + enum_field_as_literal_map: dict[str, str] | None = None + ignore_enum_constraints: bool = False + use_one_literal_as_default: bool = False + use_enum_values_in_discriminator: bool = False + set_default_enum_member: bool = False + use_subclass_enum: bool = False + use_specialized_enum: bool = True + empty_enum_field_name: str | None = None + capitalise_enum_members: bool = False + enable_faux_immutability: bool = False + disable_appending_item_suffix: bool = False + custom_class_name_generator: SkipJsonSchema[Callable[[str], str]] | None = None + special_field_name_prefix: str | None = None + remove_special_field_name_prefix: bool = False + field_extra_keys: set[str] | None = None + field_include_all_keys: bool = False + field_extra_keys_without_x_prefix: set[str] | None = None + model_extra_keys: set[str] | None = None + model_extra_keys_without_x_prefix: set[str] | None = None + openapi_scopes: list[OpenAPIScope] | None = None + include_path_parameters: bool = False + graphql_scopes: list[GraphQLScope] | None = None + wrap_string_literal: bool | None = None + use_double_quotes: bool = False + original_field_name_delimiter: str | None = None + use_title_as_name: bool = False + use_operation_id_as_name: bool = False + use_unique_items_as_set: bool = False + use_tuple_for_fixed_items: bool = False + allof_merge_mode: AllOfMergeMode = AllOfMergeMode.Constraints + use_annotated: bool = False + use_serialize_as_any: bool = False + use_non_positive_negative_number_constrained_types: bool = False + use_decimal_for_multiple_of: bool = False + collapse_root_models: bool = False + collapse_root_models_name_strategy: CollapseRootModelsNameStrategy | None = None + collapse_reuse_models: bool = False + skip_root_model: bool = False + use_type_alias: bool = False + use_root_model_type_alias: bool = False + keep_model_order: bool = False + custom_formatters: list[str] | None = None + custom_formatters_kwargs: dict[str, Any] | None = None + use_pendulum: bool = False + use_standard_primitive_types: bool = False + treat_dot_as_module: bool | None = None + use_exact_imports: bool = False + union_mode: UnionModeType = None + output_datetime_class: DatetimeClassType | None = None + output_date_class: DateClassType | None = None + keyword_only: bool = False + frozen_dataclasses: bool = False + dataclass_arguments: DataclassArguments | None = None + no_alias: bool = False + use_frozen_field: bool = False + use_default_factory_for_optional_nested_models: bool = False + formatters: list[Formatter] = DEFAULT_FORMATTERS + parent_scoped_naming: bool = False + naming_strategy: NamingStrategy | None = None + duplicate_name_suffix: dict[str, str] | None = None + type_mappings: list[str] | None = None + type_overrides: dict[str, str] | None = None + read_only_write_only_model_type: ReadOnlyWriteOnlyModelType | None = None + use_status_code_in_response_name: bool = False + field_type_collision_strategy: FieldTypeCollisionStrategy | None = None + + +class GenerateConfig(BaseConfig): + """Configuration for the generate() function. + + Extends BaseConfig with generate()-specific parameters for input handling, + output routing, headers, and HTTP settings. + """ + + input_filename: str | None = None + input_file_type: InputFileType = InputFileType.Auto + output: Path | None = None + disable_timestamp: bool = False + enable_version_header: bool = False + enable_command_header: bool = False + command_line: str | None = None + custom_file_header: str | None = None + custom_file_header_path: Path | None = None + http_headers: Sequence[tuple[str, str]] | None = None + http_ignore_tls: bool = False + http_timeout: float | None = None + http_query_parameters: Sequence[tuple[str, str]] | None = None + settings_path: Path | None = None + disable_future_imports: bool = False + all_exports_scope: AllExportsScope | None = None + all_exports_collision_strategy: AllExportsCollisionStrategy | None = None + module_split_mode: ModuleSplitMode | None = None + + +class ParserConfig(BaseConfig): + """Configuration for Parser.__init__(). + + Extends BaseConfig with Parser-specific internal parameters + for the Parser class initialization. + """ + + base_path: Path | None = None + allow_responses_without_content: bool = False + known_third_party: list[str] | None = None + defer_formatting: bool = False + target_datetime_class: DatetimeClassType | None = None + target_date_class: DateClassType | None = None + default_field_extras: dict[str, Any] | None = None + http_headers: Sequence[tuple[str, str]] | None = None + http_ignore_tls: bool = False + http_timeout: float | None = None + http_query_parameters: Sequence[tuple[str, str]] | None = None + + +class ParseConfig(BaseModel): + """Configuration for Parser.parse() method. + + Contains settings that are applied after parser construction, + during the actual parsing phase. + """ + + model_config = ConfigDict(extra="forbid") + + settings_path: Path | None = None + disable_future_imports: bool = False + all_exports_scope: AllExportsScope | None = None + all_exports_collision_strategy: AllExportsCollisionStrategy | None = None + module_split_mode: ModuleSplitMode | None = None diff --git a/src/datamodel_code_generator/model/__init__.py b/src/datamodel_code_generator/model/__init__.py index 1d981ad0e..41e076e24 100644 --- a/src/datamodel_code_generator/model/__init__.py +++ b/src/datamodel_code_generator/model/__init__.py @@ -9,7 +9,7 @@ import sys from typing import TYPE_CHECKING, NamedTuple -from datamodel_code_generator import PythonVersion +from datamodel_code_generator.format import PythonVersion from .base import ConstraintsBase, DataModel, DataModelFieldBase diff --git a/src/datamodel_code_generator/model/msgspec.py b/src/datamodel_code_generator/model/msgspec.py index d59fde1f3..f6deb86a4 100644 --- a/src/datamodel_code_generator/model/msgspec.py +++ b/src/datamodel_code_generator/model/msgspec.py @@ -10,7 +10,7 @@ from pydantic import Field -from datamodel_code_generator import DateClassType, DatetimeClassType, PythonVersion, PythonVersionMin +from datamodel_code_generator.format import DateClassType, DatetimeClassType, PythonVersion, PythonVersionMin from datamodel_code_generator.imports import ( IMPORT_DATE, IMPORT_DATETIME, diff --git a/src/datamodel_code_generator/model/types.py b/src/datamodel_code_generator/model/types.py index 7e75fbf00..476298c8e 100644 --- a/src/datamodel_code_generator/model/types.py +++ b/src/datamodel_code_generator/model/types.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any, ClassVar -from datamodel_code_generator import DateClassType, DatetimeClassType, PythonVersion, PythonVersionMin +from datamodel_code_generator.format import DateClassType, DatetimeClassType, PythonVersion, PythonVersionMin from datamodel_code_generator.imports import ( IMPORT_ANY, IMPORT_DECIMAL, diff --git a/tests/main/test_main_general.py b/tests/main/test_main_general.py index 769781b97..65d59b526 100644 --- a/tests/main/test_main_general.py +++ b/tests/main/test_main_general.py @@ -1868,3 +1868,56 @@ def test_generate_with_dict_raw_data_types_raises_error(input_file_type: InputFi with pytest.raises(Error, match=f"Dict input is not supported for {input_file_type.value}"): generate(auto_error_dict, input_file_type=input_file_type) + + +def test_generate_with_config_parameter() -> None: + """Test generate() with config parameter extracts all values correctly.""" + from datamodel_code_generator import GenerateConfig + + json_schema = '{"type": "object", "properties": {"user_name": {"type": "string"}}}' + config = GenerateConfig( + input_file_type=InputFileType.JsonSchema, + snake_case_field=True, + disable_timestamp=True, + ) + result = generate(json_schema, config=config) + assert result == snapshot("""\ +# generated by datamodel-codegen: +# filename: + +from __future__ import annotations + +from pydantic import BaseModel + + +class Model(BaseModel): + user_name: str | None = None\ +""") + + +def test_generate_config_lazy_import() -> None: + """Test that GenerateConfig can be imported via __getattr__ lazy import.""" + import datamodel_code_generator + + config_class = datamodel_code_generator.GenerateConfig + assert config_class is not None + assert config_class.__name__ == "GenerateConfig" + + +def test_module_getattr_raises_for_unknown_attribute() -> None: + """Test that __getattr__ raises AttributeError for unknown attributes.""" + import datamodel_code_generator + + with pytest.raises(AttributeError, match="module 'datamodel_code_generator' has no attribute 'NonExistentClass'"): + _ = datamodel_code_generator.NonExistentClass + + +def test_generate_with_config_and_options_raises_error() -> None: + """Test that providing both config and options raises ValueError.""" + from datamodel_code_generator import GenerateConfig + + json_schema = '{"type": "object", "properties": {"name": {"type": "string"}}}' + config = GenerateConfig(input_file_type=InputFileType.JsonSchema) + + with pytest.raises(ValueError, match="Cannot specify both 'config' and individual options"): + generate(json_schema, config=config, snake_case_field=True) diff --git a/tox.ini b/tox.ini index be296e36a..1efbeca87 100644 --- a/tox.ini +++ b/tox.ini @@ -112,6 +112,16 @@ commands = check-wheel-contents --no-config {env_tmp_dir} dependency_groups = pkg-meta +[testenv:config-types] +description = Generate TypedDict from Config models (dogfooding) +commands = + datamodel-codegen --profile base-config-dict + datamodel-codegen --profile generate-config-dict + datamodel-codegen --profile parser-config-dict + datamodel-codegen --profile parse-config-dict +dependency_groups = dev +no_default_groups = true + [testenv:type] description = run type check on code base commands =