diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 235330a6a..d9c96f8cb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,7 +39,7 @@ repos: pass_filenames: false - id: config-types name: Generate config TypedDicts - entry: bash -c '[ -x .tox/dev/bin/python ] && PREFIX=.tox/dev/bin || PREFIX=.tox/dev/Scripts; test -x "$PREFIX/python" || tox run -e dev --notest -qq; "$PREFIX/datamodel-codegen" --profile generate-config-dict && "$PREFIX/datamodel-codegen" --profile parser-config-dict && "$PREFIX/datamodel-codegen" --profile parse-config-dict' + entry: bash -c '[ -x .tox/dev/bin/python ] && PREFIX=.tox/dev/bin || PREFIX=.tox/dev/Scripts; test -x "$PREFIX/python" || tox run -e dev --notest -qq; "$PREFIX/datamodel-codegen" --profile generate-config-dict && "$PREFIX/datamodel-codegen" --profile parser-config-dict && "$PREFIX/datamodel-codegen" --profile graphql-parser-config-dict && "$PREFIX/datamodel-codegen" --profile jsonschema-parser-config-dict && "$PREFIX/datamodel-codegen" --profile openapi-parser-config-dict && "$PREFIX/datamodel-codegen" --profile parse-config-dict' language: system files: ^src/datamodel_code_generator/config\.py$ pass_filenames: false diff --git a/pyproject.toml b/pyproject.toml index 9c488419f..a0f21fabd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -280,6 +280,24 @@ input-model = "src/datamodel_code_generator/config.py:ParserConfig" output = "src/datamodel_code_generator/_types/parser_config_dict.py" class-name = "ParserConfigDict" +[tool.datamodel-codegen.profiles.graphql-parser-config-dict] +extends = "config-types-base" +input-model = "src/datamodel_code_generator/config.py:GraphQLParserConfig" +output = "src/datamodel_code_generator/_types/graphql_parser_config_dict.py" +class-name = "GraphQLParserConfigDict" + +[tool.datamodel-codegen.profiles.jsonschema-parser-config-dict] +extends = "config-types-base" +input-model = "src/datamodel_code_generator/config.py:JSONSchemaParserConfig" +output = "src/datamodel_code_generator/_types/jsonschema_parser_config_dict.py" +class-name = "JSONSchemaParserConfigDict" + +[tool.datamodel-codegen.profiles.openapi-parser-config-dict] +extends = "config-types-base" +input-model = "src/datamodel_code_generator/config.py:OpenAPIParserConfig" +output = "src/datamodel_code_generator/_types/openapi_parser_config_dict.py" +class-name = "OpenAPIParserConfigDict" + [tool.datamodel-codegen.profiles.parse-config-dict] extends = "config-types-base" input-model = "src/datamodel_code_generator/config.py:ParseConfig" diff --git a/src/datamodel_code_generator/__init__.py b/src/datamodel_code_generator/__init__.py index 435f07d49..3c28ff7c0 100644 --- a/src/datamodel_code_generator/__init__.py +++ b/src/datamodel_code_generator/__init__.py @@ -493,129 +493,14 @@ def generate( # noqa: PLR0912, PLR0914, PLR0915 GenerateConfig.update_forward_refs(StrictTypes=StrictTypes, UnionMode=UnionMode) config = GenerateConfig(**options) - # Extract config values to local variables for use throughout the function + # Variables that may be modified during processing 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 = None if config.extra_template_data is not None: extra_template_data = defaultdict(dict, config.extra_template_data) - 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 - allof_class_hierarchy = config.allof_class_hierarchy - 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 = 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 + custom_file_header = config.custom_file_header remote_text_cache: DefaultPutDict[str, str] = DefaultPutDict() match input_: @@ -624,11 +509,15 @@ def generate( # noqa: PLR0912, PLR0914, PLR0915 case ParseResult(): from datamodel_code_generator.http import DEFAULT_HTTP_TIMEOUT, get_body # noqa: PLC0415 - timeout = http_timeout if http_timeout is not None else DEFAULT_HTTP_TIMEOUT + timeout = config.http_timeout if config.http_timeout is not None else DEFAULT_HTTP_TIMEOUT input_text = remote_text_cache.get_or_put( input_.geturl(), default_factory=lambda url: get_body( - url, http_headers, http_ignore_tls, http_query_parameters, timeout + url, + config.http_headers, + config.http_ignore_tls, + config.http_query_parameters, + timeout, ), ) case _: @@ -636,9 +525,9 @@ def generate( # noqa: PLR0912, PLR0914, PLR0915 if dataclass_arguments is None: dataclass_arguments = DataclassArguments() - if frozen_dataclasses: + if config.frozen_dataclasses: dataclass_arguments["frozen"] = True - if keyword_only: + if config.keyword_only: dataclass_arguments["kw_only"] = True if isinstance(input_, Path) and not input_.is_absolute(): @@ -668,9 +557,10 @@ def generate( # noqa: PLR0912, PLR0914, PLR0915 if input_file_type == InputFileType.Auto: try: - input_text_ = ( - get_first_file(input_).read_text(encoding=encoding) if isinstance(input_, Path) else input_text - ) + if isinstance(input_, Path): + input_text_ = get_first_file(input_).read_text(encoding=config.encoding) + else: + input_text_ = input_text except FileNotFoundError as exc: msg = "File not found" raise Error(msg) from exc @@ -695,9 +585,9 @@ def generate( # noqa: PLR0912, PLR0914, PLR0915 from datamodel_code_generator.parser.openapi import OpenAPIParser # noqa: PLC0415 parser_class: type[Parser] = OpenAPIParser - kwargs["openapi_scopes"] = openapi_scopes - kwargs["include_path_parameters"] = include_path_parameters - kwargs["use_status_code_in_response_name"] = use_status_code_in_response_name + kwargs["openapi_scopes"] = config.openapi_scopes + kwargs["include_path_parameters"] = config.include_path_parameters + kwargs["use_status_code_in_response_name"] = config.use_status_code_in_response_name elif input_file_type == InputFileType.GraphQL: from datamodel_code_generator.parser.graphql import GraphQLParser # noqa: PLC0415 @@ -724,7 +614,7 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]: return dict(zip(csv_reader.fieldnames, next(csv_reader), strict=False)) if isinstance(input_, Path): - with input_.open(encoding=encoding) as f: + with input_.open(encoding=config.encoding) as f: obj = get_header_and_first_line(f) else: import io # noqa: PLC0415 @@ -732,13 +622,13 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]: obj = get_header_and_first_line(io.StringIO(input_text)) elif input_file_type == InputFileType.Yaml: if isinstance(input_, Path): - obj = load_yaml_dict(input_.read_text(encoding=encoding)) + obj = load_yaml_dict(input_.read_text(encoding=config.encoding)) else: # pragma: no cover assert input_text is not None obj = load_yaml_dict(input_text) elif input_file_type == InputFileType.Json: if isinstance(input_, Path): - obj = json.loads(input_.read_text(encoding=encoding)) + obj = json.loads(input_.read_text(encoding=config.encoding)) else: assert input_text is not None obj = json.loads(input_text) @@ -747,7 +637,7 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]: # Input can be a dict object stored in a python file obj = ( - ast.literal_eval(input_.read_text(encoding=encoding)) + ast.literal_eval(input_.read_text(encoding=config.encoding)) if isinstance(input_, Path) else cast("dict[str, Any]", input_) ) @@ -768,9 +658,9 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]: if isinstance(input_, ParseResult) and input_file_type not in RAW_DATA_TYPES: input_text = None - if union_mode is not None: - if output_model_type == DataModelType.PydanticV2BaseModel: - default_field_extras = {"union_mode": union_mode} + if config.union_mode is not None: + if config.output_model_type == DataModelType.PydanticV2BaseModel: + default_field_extras = {"union_mode": config.union_mode} else: # pragma: no cover msg = "union_mode is only supported for pydantic_v2.BaseModel" raise Error(msg) @@ -780,10 +670,10 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]: from datamodel_code_generator.model import get_data_model_types # noqa: PLC0415 data_model_types = get_data_model_types( - output_model_type, - target_python_version, - use_type_alias=use_type_alias, - use_root_model_type_alias=use_root_model_type_alias, + config.output_model_type, + config.target_python_version, + use_type_alias=config.use_type_alias, + use_root_model_type_alias=config.use_root_model_type_alias, ) # Add GraphQL-specific model types if needed @@ -797,7 +687,7 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]: source = input_text or input_ assert not isinstance(source, Mapping) - defer_formatting = output is not None and not output.suffix + defer_formatting = config.output is not None and not config.output.suffix parser = parser_class( source=source, @@ -805,126 +695,126 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]: data_model_root_type=data_model_types.root_model, data_model_field_type=data_model_types.field_model, data_type_manager_type=data_model_types.data_type_manager, - base_class=base_class, - base_class_map=base_class_map, - additional_imports=additional_imports, - class_decorators=class_decorators, - custom_template_dir=custom_template_dir, + 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=extra_template_data, - target_python_version=target_python_version, + target_python_version=config.target_python_version, dump_resolve_reference_action=data_model_types.dump_resolve_reference_action, - validation=validation, - field_constraints=field_constraints, - snake_case_field=snake_case_field, - strip_default_none=strip_default_none, - aliases=aliases, - allow_population_by_field_name=allow_population_by_field_name, - allow_extra_fields=allow_extra_fields, - extra_fields=extra_fields, - use_generic_base_class=use_generic_base_class, - apply_default_values_for_required_fields=apply_default_values_for_required_fields, - force_optional_for_required_fields=force_optional_for_required_fields, - class_name=class_name, - use_standard_collections=use_standard_collections, + 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, + 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, base_path=input_.parent if isinstance(input_, Path) and input_.is_file() else None, - use_schema_description=use_schema_description, - use_field_description=use_field_description, - use_field_description_example=use_field_description_example, - use_attribute_docstrings=use_attribute_docstrings, - use_inline_field_description=use_inline_field_description, - use_default_kwarg=use_default_kwarg, - reuse_model=reuse_model, - reuse_scope=reuse_scope, - shared_module_name=shared_module_name, - enum_field_as_literal=enum_field_as_literal - if enum_field_as_literal is not None - else (LiteralType.All if output_model_type == DataModelType.TypingTypedDict else None), - enum_field_as_literal_map=enum_field_as_literal_map, - ignore_enum_constraints=ignore_enum_constraints, - use_one_literal_as_default=use_one_literal_as_default, - use_enum_values_in_discriminator=use_enum_values_in_discriminator, + 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, + enum_field_as_literal=config.enum_field_as_literal + if config.enum_field_as_literal is not None + else (LiteralType.All if config.output_model_type == DataModelType.TypingTypedDict else None), + 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=True - if output_model_type == DataModelType.DataclassesDataclass - else set_default_enum_member, - use_subclass_enum=use_subclass_enum, - use_specialized_enum=use_specialized_enum, - strict_nullable=strict_nullable, - use_generic_container_types=use_generic_container_types, - enable_faux_immutability=enable_faux_immutability, + if config.output_model_type == DataModelType.DataclassesDataclass + else 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, remote_text_cache=remote_text_cache, - disable_appending_item_suffix=disable_appending_item_suffix, - strict_types=strict_types, - empty_enum_field_name=empty_enum_field_name, - custom_class_name_generator=custom_class_name_generator, - field_extra_keys=field_extra_keys, - field_include_all_keys=field_include_all_keys, - field_extra_keys_without_x_prefix=field_extra_keys_without_x_prefix, - model_extra_keys=model_extra_keys, - model_extra_keys_without_x_prefix=model_extra_keys_without_x_prefix, - wrap_string_literal=wrap_string_literal, - use_title_as_name=use_title_as_name, - use_operation_id_as_name=use_operation_id_as_name, - use_unique_items_as_set=use_unique_items_as_set, - use_tuple_for_fixed_items=use_tuple_for_fixed_items, - allof_merge_mode=allof_merge_mode, - allof_class_hierarchy=allof_class_hierarchy, - http_headers=http_headers, - http_ignore_tls=http_ignore_tls, - http_timeout=http_timeout, - use_annotated=use_annotated, - use_serialize_as_any=use_serialize_as_any, - use_non_positive_negative_number_constrained_types=use_non_positive_negative_number_constrained_types, - use_decimal_for_multiple_of=use_decimal_for_multiple_of, - original_field_name_delimiter=original_field_name_delimiter, - use_double_quotes=use_double_quotes, - use_union_operator=use_union_operator, - collapse_root_models=collapse_root_models, - collapse_root_models_name_strategy=collapse_root_models_name_strategy, - collapse_reuse_models=collapse_reuse_models, - skip_root_model=skip_root_model, - use_type_alias=use_type_alias, - special_field_name_prefix=special_field_name_prefix, - remove_special_field_name_prefix=remove_special_field_name_prefix, - capitalise_enum_members=capitalise_enum_members, - keep_model_order=keep_model_order, + 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, + 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, + allof_class_hierarchy=config.allof_class_hierarchy, + 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, + 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, known_third_party=data_model_types.known_third_party, - 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, + 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, default_field_extras=default_field_extras, - target_datetime_class=output_datetime_class, - target_date_class=output_date_class, - keyword_only=keyword_only, - frozen_dataclasses=frozen_dataclasses, - no_alias=no_alias, - use_frozen_field=use_frozen_field, - use_default_factory_for_optional_nested_models=use_default_factory_for_optional_nested_models, - formatters=formatters, + target_datetime_class=config.output_datetime_class, + target_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, defer_formatting=defer_formatting, - encoding=encoding, - parent_scoped_naming=parent_scoped_naming, - naming_strategy=naming_strategy, - duplicate_name_suffix=duplicate_name_suffix, + encoding=config.encoding, + parent_scoped_naming=config.parent_scoped_naming, + naming_strategy=config.naming_strategy, + duplicate_name_suffix=config.duplicate_name_suffix, dataclass_arguments=dataclass_arguments, - type_mappings=type_mappings, - type_overrides=type_overrides, - read_only_write_only_model_type=read_only_write_only_model_type, - field_type_collision_strategy=field_type_collision_strategy, - target_pydantic_version=target_pydantic_version, + type_mappings=config.type_mappings, + type_overrides=config.type_overrides, + read_only_write_only_model_type=config.read_only_write_only_model_type, + field_type_collision_strategy=config.field_type_collision_strategy, + target_pydantic_version=config.target_pydantic_version, **kwargs, ) - with chdir(output): + with chdir(config.output): results = parser.parse( - settings_path=settings_path, - disable_future_imports=disable_future_imports, - all_exports_scope=all_exports_scope, - all_exports_collision_strategy=all_exports_collision_strategy, - module_split_mode=module_split_mode, + settings_path=config.settings_path, + disable_future_imports=config.disable_future_imports, + all_exports_scope=config.all_exports_scope, + all_exports_collision_strategy=config.all_exports_collision_strategy, + module_split_mode=config.module_split_mode, ) if not input_filename: # pragma: no cover match input_: @@ -942,22 +832,22 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]: raise Error(msg) timestamp = datetime.now(timezone.utc).replace(microsecond=0).isoformat() - if custom_file_header is None and custom_file_header_path: - custom_file_header = custom_file_header_path.read_text(encoding=encoding) + if custom_file_header is None and config.custom_file_header_path: + custom_file_header = config.custom_file_header_path.read_text(encoding=config.encoding) header = """\ # generated by datamodel-codegen: # filename: {}""" - if not disable_timestamp: + if not config.disable_timestamp: header += f"\n# timestamp: {timestamp}" - if enable_version_header: + if config.enable_version_header: header += f"\n# version: {get_version()}" - if enable_command_header and command_line: - safe_command_line = command_line.replace("\n", " ").replace("\r", " ") + if config.enable_command_header and config.command_line: + safe_command_line = config.command_line.replace("\n", " ").replace("\r", " ") header += f"\n# command: {safe_command_line}" # When output is None, return generated code as string(s) instead of writing to files - if output is None: + if config.output is None: if isinstance(results, str): # Single-file output: return str safe_filename = input_filename.replace("\n", " ").replace("\r", " ") if input_filename else "" @@ -973,6 +863,7 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]: return generated # When output is a Path, write to file system + output = config.output if isinstance(results, str): # Single-file output: body already contains future imports body = results @@ -995,7 +886,7 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]: for path, (body, future_imports, filename) in modules.items(): if not path.parent.exists(): path.parent.mkdir(parents=True) - file = path.open("wt", encoding=encoding) + file = path.open("wt", encoding=config.encoding) safe_filename = filename.replace("\n", " ").replace("\r", " ") if filename else "" effective_header = custom_file_header or header.format(safe_filename) @@ -1037,17 +928,17 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]: file.close() - if defer_formatting and (Formatter.RUFF_CHECK in formatters or Formatter.RUFF_FORMAT in formatters): + if defer_formatting and (Formatter.RUFF_CHECK in config.formatters or Formatter.RUFF_FORMAT in config.formatters): code_formatter = CodeFormatter( - target_python_version, - settings_path, - wrap_string_literal, - skip_string_normalization=not use_double_quotes, + config.target_python_version, + config.settings_path, + config.wrap_string_literal, + skip_string_normalization=not config.use_double_quotes, known_third_party=data_model_types.known_third_party, - custom_formatters=custom_formatters, - custom_formatters_kwargs=custom_formatters_kwargs, - encoding=encoding, - formatters=formatters, + custom_formatters=config.custom_formatters, + custom_formatters_kwargs=config.custom_formatters_kwargs, + encoding=config.encoding, + formatters=config.formatters, ) code_formatter.format_directory(output) diff --git a/src/datamodel_code_generator/_types/__init__.py b/src/datamodel_code_generator/_types/__init__.py index 61c932e82..14116e220 100644 --- a/src/datamodel_code_generator/_types/__init__.py +++ b/src/datamodel_code_generator/_types/__init__.py @@ -3,7 +3,17 @@ from __future__ import annotations from datamodel_code_generator._types.generate_config_dict import GenerateConfigDict +from datamodel_code_generator._types.graphql_parser_config_dict import GraphQLParserConfigDict +from datamodel_code_generator._types.jsonschema_parser_config_dict import JSONSchemaParserConfigDict +from datamodel_code_generator._types.openapi_parser_config_dict import OpenAPIParserConfigDict from datamodel_code_generator._types.parse_config_dict import ParseConfigDict from datamodel_code_generator._types.parser_config_dict import ParserConfigDict -__all__ = ["GenerateConfigDict", "ParseConfigDict", "ParserConfigDict"] +__all__ = [ + "GenerateConfigDict", + "GraphQLParserConfigDict", + "JSONSchemaParserConfigDict", + "OpenAPIParserConfigDict", + "ParseConfigDict", + "ParserConfigDict", +] diff --git a/src/datamodel_code_generator/_types/graphql_parser_config_dict.py b/src/datamodel_code_generator/_types/graphql_parser_config_dict.py new file mode 100644 index 000000000..8e03fd5d9 --- /dev/null +++ b/src/datamodel_code_generator/_types/graphql_parser_config_dict.py @@ -0,0 +1,146 @@ +# generated by datamodel-codegen: +# filename: + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, TypedDict + +from typing_extensions import NotRequired + +if TYPE_CHECKING: + from collections import defaultdict + from collections.abc import Callable, Iterable, Mapping, Sequence + from pathlib import Path + + from datamodel_code_generator.enums import ( + AllOfClassHierarchy, + AllOfMergeMode, + CollapseRootModelsNameStrategy, + DataclassArguments, + FieldTypeCollisionStrategy, + NamingStrategy, + ReadOnlyWriteOnlyModelType, + ReuseScope, + StrictTypes, + TargetPydanticVersion, + ) + from datamodel_code_generator.format import DateClassType, DatetimeClassType, Formatter, PythonVersion + from datamodel_code_generator.model.base import DataModel, DataModelFieldBase + from datamodel_code_generator.parser import DefaultPutDict, LiteralType + from datamodel_code_generator.types import DataTypeManager + + +class GraphQLParserConfigDict(TypedDict): + data_model_type: NotRequired[type[DataModel]] + data_model_root_type: NotRequired[type[DataModel]] + data_type_manager_type: NotRequired[type[DataTypeManager]] + data_model_field_type: NotRequired[type[DataModelFieldBase]] + base_class: NotRequired[str | None] + base_class_map: NotRequired[dict[str, str] | None] + additional_imports: NotRequired[list[str] | None] + class_decorators: NotRequired[list[str] | None] + custom_template_dir: NotRequired[Path | None] + extra_template_data: NotRequired[defaultdict[str, dict[str, Any]] | None] + target_python_version: NotRequired[PythonVersion] + dump_resolve_reference_action: NotRequired[Callable[[Iterable[str]], str] | None] + validation: NotRequired[bool] + field_constraints: NotRequired[bool] + snake_case_field: NotRequired[bool] + strip_default_none: NotRequired[bool] + aliases: NotRequired[Mapping[str, str | list[str]] | None] + allow_population_by_field_name: NotRequired[bool] + apply_default_values_for_required_fields: NotRequired[bool] + allow_extra_fields: NotRequired[bool] + extra_fields: NotRequired[str | None] + use_generic_base_class: NotRequired[bool] + force_optional_for_required_fields: NotRequired[bool] + class_name: NotRequired[str | None] + use_standard_collections: NotRequired[bool] + base_path: NotRequired[Path | 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 | None] + 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] + set_default_enum_member: NotRequired[bool] + use_subclass_enum: NotRequired[bool] + use_specialized_enum: NotRequired[bool] + strict_nullable: NotRequired[bool] + use_generic_container_types: NotRequired[bool] + enable_faux_immutability: NotRequired[bool] + remote_text_cache: NotRequired[DefaultPutDict[str, str] | None] + disable_appending_item_suffix: NotRequired[bool] + strict_types: NotRequired[Sequence[StrictTypes] | None] + empty_enum_field_name: NotRequired[str | None] + custom_class_name_generator: NotRequired[Callable[[str], str] | None] + field_extra_keys: NotRequired[set[str] | None] + field_include_all_keys: NotRequired[bool] + field_extra_keys_without_x_prefix: NotRequired[set[str] | None] + model_extra_keys: NotRequired[set[str] | None] + model_extra_keys_without_x_prefix: NotRequired[set[str] | None] + wrap_string_literal: NotRequired[bool | 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] + allof_class_hierarchy: NotRequired[AllOfClassHierarchy] + http_headers: NotRequired[Sequence[tuple[str, str]] | None] + http_ignore_tls: NotRequired[bool] + http_timeout: NotRequired[float | None] + 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] + original_field_name_delimiter: NotRequired[str | None] + use_double_quotes: NotRequired[bool] + use_union_operator: NotRequired[bool] + allow_responses_without_content: 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] + special_field_name_prefix: NotRequired[str | None] + remove_special_field_name_prefix: NotRequired[bool] + capitalise_enum_members: NotRequired[bool] + keep_model_order: NotRequired[bool] + use_one_literal_as_default: NotRequired[bool] + use_enum_values_in_discriminator: NotRequired[bool] + known_third_party: NotRequired[list[str] | None] + custom_formatters: NotRequired[list[str] | None] + custom_formatters_kwargs: NotRequired[dict[str, Any] | None] + use_pendulum: NotRequired[bool] + use_standard_primitive_types: NotRequired[bool] + http_query_parameters: NotRequired[Sequence[tuple[str, str]] | None] + treat_dot_as_module: NotRequired[bool | None] + use_exact_imports: NotRequired[bool] + default_field_extras: NotRequired[dict[str, Any] | None] + target_datetime_class: NotRequired[DatetimeClassType | None] + target_date_class: NotRequired[DateClassType | None] + keyword_only: NotRequired[bool] + frozen_dataclasses: NotRequired[bool] + no_alias: NotRequired[bool] + use_frozen_field: NotRequired[bool] + use_default_factory_for_optional_nested_models: NotRequired[bool] + formatters: NotRequired[list[Formatter]] + defer_formatting: NotRequired[bool] + parent_scoped_naming: NotRequired[bool] + naming_strategy: NotRequired[NamingStrategy | None] + duplicate_name_suffix: NotRequired[dict[str, str] | None] + dataclass_arguments: NotRequired[DataclassArguments | None] + type_mappings: NotRequired[list[str] | None] + type_overrides: NotRequired[dict[str, str] | None] + read_only_write_only_model_type: NotRequired[ReadOnlyWriteOnlyModelType | None] + field_type_collision_strategy: NotRequired[FieldTypeCollisionStrategy | None] + target_pydantic_version: NotRequired[TargetPydanticVersion | None] + data_model_scalar_type: NotRequired[type[DataModel]] + data_model_union_type: NotRequired[type[DataModel]] diff --git a/src/datamodel_code_generator/_types/jsonschema_parser_config_dict.py b/src/datamodel_code_generator/_types/jsonschema_parser_config_dict.py new file mode 100644 index 000000000..aff4fded1 --- /dev/null +++ b/src/datamodel_code_generator/_types/jsonschema_parser_config_dict.py @@ -0,0 +1,144 @@ +# generated by datamodel-codegen: +# filename: + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, TypedDict + +from typing_extensions import NotRequired + +if TYPE_CHECKING: + from collections import defaultdict + from collections.abc import Callable, Iterable, Mapping, Sequence + from pathlib import Path + + from datamodel_code_generator.enums import ( + AllOfClassHierarchy, + AllOfMergeMode, + CollapseRootModelsNameStrategy, + DataclassArguments, + FieldTypeCollisionStrategy, + NamingStrategy, + ReadOnlyWriteOnlyModelType, + ReuseScope, + StrictTypes, + TargetPydanticVersion, + ) + from datamodel_code_generator.format import DateClassType, DatetimeClassType, Formatter, PythonVersion + from datamodel_code_generator.model.base import DataModel, DataModelFieldBase + from datamodel_code_generator.parser import DefaultPutDict, LiteralType + from datamodel_code_generator.types import DataTypeManager + + +class JSONSchemaParserConfigDict(TypedDict): + data_model_type: NotRequired[type[DataModel]] + data_model_root_type: NotRequired[type[DataModel]] + data_type_manager_type: NotRequired[type[DataTypeManager]] + data_model_field_type: NotRequired[type[DataModelFieldBase]] + base_class: NotRequired[str | None] + base_class_map: NotRequired[dict[str, str] | None] + additional_imports: NotRequired[list[str] | None] + class_decorators: NotRequired[list[str] | None] + custom_template_dir: NotRequired[Path | None] + extra_template_data: NotRequired[defaultdict[str, dict[str, Any]] | None] + target_python_version: NotRequired[PythonVersion] + dump_resolve_reference_action: NotRequired[Callable[[Iterable[str]], str] | None] + validation: NotRequired[bool] + field_constraints: NotRequired[bool] + snake_case_field: NotRequired[bool] + strip_default_none: NotRequired[bool] + aliases: NotRequired[Mapping[str, str | list[str]] | None] + allow_population_by_field_name: NotRequired[bool] + apply_default_values_for_required_fields: NotRequired[bool] + allow_extra_fields: NotRequired[bool] + extra_fields: NotRequired[str | None] + use_generic_base_class: NotRequired[bool] + force_optional_for_required_fields: NotRequired[bool] + class_name: NotRequired[str | None] + use_standard_collections: NotRequired[bool] + base_path: NotRequired[Path | 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 | None] + 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] + set_default_enum_member: NotRequired[bool] + use_subclass_enum: NotRequired[bool] + use_specialized_enum: NotRequired[bool] + strict_nullable: NotRequired[bool] + use_generic_container_types: NotRequired[bool] + enable_faux_immutability: NotRequired[bool] + remote_text_cache: NotRequired[DefaultPutDict[str, str] | None] + disable_appending_item_suffix: NotRequired[bool] + strict_types: NotRequired[Sequence[StrictTypes] | None] + empty_enum_field_name: NotRequired[str | None] + custom_class_name_generator: NotRequired[Callable[[str], str] | None] + field_extra_keys: NotRequired[set[str] | None] + field_include_all_keys: NotRequired[bool] + field_extra_keys_without_x_prefix: NotRequired[set[str] | None] + model_extra_keys: NotRequired[set[str] | None] + model_extra_keys_without_x_prefix: NotRequired[set[str] | None] + wrap_string_literal: NotRequired[bool | 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] + allof_class_hierarchy: NotRequired[AllOfClassHierarchy] + http_headers: NotRequired[Sequence[tuple[str, str]] | None] + http_ignore_tls: NotRequired[bool] + http_timeout: NotRequired[float | None] + 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] + original_field_name_delimiter: NotRequired[str | None] + use_double_quotes: NotRequired[bool] + use_union_operator: NotRequired[bool] + allow_responses_without_content: 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] + special_field_name_prefix: NotRequired[str | None] + remove_special_field_name_prefix: NotRequired[bool] + capitalise_enum_members: NotRequired[bool] + keep_model_order: NotRequired[bool] + use_one_literal_as_default: NotRequired[bool] + use_enum_values_in_discriminator: NotRequired[bool] + known_third_party: NotRequired[list[str] | None] + custom_formatters: NotRequired[list[str] | None] + custom_formatters_kwargs: NotRequired[dict[str, Any] | None] + use_pendulum: NotRequired[bool] + use_standard_primitive_types: NotRequired[bool] + http_query_parameters: NotRequired[Sequence[tuple[str, str]] | None] + treat_dot_as_module: NotRequired[bool | None] + use_exact_imports: NotRequired[bool] + default_field_extras: NotRequired[dict[str, Any] | None] + target_datetime_class: NotRequired[DatetimeClassType | None] + target_date_class: NotRequired[DateClassType | None] + keyword_only: NotRequired[bool] + frozen_dataclasses: NotRequired[bool] + no_alias: NotRequired[bool] + use_frozen_field: NotRequired[bool] + use_default_factory_for_optional_nested_models: NotRequired[bool] + formatters: NotRequired[list[Formatter]] + defer_formatting: NotRequired[bool] + parent_scoped_naming: NotRequired[bool] + naming_strategy: NotRequired[NamingStrategy | None] + duplicate_name_suffix: NotRequired[dict[str, str] | None] + dataclass_arguments: NotRequired[DataclassArguments | None] + type_mappings: NotRequired[list[str] | None] + type_overrides: NotRequired[dict[str, str] | None] + read_only_write_only_model_type: NotRequired[ReadOnlyWriteOnlyModelType | None] + field_type_collision_strategy: NotRequired[FieldTypeCollisionStrategy | None] + target_pydantic_version: NotRequired[TargetPydanticVersion | None] diff --git a/src/datamodel_code_generator/_types/openapi_parser_config_dict.py b/src/datamodel_code_generator/_types/openapi_parser_config_dict.py new file mode 100644 index 000000000..11aefddab --- /dev/null +++ b/src/datamodel_code_generator/_types/openapi_parser_config_dict.py @@ -0,0 +1,148 @@ +# generated by datamodel-codegen: +# filename: + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, TypedDict + +from typing_extensions import NotRequired + +if TYPE_CHECKING: + from collections import defaultdict + from collections.abc import Callable, Iterable, Mapping, Sequence + from pathlib import Path + + from datamodel_code_generator.enums import ( + AllOfClassHierarchy, + AllOfMergeMode, + CollapseRootModelsNameStrategy, + DataclassArguments, + FieldTypeCollisionStrategy, + NamingStrategy, + OpenAPIScope, + ReadOnlyWriteOnlyModelType, + ReuseScope, + StrictTypes, + TargetPydanticVersion, + ) + from datamodel_code_generator.format import DateClassType, DatetimeClassType, Formatter, PythonVersion + from datamodel_code_generator.model.base import DataModel, DataModelFieldBase + from datamodel_code_generator.parser import DefaultPutDict, LiteralType + from datamodel_code_generator.types import DataTypeManager + + +class OpenAPIParserConfigDict(TypedDict): + data_model_type: NotRequired[type[DataModel]] + data_model_root_type: NotRequired[type[DataModel]] + data_type_manager_type: NotRequired[type[DataTypeManager]] + data_model_field_type: NotRequired[type[DataModelFieldBase]] + base_class: NotRequired[str | None] + base_class_map: NotRequired[dict[str, str] | None] + additional_imports: NotRequired[list[str] | None] + class_decorators: NotRequired[list[str] | None] + custom_template_dir: NotRequired[Path | None] + extra_template_data: NotRequired[defaultdict[str, dict[str, Any]] | None] + target_python_version: NotRequired[PythonVersion] + dump_resolve_reference_action: NotRequired[Callable[[Iterable[str]], str] | None] + validation: NotRequired[bool] + field_constraints: NotRequired[bool] + snake_case_field: NotRequired[bool] + strip_default_none: NotRequired[bool] + aliases: NotRequired[Mapping[str, str | list[str]] | None] + allow_population_by_field_name: NotRequired[bool] + apply_default_values_for_required_fields: NotRequired[bool] + allow_extra_fields: NotRequired[bool] + extra_fields: NotRequired[str | None] + use_generic_base_class: NotRequired[bool] + force_optional_for_required_fields: NotRequired[bool] + class_name: NotRequired[str | None] + use_standard_collections: NotRequired[bool] + base_path: NotRequired[Path | 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 | None] + 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] + set_default_enum_member: NotRequired[bool] + use_subclass_enum: NotRequired[bool] + use_specialized_enum: NotRequired[bool] + strict_nullable: NotRequired[bool] + use_generic_container_types: NotRequired[bool] + enable_faux_immutability: NotRequired[bool] + remote_text_cache: NotRequired[DefaultPutDict[str, str] | None] + disable_appending_item_suffix: NotRequired[bool] + strict_types: NotRequired[Sequence[StrictTypes] | None] + empty_enum_field_name: NotRequired[str | None] + custom_class_name_generator: NotRequired[Callable[[str], str] | None] + field_extra_keys: NotRequired[set[str] | None] + field_include_all_keys: NotRequired[bool] + field_extra_keys_without_x_prefix: NotRequired[set[str] | None] + model_extra_keys: NotRequired[set[str] | None] + model_extra_keys_without_x_prefix: NotRequired[set[str] | None] + wrap_string_literal: NotRequired[bool | 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] + allof_class_hierarchy: NotRequired[AllOfClassHierarchy] + http_headers: NotRequired[Sequence[tuple[str, str]] | None] + http_ignore_tls: NotRequired[bool] + http_timeout: NotRequired[float | None] + 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] + original_field_name_delimiter: NotRequired[str | None] + use_double_quotes: NotRequired[bool] + use_union_operator: NotRequired[bool] + allow_responses_without_content: 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] + special_field_name_prefix: NotRequired[str | None] + remove_special_field_name_prefix: NotRequired[bool] + capitalise_enum_members: NotRequired[bool] + keep_model_order: NotRequired[bool] + use_one_literal_as_default: NotRequired[bool] + use_enum_values_in_discriminator: NotRequired[bool] + known_third_party: NotRequired[list[str] | None] + custom_formatters: NotRequired[list[str] | None] + custom_formatters_kwargs: NotRequired[dict[str, Any] | None] + use_pendulum: NotRequired[bool] + use_standard_primitive_types: NotRequired[bool] + http_query_parameters: NotRequired[Sequence[tuple[str, str]] | None] + treat_dot_as_module: NotRequired[bool | None] + use_exact_imports: NotRequired[bool] + default_field_extras: NotRequired[dict[str, Any] | None] + target_datetime_class: NotRequired[DatetimeClassType | None] + target_date_class: NotRequired[DateClassType | None] + keyword_only: NotRequired[bool] + frozen_dataclasses: NotRequired[bool] + no_alias: NotRequired[bool] + use_frozen_field: NotRequired[bool] + use_default_factory_for_optional_nested_models: NotRequired[bool] + formatters: NotRequired[list[Formatter]] + defer_formatting: NotRequired[bool] + parent_scoped_naming: NotRequired[bool] + naming_strategy: NotRequired[NamingStrategy | None] + duplicate_name_suffix: NotRequired[dict[str, str] | None] + dataclass_arguments: NotRequired[DataclassArguments | None] + type_mappings: NotRequired[list[str] | None] + type_overrides: NotRequired[dict[str, str] | None] + read_only_write_only_model_type: NotRequired[ReadOnlyWriteOnlyModelType | None] + field_type_collision_strategy: NotRequired[FieldTypeCollisionStrategy | None] + target_pydantic_version: NotRequired[TargetPydanticVersion | None] + openapi_scopes: NotRequired[list[OpenAPIScope] | None] + include_path_parameters: NotRequired[bool] + use_status_code_in_response_name: NotRequired[bool] diff --git a/src/datamodel_code_generator/config.py b/src/datamodel_code_generator/config.py index ab9b08d65..0c5871d8f 100644 --- a/src/datamodel_code_generator/config.py +++ b/src/datamodel_code_generator/config.py @@ -37,13 +37,18 @@ PythonVersionMin, ) from datamodel_code_generator.model import pydantic as pydantic_model +from datamodel_code_generator.model.base import ( # noqa: TC001 - used by Pydantic at runtime + DataModel, + DataModelFieldBase, +) +from datamodel_code_generator.model.scalar import DataTypeScalar +from datamodel_code_generator.model.union import DataTypeUnion from datamodel_code_generator.parser import DefaultPutDict, LiteralType +from datamodel_code_generator.types import DataTypeManager, StrictTypes # noqa: TC001 - used by Pydantic at runtime from datamodel_code_generator.util import ConfigDict, is_pydantic_v2 if TYPE_CHECKING: - from datamodel_code_generator.model.base import DataModel, DataModelFieldBase from datamodel_code_generator.model.pydantic_v2 import UnionMode - from datamodel_code_generator.types import DataTypeManager, StrictTypes CallableSchema = Callable[[str], str] @@ -319,6 +324,25 @@ class Config: target_pydantic_version: TargetPydanticVersion | None = None +class GraphQLParserConfig(ParserConfig): + """Configuration model for GraphQLParser.__init__().""" + + data_model_scalar_type: type[DataModel] = DataTypeScalar + data_model_union_type: type[DataModel] = DataTypeUnion + + +class JSONSchemaParserConfig(ParserConfig): + """Configuration model for JsonSchemaParser.__init__().""" + + +class OpenAPIParserConfig(JSONSchemaParserConfig): + """Configuration model for OpenAPIParser.__init__().""" + + openapi_scopes: list[OpenAPIScope] | None = None + include_path_parameters: bool = False + use_status_code_in_response_name: bool = False + + class ParseConfig(BaseModel): """Configuration model for Parser.parse().""" diff --git a/src/datamodel_code_generator/parser/base.py b/src/datamodel_code_generator/parser/base.py index 460002c5d..af65d69b4 100644 --- a/src/datamodel_code_generator/parser/base.py +++ b/src/datamodel_code_generator/parser/base.py @@ -16,7 +16,18 @@ from collections.abc import Callable, Hashable, Sequence from itertools import groupby from pathlib import Path -from typing import TYPE_CHECKING, Any, NamedTuple, Optional, Protocol, TypeAlias, TypeVar, cast, runtime_checkable +from typing import ( + TYPE_CHECKING, + Any, + Generic, + NamedTuple, + Optional, + Protocol, + TypeAlias, + TypeVar, + cast, + runtime_checkable, +) from urllib.parse import ParseResult from warnings import warn @@ -71,7 +82,7 @@ from datamodel_code_generator.parser._scc import find_circular_sccs, strongly_connected_components from datamodel_code_generator.reference import ModelResolver, ModelType, Reference from datamodel_code_generator.types import DataType, DataTypeManager -from datamodel_code_generator.util import camel_to_snake, is_pydantic_v2, model_copy, model_dump +from datamodel_code_generator.util import camel_to_snake, model_copy, model_dump if TYPE_CHECKING: from collections.abc import Iterable, Iterator, Sequence @@ -79,6 +90,8 @@ from datamodel_code_generator._types import ParserConfigDict from datamodel_code_generator.config import ParserConfig +ParserConfigT = TypeVar("ParserConfigT", bound="ParserConfig") + @runtime_checkable class HashableComparable(Hashable, Protocol): @@ -675,18 +688,49 @@ def from_dict(cls, data: dict[str, YamlValue]) -> Source: return cls(path=Path(), raw_data=data) -class Parser(ABC): +class Parser(ABC, Generic[ParserConfigT]): """Abstract base class for schema parsers. Provides the parsing algorithm and code generation. Subclasses implement parse_raw() to handle specific schema formats. """ - def __init__( # noqa: PLR0912, PLR0914, PLR0915 + @classmethod + def _create_default_config(cls, options: ParserConfigDict) -> ParserConfigT: + """Create a default config from options. + + Subclasses should override this to return their own config type. + """ + from datamodel_code_generator import types as types_module # noqa: PLC0415 + from datamodel_code_generator.config import ParserConfig # noqa: PLC0415 + from datamodel_code_generator.model import base as model_base # noqa: PLC0415 + from datamodel_code_generator.util import is_pydantic_v2 # noqa: PLC0415 + + if is_pydantic_v2(): + ParserConfig.model_rebuild( + _types_namespace={ + "StrictTypes": types_module.StrictTypes, + "DataModel": model_base.DataModel, + "DataModelFieldBase": model_base.DataModelFieldBase, + "DataTypeManager": types_module.DataTypeManager, + } + ) + return ParserConfig.model_validate(options) # type: ignore[return-value] + ParserConfig.update_forward_refs( + StrictTypes=types_module.StrictTypes, + DataModel=model_base.DataModel, + DataModelFieldBase=model_base.DataModelFieldBase, + DataTypeManager=types_module.DataTypeManager, + ) + defaults = {name: field.default for name, field in ParserConfig.__fields__.items()} + defaults.update(options) + return ParserConfig.construct(**defaults) # type: ignore[return-value] + + def __init__( # noqa: PLR0912, PLR0915 self, source: str | Path | list[Path] | ParseResult | dict[str, YamlValue], *, - config: ParserConfig | None = None, + config: ParserConfigT | None = None, **options: Unpack[ParserConfigDict], ) -> None: """Initialize the Parser with configuration options. @@ -699,330 +743,200 @@ def __init__( # noqa: PLR0912, PLR0914, PLR0915 Raises: ValueError: If both config and **options are provided. """ - from datamodel_code_generator.config import ParserConfig # noqa: PLC0415 - if config is not None and options: msg = "Cannot specify both 'config' and keyword arguments. Use one or the other." raise ValueError(msg) if config is None: - from datamodel_code_generator import types as types_module # noqa: PLC0415 - from datamodel_code_generator.model import base as model_base # noqa: PLC0415 - - if is_pydantic_v2(): - ParserConfig.model_rebuild( - _types_namespace={ - "StrictTypes": types_module.StrictTypes, - "DataModel": model_base.DataModel, - "DataModelFieldBase": model_base.DataModelFieldBase, - "DataTypeManager": types_module.DataTypeManager, - } - ) - config = ParserConfig.model_validate(options) - else: - ParserConfig.update_forward_refs( - StrictTypes=types_module.StrictTypes, - DataModel=model_base.DataModel, - DataModelFieldBase=model_base.DataModelFieldBase, - DataTypeManager=types_module.DataTypeManager, - ) - defaults = {name: field.default for name, field in ParserConfig.__fields__.items()} - defaults.update(options) - config = ParserConfig.construct(**defaults) - - data_model_type = config.data_model_type - data_model_root_type = config.data_model_root_type - data_type_manager_type = config.data_type_manager_type - data_model_field_type = config.data_model_field_type - 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 = config.extra_template_data - target_python_version = config.target_python_version - dump_resolve_reference_action = config.dump_resolve_reference_action - 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 - allow_population_by_field_name = config.allow_population_by_field_name - apply_default_values_for_required_fields = config.apply_default_values_for_required_fields - allow_extra_fields = config.allow_extra_fields - extra_fields = config.extra_fields - use_generic_base_class = config.use_generic_base_class - force_optional_for_required_fields = config.force_optional_for_required_fields - class_name = config.class_name - use_standard_collections = config.use_standard_collections - base_path = config.base_path - 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 - 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 - remote_text_cache = config.remote_text_cache - 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 - 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 - allof_class_hierarchy = config.allof_class_hierarchy - 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 - allow_responses_without_content = config.allow_responses_without_content - 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 - 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 - use_one_literal_as_default = config.use_one_literal_as_default - use_enum_values_in_discriminator = config.use_enum_values_in_discriminator - known_third_party = config.known_third_party - 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 - default_field_extras = config.default_field_extras - target_datetime_class = config.target_datetime_class - target_date_class = config.target_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 - defer_formatting = config.defer_formatting - parent_scoped_naming = config.parent_scoped_naming - naming_strategy = config.naming_strategy - duplicate_name_suffix = config.duplicate_name_suffix - dataclass_arguments = config.dataclass_arguments - type_mappings = config.type_mappings - type_overrides = config.type_overrides - read_only_write_only_model_type = config.read_only_write_only_model_type - field_type_collision_strategy = config.field_type_collision_strategy - target_pydantic_version = config.target_pydantic_version - - self.keyword_only = keyword_only - self.target_pydantic_version = target_pydantic_version - self.frozen_dataclasses = frozen_dataclasses - self.data_type_manager: DataTypeManager = data_type_manager_type( - python_version=target_python_version, - use_standard_collections=use_standard_collections, - use_generic_container_types=use_generic_container_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, - 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, - target_date_class=target_date_class, - treat_dot_as_module=treat_dot_as_module or False, - use_serialize_as_any=use_serialize_as_any, + config = self._create_default_config(options) + + self.config = config + + self.keyword_only = config.keyword_only + self.target_pydantic_version = config.target_pydantic_version + self.frozen_dataclasses = config.frozen_dataclasses + self.data_type_manager: DataTypeManager = config.data_type_manager_type( + python_version=config.target_python_version, + use_standard_collections=config.use_standard_collections, + use_generic_container_types=config.use_generic_container_types, + 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, + strict_types=config.strict_types, + use_union_operator=config.use_union_operator, + use_pendulum=config.use_pendulum, + use_standard_primitive_types=config.use_standard_primitive_types, + target_datetime_class=config.target_datetime_class, + target_date_class=config.target_date_class, + treat_dot_as_module=config.treat_dot_as_module or False, + use_serialize_as_any=config.use_serialize_as_any, ) - self.data_model_type: type[DataModel] = data_model_type - self.data_model_root_type: type[DataModel] = data_model_root_type - self.data_model_field_type: type[DataModelFieldBase] = data_model_field_type - - self.imports: Imports = Imports(use_exact_imports) - self.use_exact_imports: bool = use_exact_imports - self._append_additional_imports(additional_imports=additional_imports) - self.class_decorators: list[str] = class_decorators or [] - - self.base_class: str | None = base_class - self.base_class_map: dict[str, str] | None = base_class_map - self.target_python_version: PythonVersion = target_python_version + self.data_model_type: type[DataModel] = config.data_model_type + self.data_model_root_type: type[DataModel] = config.data_model_root_type + self.data_model_field_type: type[DataModelFieldBase] = config.data_model_field_type + + self.imports: Imports = Imports(config.use_exact_imports) + self.use_exact_imports: bool = config.use_exact_imports + self._append_additional_imports(additional_imports=config.additional_imports) + self.class_decorators: list[str] = config.class_decorators or [] + + self.base_class: str | None = config.base_class + self.base_class_map: dict[str, str] | None = config.base_class_map + self.target_python_version: PythonVersion = config.target_python_version self.results: list[DataModel] = [] - self.dump_resolve_reference_action: Callable[[Iterable[str]], str] | None = dump_resolve_reference_action - self.validation: bool = validation - self.field_constraints: bool = field_constraints - self.snake_case_field: bool = snake_case_field - self.strip_default_none: bool = strip_default_none - self.apply_default_values_for_required_fields: bool = apply_default_values_for_required_fields - self.force_optional_for_required_fields: bool = force_optional_for_required_fields - self.use_schema_description: bool = use_schema_description - self.use_field_description: bool = use_field_description - self.use_field_description_example: bool = use_field_description_example - self.use_inline_field_description: bool = use_inline_field_description - self.use_default_kwarg: bool = use_default_kwarg - self.reuse_model: bool = reuse_model - self.reuse_scope: ReuseScope | None = reuse_scope - self.shared_module_name: str = shared_module_name - self.encoding: str = encoding - self.enum_field_as_literal: LiteralType | None = enum_field_as_literal - self.enum_field_as_literal_map: dict[str, str] = enum_field_as_literal_map or {} - self.ignore_enum_constraints: bool = ignore_enum_constraints - self.set_default_enum_member: bool = set_default_enum_member - self.use_subclass_enum: bool = use_subclass_enum - self.use_specialized_enum: bool = use_specialized_enum - self.strict_nullable: bool = strict_nullable - self.use_generic_container_types: bool = use_generic_container_types - self.use_union_operator: bool = use_union_operator - self.enable_faux_immutability: bool = enable_faux_immutability - self.custom_class_name_generator: Callable[[str], str] | None = custom_class_name_generator - self.field_extra_keys: set[str] = field_extra_keys or set() - self.field_extra_keys_without_x_prefix: set[str] = field_extra_keys_without_x_prefix or set() - self.model_extra_keys: set[str] = model_extra_keys or set() - self.model_extra_keys_without_x_prefix: set[str] = model_extra_keys_without_x_prefix or set() - self.field_include_all_keys: bool = field_include_all_keys - - self.remote_text_cache: DefaultPutDict[str, str] = remote_text_cache or DefaultPutDict() + self.dump_resolve_reference_action: Callable[[Iterable[str]], str] | None = config.dump_resolve_reference_action + self.validation: bool = config.validation + self.field_constraints: bool = config.field_constraints + self.snake_case_field: bool = config.snake_case_field + self.strip_default_none: bool = config.strip_default_none + self.apply_default_values_for_required_fields: bool = config.apply_default_values_for_required_fields + self.force_optional_for_required_fields: bool = config.force_optional_for_required_fields + self.use_schema_description: bool = config.use_schema_description + self.use_field_description: bool = config.use_field_description + self.use_field_description_example: bool = config.use_field_description_example + self.use_inline_field_description: bool = config.use_inline_field_description + self.use_default_kwarg: bool = config.use_default_kwarg + self.reuse_model: bool = config.reuse_model + self.reuse_scope: ReuseScope | None = config.reuse_scope + self.shared_module_name: str = config.shared_module_name + self.encoding: str = config.encoding + self.enum_field_as_literal: LiteralType | None = config.enum_field_as_literal + self.enum_field_as_literal_map: dict[str, str] = config.enum_field_as_literal_map or {} + self.ignore_enum_constraints: bool = config.ignore_enum_constraints + self.set_default_enum_member: bool = config.set_default_enum_member + 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.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 + self.custom_class_name_generator: Callable[[str], str] | None = config.custom_class_name_generator + self.field_extra_keys: set[str] = config.field_extra_keys or set() + self.field_extra_keys_without_x_prefix: set[str] = config.field_extra_keys_without_x_prefix or set() + self.model_extra_keys: set[str] = config.model_extra_keys or set() + self.model_extra_keys_without_x_prefix: set[str] = config.model_extra_keys_without_x_prefix or set() + self.field_include_all_keys: bool = config.field_include_all_keys + + self.remote_text_cache: DefaultPutDict[str, str] = config.remote_text_cache or DefaultPutDict() self.current_source_path: Path | None = None - self.use_title_as_name: bool = use_title_as_name - self.use_operation_id_as_name: bool = use_operation_id_as_name - self.use_unique_items_as_set: bool = use_unique_items_as_set - self.use_tuple_for_fixed_items: bool = use_tuple_for_fixed_items - self.allof_merge_mode: AllOfMergeMode = allof_merge_mode - self.allof_class_hierarchy: AllOfClassHierarchy = allof_class_hierarchy - self.dataclass_arguments = dataclass_arguments - - if base_path: - self.base_path = base_path + self.use_title_as_name: bool = config.use_title_as_name + self.use_operation_id_as_name: bool = config.use_operation_id_as_name + self.use_unique_items_as_set: bool = config.use_unique_items_as_set + self.use_tuple_for_fixed_items: bool = config.use_tuple_for_fixed_items + self.allof_merge_mode: AllOfMergeMode = config.allof_merge_mode + self.allof_class_hierarchy: AllOfClassHierarchy = config.allof_class_hierarchy + self.dataclass_arguments = config.dataclass_arguments + + if config.base_path: + self.base_path = config.base_path elif isinstance(source, Path): self.base_path = source.absolute() if source.is_dir() else source.absolute().parent else: self.base_path = Path.cwd() self.source: str | Path | list[Path] | ParseResult | dict[str, YamlValue] = source - self.custom_template_dir = custom_template_dir - self.extra_template_data: defaultdict[str, Any] = extra_template_data or defaultdict(dict) + self.custom_template_dir = config.custom_template_dir + self.extra_template_data: defaultdict[str, Any] = config.extra_template_data or defaultdict(dict) - self.use_generic_base_class: bool = use_generic_base_class + self.use_generic_base_class: bool = config.use_generic_base_class self.generic_base_class_config: dict[str, Any] = {} - if allow_population_by_field_name: - if use_generic_base_class: + if config.allow_population_by_field_name: + if config.use_generic_base_class: self.generic_base_class_config["allow_population_by_field_name"] = True else: self.extra_template_data[ALL_MODEL]["allow_population_by_field_name"] = True - if allow_extra_fields: - if use_generic_base_class: + if config.allow_extra_fields: + if config.use_generic_base_class: self.generic_base_class_config["allow_extra_fields"] = True else: self.extra_template_data[ALL_MODEL]["allow_extra_fields"] = True - if extra_fields: - if use_generic_base_class: - self.generic_base_class_config["extra_fields"] = extra_fields + if config.extra_fields: + if config.use_generic_base_class: + self.generic_base_class_config["extra_fields"] = config.extra_fields else: - self.extra_template_data[ALL_MODEL]["extra_fields"] = extra_fields + self.extra_template_data[ALL_MODEL]["extra_fields"] = config.extra_fields - if enable_faux_immutability: - if use_generic_base_class: + if config.enable_faux_immutability: + if config.use_generic_base_class: self.generic_base_class_config["allow_mutation"] = False else: self.extra_template_data[ALL_MODEL]["allow_mutation"] = False - if use_attribute_docstrings: - if use_generic_base_class: + if config.use_attribute_docstrings: + if config.use_generic_base_class: self.generic_base_class_config["use_attribute_docstrings"] = True else: self.extra_template_data[ALL_MODEL]["use_attribute_docstrings"] = True - if target_pydantic_version: - if use_generic_base_class: - self.generic_base_class_config["target_pydantic_version"] = target_pydantic_version + if config.target_pydantic_version: + if config.use_generic_base_class: + self.generic_base_class_config["target_pydantic_version"] = config.target_pydantic_version else: - self.extra_template_data[ALL_MODEL]["target_pydantic_version"] = target_pydantic_version + self.extra_template_data[ALL_MODEL]["target_pydantic_version"] = config.target_pydantic_version self.model_resolver = ModelResolver( base_url=source.geturl() if isinstance(source, ParseResult) else None, - singular_name_suffix="" if disable_appending_item_suffix else None, - aliases=aliases, - empty_field_name=empty_enum_field_name, - snake_case_field=snake_case_field, - custom_class_name_generator=custom_class_name_generator, + singular_name_suffix="" if config.disable_appending_item_suffix else None, + aliases=config.aliases, + empty_field_name=config.empty_enum_field_name, + snake_case_field=config.snake_case_field, + custom_class_name_generator=config.custom_class_name_generator, base_path=self.base_path, - original_field_name_delimiter=original_field_name_delimiter, - special_field_name_prefix=special_field_name_prefix, - remove_special_field_name_prefix=remove_special_field_name_prefix, - capitalise_enum_members=capitalise_enum_members, - no_alias=no_alias, - parent_scoped_naming=parent_scoped_naming, - treat_dot_as_module=treat_dot_as_module, - naming_strategy=naming_strategy, - duplicate_name_suffix_map=duplicate_name_suffix, + original_field_name_delimiter=config.original_field_name_delimiter, + 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, + no_alias=config.no_alias, + parent_scoped_naming=config.parent_scoped_naming, + treat_dot_as_module=config.treat_dot_as_module, + naming_strategy=config.naming_strategy, + duplicate_name_suffix_map=config.duplicate_name_suffix, ) - self.class_name: str | None = class_name - self.wrap_string_literal: bool | None = wrap_string_literal - self.http_headers: Sequence[tuple[str, str]] | None = http_headers - self.http_query_parameters: Sequence[tuple[str, str]] | None = http_query_parameters - self.http_ignore_tls: bool = http_ignore_tls - self.http_timeout: float | None = http_timeout - self.use_annotated: bool = use_annotated + self.class_name: str | None = config.class_name + self.wrap_string_literal: bool | None = config.wrap_string_literal + self.http_headers: Sequence[tuple[str, str]] | None = config.http_headers + self.http_query_parameters: Sequence[tuple[str, str]] | None = config.http_query_parameters + self.http_ignore_tls: bool = config.http_ignore_tls + self.http_timeout: float | None = config.http_timeout + self.use_annotated: bool = config.use_annotated if self.use_annotated and not self.field_constraints: # pragma: no cover msg = "`use_annotated=True` has to be used with `field_constraints=True`" raise Exception(msg) # noqa: TRY002 - self.use_serialize_as_any: bool = use_serialize_as_any - self.use_non_positive_negative_number_constrained_types = use_non_positive_negative_number_constrained_types - self.use_double_quotes = use_double_quotes - self.allow_responses_without_content = allow_responses_without_content - self.collapse_root_models = collapse_root_models - self.collapse_root_models_name_strategy = collapse_root_models_name_strategy - self.collapse_reuse_models = collapse_reuse_models - self.skip_root_model = skip_root_model - self.use_type_alias = use_type_alias - self.capitalise_enum_members = capitalise_enum_members - self.keep_model_order = keep_model_order - self.use_one_literal_as_default = use_one_literal_as_default - self.use_enum_values_in_discriminator = use_enum_values_in_discriminator - self.known_third_party = known_third_party - self.custom_formatter = custom_formatters - self.custom_formatters_kwargs = custom_formatters_kwargs - self.treat_dot_as_module = treat_dot_as_module - self.default_field_extras: dict[str, Any] | None = default_field_extras - self.formatters: list[Formatter] = formatters - self.defer_formatting: bool = defer_formatting - self.type_mappings: dict[tuple[str, str], str] = Parser._parse_type_mappings(type_mappings) - self.type_overrides: dict[str, str] = type_overrides or {} + self.use_serialize_as_any: bool = config.use_serialize_as_any + self.use_non_positive_negative_number_constrained_types = ( + config.use_non_positive_negative_number_constrained_types + ) + self.use_double_quotes = config.use_double_quotes + self.allow_responses_without_content = config.allow_responses_without_content + self.collapse_root_models = config.collapse_root_models + self.collapse_root_models_name_strategy = config.collapse_root_models_name_strategy + self.collapse_reuse_models = config.collapse_reuse_models + self.skip_root_model = config.skip_root_model + self.use_type_alias = config.use_type_alias + self.capitalise_enum_members = config.capitalise_enum_members + self.keep_model_order = config.keep_model_order + self.use_one_literal_as_default = config.use_one_literal_as_default + self.use_enum_values_in_discriminator = config.use_enum_values_in_discriminator + self.known_third_party = config.known_third_party + self.custom_formatter = config.custom_formatters + self.custom_formatters_kwargs = config.custom_formatters_kwargs + self.treat_dot_as_module = config.treat_dot_as_module + self.default_field_extras: dict[str, Any] | None = config.default_field_extras + self.formatters: list[Formatter] = config.formatters + self.defer_formatting: bool = config.defer_formatting + self.type_mappings: dict[tuple[str, str], str] = Parser._parse_type_mappings(config.type_mappings) + self.type_overrides: dict[str, str] = config.type_overrides or {} self._type_override_imports: dict[str, Import] = { key: Import.from_full_path(value) for key, value in self.type_overrides.items() } - self.read_only_write_only_model_type: ReadOnlyWriteOnlyModelType | None = read_only_write_only_model_type - self.use_frozen_field: bool = use_frozen_field - self.use_default_factory_for_optional_nested_models: bool = use_default_factory_for_optional_nested_models - self.field_type_collision_strategy: FieldTypeCollisionStrategy | None = field_type_collision_strategy + self.read_only_write_only_model_type: ReadOnlyWriteOnlyModelType | None = config.read_only_write_only_model_type + self.use_frozen_field: bool = config.use_frozen_field + self.use_default_factory_for_optional_nested_models: bool = ( + config.use_default_factory_for_optional_nested_models + ) + self.field_type_collision_strategy: FieldTypeCollisionStrategy | None = config.field_type_collision_strategy @property def field_name_model_type(self) -> ModelType: diff --git a/src/datamodel_code_generator/parser/graphql.py b/src/datamodel_code_generator/parser/graphql.py index 1b6deb2ce..1249b61e0 100644 --- a/src/datamodel_code_generator/parser/graphql.py +++ b/src/datamodel_code_generator/parser/graphql.py @@ -23,8 +23,6 @@ from datamodel_code_generator.model.dataclass import DataClass from datamodel_code_generator.model.enum import SPECIALIZED_ENUM_TYPE_MATCH, Enum from datamodel_code_generator.model.pydantic_v2.dataclass import DataClass as PydanticV2DataClass -from datamodel_code_generator.model.scalar import DataTypeScalar -from datamodel_code_generator.model.union import DataTypeUnion from datamodel_code_generator.parser.base import ( DataType, Parser, @@ -44,8 +42,8 @@ if TYPE_CHECKING: from collections.abc import Iterator - from datamodel_code_generator._types import ParserConfigDict - from datamodel_code_generator.config import ParserConfig + from datamodel_code_generator._types import GraphQLParserConfigDict + from datamodel_code_generator.config import GraphQLParserConfig from datamodel_code_generator.model import DataModel, DataModelFieldBase # graphql-core >=3.2.7 removed TypeResolvers in favor of TypeFields.kind. @@ -63,7 +61,7 @@ def build_graphql_schema(schema_str: str) -> graphql.GraphQLSchema: @snooper_to_methods() -class GraphQLParser(Parser): +class GraphQLParser(Parser["GraphQLParserConfig"]): """Parser for GraphQL schema files.""" # raw graphql schema as `graphql-core` object @@ -90,14 +88,40 @@ class GraphQLParser(Parser): graphql.type.introspection.TypeKind.UNION, ] + @classmethod + def _create_default_config(cls, options: GraphQLParserConfigDict) -> GraphQLParserConfig: # type: ignore[override] + """Create a GraphQLParserConfig from options.""" + from datamodel_code_generator import types as types_module # noqa: PLC0415 + from datamodel_code_generator.config import GraphQLParserConfig # noqa: PLC0415 + from datamodel_code_generator.model import base as model_base # noqa: PLC0415 + from datamodel_code_generator.util import is_pydantic_v2 # noqa: PLC0415 + + if is_pydantic_v2(): + GraphQLParserConfig.model_rebuild( + _types_namespace={ + "StrictTypes": types_module.StrictTypes, + "DataModel": model_base.DataModel, + "DataModelFieldBase": model_base.DataModelFieldBase, + "DataTypeManager": types_module.DataTypeManager, + } + ) + return GraphQLParserConfig.model_validate(options) + GraphQLParserConfig.update_forward_refs( + StrictTypes=types_module.StrictTypes, + DataModel=model_base.DataModel, + DataModelFieldBase=model_base.DataModelFieldBase, + DataTypeManager=types_module.DataTypeManager, + ) + defaults = {name: field.default for name, field in GraphQLParserConfig.__fields__.items()} + defaults.update(options) + return GraphQLParserConfig.construct(**defaults) # type: ignore[return-value] # pragma: no cover + def __init__( self, source: str | Path | ParseResult, *, - config: ParserConfig | None = None, - data_model_scalar_type: type[DataModel] = DataTypeScalar, - data_model_union_type: type[DataModel] = DataTypeUnion, - **options: Unpack[ParserConfigDict], + config: GraphQLParserConfig | None = None, + **options: Unpack[GraphQLParserConfigDict], ) -> None: """Initialize the GraphQL parser with configuration options.""" if config is None and options.get("target_datetime_class") is None: @@ -108,8 +132,8 @@ def __init__( use_union_operator = config.use_union_operator if config else options.get("use_union_operator", False) super().__init__(source=source, config=config, **options) - self.data_model_scalar_type = data_model_scalar_type - self.data_model_union_type = data_model_union_type + self.data_model_scalar_type = self.config.data_model_scalar_type + self.data_model_union_type = self.config.data_model_union_type self.use_standard_collections = use_standard_collections self.use_union_operator = use_union_operator diff --git a/src/datamodel_code_generator/parser/jsonschema.py b/src/datamodel_code_generator/parser/jsonschema.py index 380ebffff..2d9d85684 100644 --- a/src/datamodel_code_generator/parser/jsonschema.py +++ b/src/datamodel_code_generator/parser/jsonschema.py @@ -85,8 +85,8 @@ if TYPE_CHECKING: from collections.abc import Callable, Generator, Iterable, Iterator - from datamodel_code_generator._types import ParserConfigDict - from datamodel_code_generator.config import ParserConfig + from datamodel_code_generator._types import JSONSchemaParserConfigDict + from datamodel_code_generator.config import JSONSchemaParserConfig def unescape_json_pointer_segment(segment: str) -> str: @@ -523,7 +523,7 @@ def _get_type(type_: str, format__: str | None = None) -> Types: @snooper_to_methods() # noqa: PLR0904 -class JsonSchemaParser(Parser): +class JsonSchemaParser(Parser["JSONSchemaParserConfig"]): """Parser for JSON Schema, JSON, YAML, Dict, and CSV formats.""" SCHEMA_PATHS: ClassVar[list[str]] = ["#/definitions", "#/$defs"] @@ -627,12 +627,39 @@ class JsonSchemaParser(Parser): "ChainMap", }) + @classmethod + def _create_default_config(cls, options: JSONSchemaParserConfigDict) -> JSONSchemaParserConfig: + """Create a JSONSchemaParserConfig from options.""" + from datamodel_code_generator import types as types_module # noqa: PLC0415 + from datamodel_code_generator.config import JSONSchemaParserConfig # noqa: PLC0415 + from datamodel_code_generator.model import base as model_base # noqa: PLC0415 + + if is_pydantic_v2(): + JSONSchemaParserConfig.model_rebuild( + _types_namespace={ + "StrictTypes": types_module.StrictTypes, + "DataModel": model_base.DataModel, + "DataModelFieldBase": model_base.DataModelFieldBase, + "DataTypeManager": types_module.DataTypeManager, + } + ) + return JSONSchemaParserConfig.model_validate(options) + JSONSchemaParserConfig.update_forward_refs( + StrictTypes=types_module.StrictTypes, + DataModel=model_base.DataModel, + DataModelFieldBase=model_base.DataModelFieldBase, + DataTypeManager=types_module.DataTypeManager, + ) + defaults = {name: field.default for name, field in JSONSchemaParserConfig.__fields__.items()} + defaults.update(options) + return JSONSchemaParserConfig.construct(**defaults) # type: ignore[return-value] # pragma: no cover + def __init__( self, source: str | Path | list[Path] | ParseResult, *, - config: ParserConfig | None = None, - **options: Unpack[ParserConfigDict], + config: JSONSchemaParserConfig | None = None, + **options: Unpack[JSONSchemaParserConfigDict], ) -> None: """Initialize the JSON Schema parser with configuration options.""" if config is None and options.get("target_datetime_class") is None: diff --git a/src/datamodel_code_generator/parser/openapi.py b/src/datamodel_code_generator/parser/openapi.py index d33cd46aa..89310553c 100644 --- a/src/datamodel_code_generator/parser/openapi.py +++ b/src/datamodel_code_generator/parser/openapi.py @@ -41,8 +41,8 @@ if TYPE_CHECKING: from urllib.parse import ParseResult - from datamodel_code_generator._types import ParserConfigDict - from datamodel_code_generator.config import ParserConfig + from datamodel_code_generator._types import OpenAPIParserConfigDict + from datamodel_code_generator.config import OpenAPIParserConfig from datamodel_code_generator.model import DataModelFieldBase @@ -166,23 +166,48 @@ class OpenAPIParser(JsonSchemaParser): SCHEMA_PATHS: ClassVar[list[str]] = ["#/components/schemas"] + @classmethod + def _create_default_config(cls, options: OpenAPIParserConfigDict) -> OpenAPIParserConfig: + """Create an OpenAPIParserConfig from options.""" + from datamodel_code_generator import types as types_module # noqa: PLC0415 + from datamodel_code_generator.config import OpenAPIParserConfig # noqa: PLC0415 + from datamodel_code_generator.model import base as model_base # noqa: PLC0415 + from datamodel_code_generator.util import is_pydantic_v2 # noqa: PLC0415 + + if is_pydantic_v2(): + OpenAPIParserConfig.model_rebuild( + _types_namespace={ + "StrictTypes": types_module.StrictTypes, + "DataModel": model_base.DataModel, + "DataModelFieldBase": model_base.DataModelFieldBase, + "DataTypeManager": types_module.DataTypeManager, + } + ) + return OpenAPIParserConfig.model_validate(options) + OpenAPIParserConfig.update_forward_refs( + StrictTypes=types_module.StrictTypes, + DataModel=model_base.DataModel, + DataModelFieldBase=model_base.DataModelFieldBase, + DataTypeManager=types_module.DataTypeManager, + ) + defaults = {name: field.default for name, field in OpenAPIParserConfig.__fields__.items()} + defaults.update(options) + return OpenAPIParserConfig.construct(**defaults) # type: ignore[return-value] # pragma: no cover + def __init__( self, source: str | Path | list[Path] | ParseResult, *, - config: ParserConfig | None = None, - openapi_scopes: list[OpenAPIScope] | None = None, - include_path_parameters: bool = False, - use_status_code_in_response_name: bool = False, - **options: Unpack[ParserConfigDict], + config: OpenAPIParserConfig | None = None, + **options: Unpack[OpenAPIParserConfigDict], ) -> None: """Initialize the OpenAPI parser with extensive configuration options.""" if config is None and options.get("wrap_string_literal") is None: options["wrap_string_literal"] = False - super().__init__(source=source, config=config, **options) - self.open_api_scopes: list[OpenAPIScope] = openapi_scopes or [OpenAPIScope.Schemas] - self.include_path_parameters: bool = include_path_parameters - self.use_status_code_in_response_name: bool = use_status_code_in_response_name + super().__init__(source=source, config=config, **options) # type: ignore[arg-type] + self.open_api_scopes: list[OpenAPIScope] = self.config.openapi_scopes or [OpenAPIScope.Schemas] + self.include_path_parameters: bool = self.config.include_path_parameters + self.use_status_code_in_response_name: bool = self.config.use_status_code_in_response_name self._discriminator_schemas: dict[str, dict[str, Any]] = {} self._discriminator_subtypes: dict[str, list[str]] = defaultdict(list) diff --git a/tests/main/test_main_general.py b/tests/main/test_main_general.py index a8903e49d..086a48c2c 100644 --- a/tests/main/test_main_general.py +++ b/tests/main/test_main_general.py @@ -2089,13 +2089,13 @@ def test_jsonschema_parser_with_config_object() -> None: @pytest.mark.skipif(pydantic.VERSION < "2.0.0", reason="ParserConfig requires Pydantic v2") def test_openapi_parser_with_config_object() -> None: - """Test OpenAPIParser with ParserConfig object to cover config is not None branch.""" - from datamodel_code_generator.config import ParserConfig + """Test OpenAPIParser with OpenAPIParserConfig object to cover config is not None branch.""" + from datamodel_code_generator.config import OpenAPIParserConfig from datamodel_code_generator.model.base import DataModel, DataModelFieldBase from datamodel_code_generator.parser.openapi import OpenAPIParser from datamodel_code_generator.types import DataTypeManager, StrictTypes - ParserConfig.model_rebuild( + OpenAPIParserConfig.model_rebuild( _types_namespace={ "StrictTypes": StrictTypes, "DataModel": DataModel, @@ -2103,7 +2103,7 @@ def test_openapi_parser_with_config_object() -> None: "DataTypeManager": DataTypeManager, } ) - config = ParserConfig(wrap_string_literal=True) + config = OpenAPIParserConfig(wrap_string_literal=True) parser = OpenAPIParser( source='{"openapi": "3.0.0", "info": {"title": "Test", "version": "1.0"}, "paths": {}}', config=config, @@ -2113,14 +2113,14 @@ def test_openapi_parser_with_config_object() -> None: @pytest.mark.skipif(pydantic.VERSION < "2.0.0", reason="ParserConfig requires Pydantic v2") def test_graphql_parser_with_config_object() -> None: - """Test GraphQLParser with ParserConfig object to cover config is not None branch.""" - from datamodel_code_generator.config import ParserConfig + """Test GraphQLParser with GraphQLParserConfig object to cover config is not None branch.""" + from datamodel_code_generator.config import GraphQLParserConfig from datamodel_code_generator.format import DatetimeClassType from datamodel_code_generator.model.base import DataModel, DataModelFieldBase from datamodel_code_generator.parser.graphql import GraphQLParser from datamodel_code_generator.types import DataTypeManager, StrictTypes - ParserConfig.model_rebuild( + GraphQLParserConfig.model_rebuild( _types_namespace={ "StrictTypes": StrictTypes, "DataModel": DataModel, @@ -2128,6 +2128,6 @@ def test_graphql_parser_with_config_object() -> None: "DataTypeManager": DataTypeManager, } ) - config = ParserConfig(target_datetime_class=DatetimeClassType.Awaredatetime) + config = GraphQLParserConfig(target_datetime_class=DatetimeClassType.Awaredatetime) parser = GraphQLParser(source="type Query { id: ID }", config=config) assert parser.data_type_manager.target_datetime_class == DatetimeClassType.Awaredatetime diff --git a/tox.ini b/tox.ini index 6c14593f7..0e9355a94 100644 --- a/tox.ini +++ b/tox.ini @@ -117,6 +117,9 @@ description = Generate TypedDict files from config models (use --check to valida commands = datamodel-codegen --profile generate-config-dict {posargs} datamodel-codegen --profile parser-config-dict {posargs} + datamodel-codegen --profile graphql-parser-config-dict {posargs} + datamodel-codegen --profile jsonschema-parser-config-dict {posargs} + datamodel-codegen --profile openapi-parser-config-dict {posargs} datamodel-codegen --profile parse-config-dict {posargs} dependency_groups = dev no_default_groups = true