diff --git a/src/datamodel_code_generator/__init__.py b/src/datamodel_code_generator/__init__.py index 84a6747b4..cc2d395f7 100644 --- a/src/datamodel_code_generator/__init__.py +++ b/src/datamodel_code_generator/__init__.py @@ -290,6 +290,7 @@ def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915 no_alias: bool = False, formatters: list[Formatter] = DEFAULT_FORMATTERS, parent_scoped_naming: bool = False, + exclude_typename_field: bool = False, ) -> None: remote_text_cache: DefaultPutDict[str, str] = DefaultPutDict() if isinstance(input_, str): @@ -331,6 +332,7 @@ def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915 from datamodel_code_generator.parser.graphql import GraphQLParser # noqa: PLC0415 parser_class: type[Parser] = GraphQLParser + kwargs["exclude_typename_field"] = exclude_typename_field else: from datamodel_code_generator.parser.jsonschema import JsonSchemaParser # noqa: PLC0415 diff --git a/src/datamodel_code_generator/__main__.py b/src/datamodel_code_generator/__main__.py index 7e6804cfe..babb94551 100644 --- a/src/datamodel_code_generator/__main__.py +++ b/src/datamodel_code_generator/__main__.py @@ -317,6 +317,7 @@ def validate_root(cls, values: Any) -> Any: # noqa: N805 no_alias: bool = False formatters: list[Formatter] = DEFAULT_FORMATTERS parent_scoped_naming: bool = False + exclude_typename_field: bool = False def merge_args(self, args: Namespace) -> None: set_args = {f: getattr(args, f) for f in self.get_fields() if getattr(args, f) is not None} @@ -531,6 +532,7 @@ def main(args: Sequence[str] | None = None) -> Exit: # noqa: PLR0911, PLR0912, no_alias=config.no_alias, formatters=config.formatters, parent_scoped_naming=config.parent_scoped_naming, + exclude_typename_field=config.exclude_typename_field, ) except InvalidClassNameError as e: print(f"{e} You have to set `--class-name` option", file=sys.stderr) # noqa: T201 diff --git a/src/datamodel_code_generator/arguments.py b/src/datamodel_code_generator/arguments.py index 98ef513ff..1217af88c 100644 --- a/src/datamodel_code_generator/arguments.py +++ b/src/datamodel_code_generator/arguments.py @@ -404,6 +404,13 @@ def start_section(self, heading: str | None) -> None: action="store_true", default=None, ) +field_options.add_argument( + "--exclude-typename-field", + "--exclude_typename_field", + help="Exclude __typename field from the generated model", + action="store_true", + default=None, +) # ====================================================================================== # Options for templating output diff --git a/src/datamodel_code_generator/parser/graphql.py b/src/datamodel_code_generator/parser/graphql.py index 5db5db6ef..6aa4bf904 100644 --- a/src/datamodel_code_generator/parser/graphql.py +++ b/src/datamodel_code_generator/parser/graphql.py @@ -158,6 +158,7 @@ def __init__( # noqa: PLR0913 no_alias: bool = False, formatters: list[Formatter] = DEFAULT_FORMATTERS, parent_scoped_naming: bool = False, + exclude_typename_field: bool = False, ) -> None: super().__init__( source=source, @@ -241,6 +242,7 @@ def __init__( # noqa: PLR0913 self.data_model_union_type = data_model_union_type self.use_standard_collections = use_standard_collections self.use_union_operator = use_union_operator + self.exclude_typename_field = exclude_typename_field def _get_context_source_path_parts(self) -> Iterator[tuple[Source, list[str]]]: # TODO (denisart): Temporarily this method duplicates @@ -450,8 +452,8 @@ def parse_object_like( data_model_field_type = self.parse_field(field_name_, alias, field) fields.append(data_model_field_type) - - fields.append(self._typename_field(obj.name)) + if not self.exclude_typename_field: + fields.append(self._typename_field(obj.name)) base_classes = [] if hasattr(obj, "interfaces"): # pragma: no cover diff --git a/tests/data/expected/parser/graphql/exclude_typename_field.py b/tests/data/expected/parser/graphql/exclude_typename_field.py new file mode 100644 index 000000000..5fd981140 --- /dev/null +++ b/tests/data/expected/parser/graphql/exclude_typename_field.py @@ -0,0 +1,24 @@ +# generated by datamodel-codegen: +# filename: exclude-typename-field.graphql +# timestamp: 2019-07-26T00:00:00+00:00 + +from __future__ import annotations + +from typing import TypeAlias + +from pydantic import BaseModel + +Boolean: TypeAlias = bool +""" +The `Boolean` scalar type represents `true` or `false`. +""" + + +String: TypeAlias = str +""" +The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text. +""" + + +class A(BaseModel): + field: String diff --git a/tests/data/graphql/exclude-typename-field.graphql b/tests/data/graphql/exclude-typename-field.graphql new file mode 100644 index 000000000..74063d9c9 --- /dev/null +++ b/tests/data/graphql/exclude-typename-field.graphql @@ -0,0 +1,3 @@ +type A { + field: String! +} diff --git a/tests/parser/test_graphql.py b/tests/parser/test_graphql.py index f3e6df565..73491ffe9 100644 --- a/tests/parser/test_graphql.py +++ b/tests/parser/test_graphql.py @@ -47,3 +47,20 @@ def test_graphql_union_aliased_bug() -> None: if actual != expected: pass assert actual == expected + + +@freeze_time("2019-07-26") +def test_main_graphql_exclude_typename_field() -> None: + with TemporaryDirectory() as output_dir: + output_file: Path = Path(output_dir) / "output.py" + return_code: Exit = main([ + "--input", + str(GRAPHQL_DATA_PATH / "exclude-typename-field.graphql"), + "--output", + str(output_file), + "--input-file-type", + "graphql", + "--exclude-typename-field", + ]) + assert return_code == Exit.OK + assert output_file.read_text() == (EXPECTED_GRAPHQL_PATH / "exclude_typename_field.py").read_text()