datamodel-code-generator 0.11.12__py3-none-any.whl → 0.45.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- datamodel_code_generator/__init__.py +654 -185
- datamodel_code_generator/__main__.py +872 -388
- datamodel_code_generator/arguments.py +798 -0
- datamodel_code_generator/cli_options.py +295 -0
- datamodel_code_generator/format.py +292 -54
- datamodel_code_generator/http.py +85 -10
- datamodel_code_generator/imports.py +152 -43
- datamodel_code_generator/model/__init__.py +138 -1
- datamodel_code_generator/model/base.py +531 -120
- datamodel_code_generator/model/dataclass.py +211 -0
- datamodel_code_generator/model/enum.py +133 -12
- datamodel_code_generator/model/imports.py +22 -0
- datamodel_code_generator/model/msgspec.py +462 -0
- datamodel_code_generator/model/pydantic/__init__.py +30 -25
- datamodel_code_generator/model/pydantic/base_model.py +304 -100
- datamodel_code_generator/model/pydantic/custom_root_type.py +11 -2
- datamodel_code_generator/model/pydantic/dataclass.py +15 -4
- datamodel_code_generator/model/pydantic/imports.py +40 -27
- datamodel_code_generator/model/pydantic/types.py +188 -96
- datamodel_code_generator/model/pydantic_v2/__init__.py +51 -0
- datamodel_code_generator/model/pydantic_v2/base_model.py +268 -0
- datamodel_code_generator/model/pydantic_v2/imports.py +15 -0
- datamodel_code_generator/model/pydantic_v2/root_model.py +35 -0
- datamodel_code_generator/model/pydantic_v2/types.py +143 -0
- datamodel_code_generator/model/scalar.py +124 -0
- datamodel_code_generator/model/template/Enum.jinja2 +15 -2
- datamodel_code_generator/model/template/ScalarTypeAliasAnnotation.jinja2 +6 -0
- datamodel_code_generator/model/template/ScalarTypeAliasType.jinja2 +6 -0
- datamodel_code_generator/model/template/ScalarTypeStatement.jinja2 +6 -0
- datamodel_code_generator/model/template/TypeAliasAnnotation.jinja2 +20 -0
- datamodel_code_generator/model/template/TypeAliasType.jinja2 +20 -0
- datamodel_code_generator/model/template/TypeStatement.jinja2 +20 -0
- datamodel_code_generator/model/template/TypedDict.jinja2 +5 -0
- datamodel_code_generator/model/template/TypedDictClass.jinja2 +25 -0
- datamodel_code_generator/model/template/TypedDictFunction.jinja2 +24 -0
- datamodel_code_generator/model/template/UnionTypeAliasAnnotation.jinja2 +10 -0
- datamodel_code_generator/model/template/UnionTypeAliasType.jinja2 +10 -0
- datamodel_code_generator/model/template/UnionTypeStatement.jinja2 +10 -0
- datamodel_code_generator/model/template/dataclass.jinja2 +50 -0
- datamodel_code_generator/model/template/msgspec.jinja2 +55 -0
- datamodel_code_generator/model/template/pydantic/BaseModel.jinja2 +17 -4
- datamodel_code_generator/model/template/pydantic/BaseModel_root.jinja2 +12 -4
- datamodel_code_generator/model/template/pydantic/Config.jinja2 +1 -1
- datamodel_code_generator/model/template/pydantic/dataclass.jinja2 +15 -2
- datamodel_code_generator/model/template/pydantic_v2/BaseModel.jinja2 +57 -0
- datamodel_code_generator/model/template/pydantic_v2/ConfigDict.jinja2 +5 -0
- datamodel_code_generator/model/template/pydantic_v2/RootModel.jinja2 +48 -0
- datamodel_code_generator/model/type_alias.py +70 -0
- datamodel_code_generator/model/typed_dict.py +161 -0
- datamodel_code_generator/model/types.py +106 -0
- datamodel_code_generator/model/union.py +105 -0
- datamodel_code_generator/parser/__init__.py +30 -12
- datamodel_code_generator/parser/_graph.py +67 -0
- datamodel_code_generator/parser/_scc.py +171 -0
- datamodel_code_generator/parser/base.py +2426 -380
- datamodel_code_generator/parser/graphql.py +652 -0
- datamodel_code_generator/parser/jsonschema.py +2518 -647
- datamodel_code_generator/parser/openapi.py +631 -222
- datamodel_code_generator/py.typed +0 -0
- datamodel_code_generator/pydantic_patch.py +28 -0
- datamodel_code_generator/reference.py +672 -290
- datamodel_code_generator/types.py +521 -145
- datamodel_code_generator/util.py +155 -0
- datamodel_code_generator/watch.py +65 -0
- datamodel_code_generator-0.45.0.dist-info/METADATA +301 -0
- datamodel_code_generator-0.45.0.dist-info/RECORD +69 -0
- {datamodel_code_generator-0.11.12.dist-info → datamodel_code_generator-0.45.0.dist-info}/WHEEL +1 -1
- datamodel_code_generator-0.45.0.dist-info/entry_points.txt +2 -0
- datamodel_code_generator/version.py +0 -1
- datamodel_code_generator-0.11.12.dist-info/METADATA +0 -440
- datamodel_code_generator-0.11.12.dist-info/RECORD +0 -31
- datamodel_code_generator-0.11.12.dist-info/entry_points.txt +0 -3
- {datamodel_code_generator-0.11.12.dist-info → datamodel_code_generator-0.45.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
"""GraphQL schema parser implementation.
|
|
2
|
+
|
|
3
|
+
Parses GraphQL schema files to generate Python data models including
|
|
4
|
+
objects, interfaces, enums, scalars, inputs, and union types.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import (
|
|
11
|
+
TYPE_CHECKING,
|
|
12
|
+
Any,
|
|
13
|
+
Callable,
|
|
14
|
+
)
|
|
15
|
+
from urllib.parse import ParseResult
|
|
16
|
+
|
|
17
|
+
from datamodel_code_generator import (
|
|
18
|
+
DEFAULT_SHARED_MODULE_NAME,
|
|
19
|
+
AllOfMergeMode,
|
|
20
|
+
DataclassArguments,
|
|
21
|
+
DefaultPutDict,
|
|
22
|
+
LiteralType,
|
|
23
|
+
PythonVersion,
|
|
24
|
+
PythonVersionMin,
|
|
25
|
+
ReadOnlyWriteOnlyModelType,
|
|
26
|
+
ReuseScope,
|
|
27
|
+
snooper_to_methods,
|
|
28
|
+
)
|
|
29
|
+
from datamodel_code_generator.format import DEFAULT_FORMATTERS, DatetimeClassType, Formatter
|
|
30
|
+
from datamodel_code_generator.model import DataModel, DataModelFieldBase
|
|
31
|
+
from datamodel_code_generator.model import pydantic as pydantic_model
|
|
32
|
+
from datamodel_code_generator.model.dataclass import DataClass
|
|
33
|
+
from datamodel_code_generator.model.enum import SPECIALIZED_ENUM_TYPE_MATCH, Enum
|
|
34
|
+
from datamodel_code_generator.model.scalar import DataTypeScalarBackport
|
|
35
|
+
from datamodel_code_generator.model.union import DataTypeUnionBackport
|
|
36
|
+
from datamodel_code_generator.parser.base import (
|
|
37
|
+
DataType,
|
|
38
|
+
Parser,
|
|
39
|
+
Source,
|
|
40
|
+
escape_characters,
|
|
41
|
+
)
|
|
42
|
+
from datamodel_code_generator.reference import ModelType, Reference
|
|
43
|
+
from datamodel_code_generator.types import DataTypeManager, StrictTypes, Types
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
import graphql
|
|
47
|
+
except ImportError as exc: # pragma: no cover
|
|
48
|
+
msg = "Please run `$pip install 'datamodel-code-generator[graphql]`' to generate data-model from a GraphQL schema."
|
|
49
|
+
raise Exception(msg) from exc # noqa: TRY002
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if TYPE_CHECKING:
|
|
53
|
+
from collections import defaultdict
|
|
54
|
+
from collections.abc import Iterable, Iterator, Mapping, Sequence
|
|
55
|
+
|
|
56
|
+
# graphql-core >=3.2.7 removed TypeResolvers in favor of TypeFields.kind.
|
|
57
|
+
# Normalize to a single callable for resolving type kinds.
|
|
58
|
+
try: # graphql-core < 3.2.7
|
|
59
|
+
graphql_resolver_kind = graphql.type.introspection.TypeResolvers().kind # pyright: ignore[reportAttributeAccessIssue]
|
|
60
|
+
except AttributeError: # pragma: no cover - executed on newer graphql-core
|
|
61
|
+
graphql_resolver_kind = graphql.type.introspection.TypeFields.kind # pyright: ignore[reportAttributeAccessIssue]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def build_graphql_schema(schema_str: str) -> graphql.GraphQLSchema:
|
|
65
|
+
"""Build a graphql schema from a string."""
|
|
66
|
+
schema = graphql.build_schema(schema_str)
|
|
67
|
+
return graphql.lexicographic_sort_schema(schema)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@snooper_to_methods()
|
|
71
|
+
class GraphQLParser(Parser):
|
|
72
|
+
"""Parser for GraphQL schema files."""
|
|
73
|
+
|
|
74
|
+
# raw graphql schema as `graphql-core` object
|
|
75
|
+
raw_obj: graphql.GraphQLSchema
|
|
76
|
+
# all processed graphql objects
|
|
77
|
+
# mapper from an object name (unique) to an object
|
|
78
|
+
all_graphql_objects: dict[str, graphql.GraphQLNamedType]
|
|
79
|
+
# a reference for each object
|
|
80
|
+
# mapper from an object name to his reference
|
|
81
|
+
references: dict[str, Reference] = {} # noqa: RUF012
|
|
82
|
+
# mapper from graphql type to all objects with this type
|
|
83
|
+
# `graphql.type.introspection.TypeKind` -- an enum with all supported types
|
|
84
|
+
# `graphql.GraphQLNamedType` -- base type for each graphql object
|
|
85
|
+
# see `graphql-core` for more details
|
|
86
|
+
support_graphql_types: dict[graphql.type.introspection.TypeKind, list[graphql.GraphQLNamedType]]
|
|
87
|
+
# graphql types order for render
|
|
88
|
+
# may be as a parameter in the future
|
|
89
|
+
parse_order: list[graphql.type.introspection.TypeKind] = [ # noqa: RUF012
|
|
90
|
+
graphql.type.introspection.TypeKind.SCALAR,
|
|
91
|
+
graphql.type.introspection.TypeKind.ENUM,
|
|
92
|
+
graphql.type.introspection.TypeKind.INTERFACE,
|
|
93
|
+
graphql.type.introspection.TypeKind.OBJECT,
|
|
94
|
+
graphql.type.introspection.TypeKind.INPUT_OBJECT,
|
|
95
|
+
graphql.type.introspection.TypeKind.UNION,
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
def __init__( # noqa: PLR0913
|
|
99
|
+
self,
|
|
100
|
+
source: str | Path | ParseResult,
|
|
101
|
+
*,
|
|
102
|
+
data_model_type: type[DataModel] = pydantic_model.BaseModel,
|
|
103
|
+
data_model_root_type: type[DataModel] = pydantic_model.CustomRootType,
|
|
104
|
+
data_model_scalar_type: type[DataModel] = DataTypeScalarBackport,
|
|
105
|
+
data_model_union_type: type[DataModel] = DataTypeUnionBackport,
|
|
106
|
+
data_type_manager_type: type[DataTypeManager] = pydantic_model.DataTypeManager,
|
|
107
|
+
data_model_field_type: type[DataModelFieldBase] = pydantic_model.DataModelField,
|
|
108
|
+
base_class: str | None = None,
|
|
109
|
+
additional_imports: list[str] | None = None,
|
|
110
|
+
custom_template_dir: Path | None = None,
|
|
111
|
+
extra_template_data: defaultdict[str, dict[str, Any]] | None = None,
|
|
112
|
+
target_python_version: PythonVersion = PythonVersionMin,
|
|
113
|
+
dump_resolve_reference_action: Callable[[Iterable[str]], str] | None = None,
|
|
114
|
+
validation: bool = False,
|
|
115
|
+
field_constraints: bool = False,
|
|
116
|
+
snake_case_field: bool = False,
|
|
117
|
+
strip_default_none: bool = False,
|
|
118
|
+
aliases: Mapping[str, str] | None = None,
|
|
119
|
+
allow_population_by_field_name: bool = False,
|
|
120
|
+
apply_default_values_for_required_fields: bool = False,
|
|
121
|
+
allow_extra_fields: bool = False,
|
|
122
|
+
extra_fields: str | None = None,
|
|
123
|
+
force_optional_for_required_fields: bool = False,
|
|
124
|
+
class_name: str | None = None,
|
|
125
|
+
use_standard_collections: bool = False,
|
|
126
|
+
base_path: Path | None = None,
|
|
127
|
+
use_schema_description: bool = False,
|
|
128
|
+
use_field_description: bool = False,
|
|
129
|
+
use_attribute_docstrings: bool = False,
|
|
130
|
+
use_inline_field_description: bool = False,
|
|
131
|
+
use_default_kwarg: bool = False,
|
|
132
|
+
reuse_model: bool = False,
|
|
133
|
+
reuse_scope: ReuseScope | None = None,
|
|
134
|
+
shared_module_name: str = DEFAULT_SHARED_MODULE_NAME,
|
|
135
|
+
encoding: str = "utf-8",
|
|
136
|
+
enum_field_as_literal: LiteralType | None = None,
|
|
137
|
+
set_default_enum_member: bool = False,
|
|
138
|
+
use_subclass_enum: bool = False,
|
|
139
|
+
use_specialized_enum: bool = True,
|
|
140
|
+
strict_nullable: bool = False,
|
|
141
|
+
use_generic_container_types: bool = False,
|
|
142
|
+
enable_faux_immutability: bool = False,
|
|
143
|
+
remote_text_cache: DefaultPutDict[str, str] | None = None,
|
|
144
|
+
disable_appending_item_suffix: bool = False,
|
|
145
|
+
strict_types: Sequence[StrictTypes] | None = None,
|
|
146
|
+
empty_enum_field_name: str | None = None,
|
|
147
|
+
custom_class_name_generator: Callable[[str], str] | None = None,
|
|
148
|
+
field_extra_keys: set[str] | None = None,
|
|
149
|
+
field_include_all_keys: bool = False,
|
|
150
|
+
field_extra_keys_without_x_prefix: set[str] | None = None,
|
|
151
|
+
wrap_string_literal: bool | None = None,
|
|
152
|
+
use_title_as_name: bool = False,
|
|
153
|
+
use_operation_id_as_name: bool = False,
|
|
154
|
+
use_unique_items_as_set: bool = False,
|
|
155
|
+
allof_merge_mode: AllOfMergeMode = AllOfMergeMode.Constraints,
|
|
156
|
+
http_headers: Sequence[tuple[str, str]] | None = None,
|
|
157
|
+
http_ignore_tls: bool = False,
|
|
158
|
+
use_annotated: bool = False,
|
|
159
|
+
use_non_positive_negative_number_constrained_types: bool = False,
|
|
160
|
+
use_decimal_for_multiple_of: bool = False,
|
|
161
|
+
original_field_name_delimiter: str | None = None,
|
|
162
|
+
use_double_quotes: bool = False,
|
|
163
|
+
use_union_operator: bool = False,
|
|
164
|
+
allow_responses_without_content: bool = False,
|
|
165
|
+
collapse_root_models: bool = False,
|
|
166
|
+
skip_root_model: bool = False,
|
|
167
|
+
use_type_alias: bool = False,
|
|
168
|
+
special_field_name_prefix: str | None = None,
|
|
169
|
+
remove_special_field_name_prefix: bool = False,
|
|
170
|
+
capitalise_enum_members: bool = False,
|
|
171
|
+
keep_model_order: bool = False,
|
|
172
|
+
use_one_literal_as_default: bool = False,
|
|
173
|
+
use_enum_values_in_discriminator: bool = False,
|
|
174
|
+
known_third_party: list[str] | None = None,
|
|
175
|
+
custom_formatters: list[str] | None = None,
|
|
176
|
+
custom_formatters_kwargs: dict[str, Any] | None = None,
|
|
177
|
+
use_pendulum: bool = False,
|
|
178
|
+
http_query_parameters: Sequence[tuple[str, str]] | None = None,
|
|
179
|
+
treat_dot_as_module: bool = False,
|
|
180
|
+
use_exact_imports: bool = False,
|
|
181
|
+
default_field_extras: dict[str, Any] | None = None,
|
|
182
|
+
target_datetime_class: DatetimeClassType = DatetimeClassType.Datetime,
|
|
183
|
+
keyword_only: bool = False,
|
|
184
|
+
frozen_dataclasses: bool = False,
|
|
185
|
+
no_alias: bool = False,
|
|
186
|
+
formatters: list[Formatter] = DEFAULT_FORMATTERS,
|
|
187
|
+
parent_scoped_naming: bool = False,
|
|
188
|
+
dataclass_arguments: DataclassArguments | None = None,
|
|
189
|
+
type_mappings: list[str] | None = None,
|
|
190
|
+
read_only_write_only_model_type: ReadOnlyWriteOnlyModelType | None = None,
|
|
191
|
+
use_serialize_as_any: bool = False,
|
|
192
|
+
use_frozen_field: bool = False,
|
|
193
|
+
) -> None:
|
|
194
|
+
"""Initialize the GraphQL parser with configuration options."""
|
|
195
|
+
super().__init__(
|
|
196
|
+
source=source,
|
|
197
|
+
data_model_type=data_model_type,
|
|
198
|
+
data_model_root_type=data_model_root_type,
|
|
199
|
+
data_type_manager_type=data_type_manager_type,
|
|
200
|
+
data_model_field_type=data_model_field_type,
|
|
201
|
+
base_class=base_class,
|
|
202
|
+
additional_imports=additional_imports,
|
|
203
|
+
custom_template_dir=custom_template_dir,
|
|
204
|
+
extra_template_data=extra_template_data,
|
|
205
|
+
target_python_version=target_python_version,
|
|
206
|
+
dump_resolve_reference_action=dump_resolve_reference_action,
|
|
207
|
+
validation=validation,
|
|
208
|
+
field_constraints=field_constraints,
|
|
209
|
+
snake_case_field=snake_case_field,
|
|
210
|
+
strip_default_none=strip_default_none,
|
|
211
|
+
aliases=aliases,
|
|
212
|
+
allow_population_by_field_name=allow_population_by_field_name,
|
|
213
|
+
allow_extra_fields=allow_extra_fields,
|
|
214
|
+
extra_fields=extra_fields,
|
|
215
|
+
apply_default_values_for_required_fields=apply_default_values_for_required_fields,
|
|
216
|
+
force_optional_for_required_fields=force_optional_for_required_fields,
|
|
217
|
+
class_name=class_name,
|
|
218
|
+
use_standard_collections=use_standard_collections,
|
|
219
|
+
base_path=base_path,
|
|
220
|
+
use_schema_description=use_schema_description,
|
|
221
|
+
use_field_description=use_field_description,
|
|
222
|
+
use_attribute_docstrings=use_attribute_docstrings,
|
|
223
|
+
use_inline_field_description=use_inline_field_description,
|
|
224
|
+
use_default_kwarg=use_default_kwarg,
|
|
225
|
+
reuse_model=reuse_model,
|
|
226
|
+
reuse_scope=reuse_scope,
|
|
227
|
+
shared_module_name=shared_module_name,
|
|
228
|
+
encoding=encoding,
|
|
229
|
+
enum_field_as_literal=enum_field_as_literal,
|
|
230
|
+
use_one_literal_as_default=use_one_literal_as_default,
|
|
231
|
+
use_enum_values_in_discriminator=use_enum_values_in_discriminator,
|
|
232
|
+
set_default_enum_member=set_default_enum_member,
|
|
233
|
+
use_subclass_enum=use_subclass_enum,
|
|
234
|
+
use_specialized_enum=use_specialized_enum,
|
|
235
|
+
strict_nullable=strict_nullable,
|
|
236
|
+
use_generic_container_types=use_generic_container_types,
|
|
237
|
+
enable_faux_immutability=enable_faux_immutability,
|
|
238
|
+
remote_text_cache=remote_text_cache,
|
|
239
|
+
disable_appending_item_suffix=disable_appending_item_suffix,
|
|
240
|
+
strict_types=strict_types,
|
|
241
|
+
empty_enum_field_name=empty_enum_field_name,
|
|
242
|
+
custom_class_name_generator=custom_class_name_generator,
|
|
243
|
+
field_extra_keys=field_extra_keys,
|
|
244
|
+
field_include_all_keys=field_include_all_keys,
|
|
245
|
+
field_extra_keys_without_x_prefix=field_extra_keys_without_x_prefix,
|
|
246
|
+
wrap_string_literal=wrap_string_literal,
|
|
247
|
+
use_title_as_name=use_title_as_name,
|
|
248
|
+
use_operation_id_as_name=use_operation_id_as_name,
|
|
249
|
+
use_unique_items_as_set=use_unique_items_as_set,
|
|
250
|
+
allof_merge_mode=allof_merge_mode,
|
|
251
|
+
http_headers=http_headers,
|
|
252
|
+
http_ignore_tls=http_ignore_tls,
|
|
253
|
+
use_annotated=use_annotated,
|
|
254
|
+
use_non_positive_negative_number_constrained_types=use_non_positive_negative_number_constrained_types,
|
|
255
|
+
use_decimal_for_multiple_of=use_decimal_for_multiple_of,
|
|
256
|
+
original_field_name_delimiter=original_field_name_delimiter,
|
|
257
|
+
use_double_quotes=use_double_quotes,
|
|
258
|
+
use_union_operator=use_union_operator,
|
|
259
|
+
allow_responses_without_content=allow_responses_without_content,
|
|
260
|
+
collapse_root_models=collapse_root_models,
|
|
261
|
+
skip_root_model=skip_root_model,
|
|
262
|
+
use_type_alias=use_type_alias,
|
|
263
|
+
special_field_name_prefix=special_field_name_prefix,
|
|
264
|
+
remove_special_field_name_prefix=remove_special_field_name_prefix,
|
|
265
|
+
capitalise_enum_members=capitalise_enum_members,
|
|
266
|
+
keep_model_order=keep_model_order,
|
|
267
|
+
known_third_party=known_third_party,
|
|
268
|
+
custom_formatters=custom_formatters,
|
|
269
|
+
custom_formatters_kwargs=custom_formatters_kwargs,
|
|
270
|
+
use_pendulum=use_pendulum,
|
|
271
|
+
http_query_parameters=http_query_parameters,
|
|
272
|
+
treat_dot_as_module=treat_dot_as_module,
|
|
273
|
+
use_exact_imports=use_exact_imports,
|
|
274
|
+
default_field_extras=default_field_extras,
|
|
275
|
+
target_datetime_class=target_datetime_class,
|
|
276
|
+
keyword_only=keyword_only,
|
|
277
|
+
frozen_dataclasses=frozen_dataclasses,
|
|
278
|
+
no_alias=no_alias,
|
|
279
|
+
formatters=formatters,
|
|
280
|
+
parent_scoped_naming=parent_scoped_naming,
|
|
281
|
+
dataclass_arguments=dataclass_arguments,
|
|
282
|
+
type_mappings=type_mappings,
|
|
283
|
+
read_only_write_only_model_type=read_only_write_only_model_type,
|
|
284
|
+
use_serialize_as_any=use_serialize_as_any,
|
|
285
|
+
use_frozen_field=use_frozen_field,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
self.data_model_scalar_type = data_model_scalar_type
|
|
289
|
+
self.data_model_union_type = data_model_union_type
|
|
290
|
+
self.use_standard_collections = use_standard_collections
|
|
291
|
+
self.use_union_operator = use_union_operator
|
|
292
|
+
|
|
293
|
+
def _get_context_source_path_parts(self) -> Iterator[tuple[Source, list[str]]]:
|
|
294
|
+
# TODO (denisart): Temporarily this method duplicates
|
|
295
|
+
# the method `datamodel_code_generator.parser.jsonschema.JsonSchemaParser._get_context_source_path_parts`.
|
|
296
|
+
|
|
297
|
+
if isinstance(self.source, list) or ( # pragma: no cover
|
|
298
|
+
isinstance(self.source, Path) and self.source.is_dir()
|
|
299
|
+
): # pragma: no cover
|
|
300
|
+
self.current_source_path = Path()
|
|
301
|
+
self.model_resolver.after_load_files = {
|
|
302
|
+
self.base_path.joinpath(s.path).resolve().as_posix() for s in self.iter_source
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
for source in self.iter_source:
|
|
306
|
+
if isinstance(self.source, ParseResult): # pragma: no cover
|
|
307
|
+
path_parts = self.get_url_path_parts(self.source)
|
|
308
|
+
else:
|
|
309
|
+
path_parts = list(source.path.parts)
|
|
310
|
+
if self.current_source_path is not None: # pragma: no cover
|
|
311
|
+
self.current_source_path = source.path
|
|
312
|
+
with (
|
|
313
|
+
self.model_resolver.current_base_path_context(source.path.parent),
|
|
314
|
+
self.model_resolver.current_root_context(path_parts),
|
|
315
|
+
):
|
|
316
|
+
yield source, path_parts
|
|
317
|
+
|
|
318
|
+
def _resolve_types(self, paths: list[str], schema: graphql.GraphQLSchema) -> None:
|
|
319
|
+
for type_name, type_ in schema.type_map.items():
|
|
320
|
+
if type_name.startswith("__"):
|
|
321
|
+
continue
|
|
322
|
+
|
|
323
|
+
if type_name in {"Query", "Mutation"}:
|
|
324
|
+
continue
|
|
325
|
+
|
|
326
|
+
resolved_type = graphql_resolver_kind(type_, None)
|
|
327
|
+
|
|
328
|
+
if resolved_type in self.support_graphql_types: # pragma: no cover
|
|
329
|
+
self.all_graphql_objects[type_.name] = type_
|
|
330
|
+
# TODO: need a special method for each graph type
|
|
331
|
+
self.references[type_.name] = Reference(
|
|
332
|
+
path=f"{paths!s}/{resolved_type.value}/{type_.name}",
|
|
333
|
+
name=type_.name,
|
|
334
|
+
original_name=type_.name,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
self.support_graphql_types[resolved_type].append(type_)
|
|
338
|
+
|
|
339
|
+
def _create_data_model(self, model_type: type[DataModel] | None = None, **kwargs: Any) -> DataModel:
|
|
340
|
+
"""Create data model instance with dataclass_arguments support for DataClass."""
|
|
341
|
+
data_model_class = model_type or self.data_model_type
|
|
342
|
+
if issubclass(data_model_class, DataClass):
|
|
343
|
+
# Use dataclass_arguments from kwargs, or fall back to self.dataclass_arguments
|
|
344
|
+
# If both are None, construct from legacy frozen_dataclasses/keyword_only flags
|
|
345
|
+
dataclass_arguments = kwargs.pop("dataclass_arguments", None)
|
|
346
|
+
if dataclass_arguments is None:
|
|
347
|
+
dataclass_arguments = self.dataclass_arguments
|
|
348
|
+
if dataclass_arguments is None:
|
|
349
|
+
# Construct from legacy flags for library API compatibility
|
|
350
|
+
dataclass_arguments = {}
|
|
351
|
+
if self.frozen_dataclasses:
|
|
352
|
+
dataclass_arguments["frozen"] = True
|
|
353
|
+
if self.keyword_only:
|
|
354
|
+
dataclass_arguments["kw_only"] = True
|
|
355
|
+
kwargs["dataclass_arguments"] = dataclass_arguments
|
|
356
|
+
kwargs.pop("frozen", None)
|
|
357
|
+
kwargs.pop("keyword_only", None)
|
|
358
|
+
else:
|
|
359
|
+
kwargs.pop("dataclass_arguments", None)
|
|
360
|
+
return data_model_class(**kwargs)
|
|
361
|
+
|
|
362
|
+
def _typename_field(self, name: str) -> DataModelFieldBase:
|
|
363
|
+
return self.data_model_field_type(
|
|
364
|
+
name="typename__",
|
|
365
|
+
data_type=DataType(
|
|
366
|
+
literals=[name],
|
|
367
|
+
use_union_operator=self.use_union_operator,
|
|
368
|
+
use_standard_collections=self.use_standard_collections,
|
|
369
|
+
),
|
|
370
|
+
default=name,
|
|
371
|
+
use_annotated=self.use_annotated,
|
|
372
|
+
required=False,
|
|
373
|
+
alias="__typename",
|
|
374
|
+
use_one_literal_as_default=True,
|
|
375
|
+
use_default_kwarg=self.use_default_kwarg,
|
|
376
|
+
has_default=True,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
def _get_default( # noqa: PLR6301
|
|
380
|
+
self,
|
|
381
|
+
field: graphql.GraphQLField | graphql.GraphQLInputField,
|
|
382
|
+
final_data_type: DataType,
|
|
383
|
+
*,
|
|
384
|
+
required: bool,
|
|
385
|
+
) -> Any:
|
|
386
|
+
if isinstance(field, graphql.GraphQLInputField): # pragma: no cover
|
|
387
|
+
if field.default_value == graphql.pyutils.Undefined: # pragma: no cover
|
|
388
|
+
return None
|
|
389
|
+
return field.default_value
|
|
390
|
+
if required is False and final_data_type.is_list:
|
|
391
|
+
return None
|
|
392
|
+
|
|
393
|
+
return None
|
|
394
|
+
|
|
395
|
+
def parse_scalar(self, scalar_graphql_object: graphql.GraphQLScalarType) -> None:
|
|
396
|
+
"""Parse a GraphQL scalar type and add it to results."""
|
|
397
|
+
self.results.append(
|
|
398
|
+
self.data_model_scalar_type(
|
|
399
|
+
reference=self.references[scalar_graphql_object.name],
|
|
400
|
+
fields=[],
|
|
401
|
+
custom_template_dir=self.custom_template_dir,
|
|
402
|
+
extra_template_data=self.extra_template_data,
|
|
403
|
+
description=scalar_graphql_object.description,
|
|
404
|
+
)
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
def should_parse_enum_as_literal(self, obj: graphql.GraphQLEnumType) -> bool:
|
|
408
|
+
"""Determine if an enum should be parsed as a literal type."""
|
|
409
|
+
return self.enum_field_as_literal == LiteralType.All or (
|
|
410
|
+
self.enum_field_as_literal == LiteralType.One and len(obj.values) == 1
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
def parse_enum(self, enum_object: graphql.GraphQLEnumType) -> None:
|
|
414
|
+
"""Parse a GraphQL enum type and add it to results."""
|
|
415
|
+
if self.should_parse_enum_as_literal(enum_object):
|
|
416
|
+
return self.parse_enum_as_literal(enum_object)
|
|
417
|
+
return self.parse_enum_as_enum_class(enum_object)
|
|
418
|
+
|
|
419
|
+
def parse_enum_as_literal(self, enum_object: graphql.GraphQLEnumType) -> None:
|
|
420
|
+
"""Parse enum values as a Literal type."""
|
|
421
|
+
data_type = self.data_type(literals=list(enum_object.values.keys()))
|
|
422
|
+
data_model_type = self._create_data_model(
|
|
423
|
+
model_type=self.data_model_root_type,
|
|
424
|
+
reference=self.references[enum_object.name],
|
|
425
|
+
fields=[
|
|
426
|
+
self.data_model_field_type(
|
|
427
|
+
required=True,
|
|
428
|
+
data_type=data_type,
|
|
429
|
+
)
|
|
430
|
+
],
|
|
431
|
+
custom_base_class=self.base_class,
|
|
432
|
+
custom_template_dir=self.custom_template_dir,
|
|
433
|
+
extra_template_data=self.extra_template_data,
|
|
434
|
+
path=self.current_source_path,
|
|
435
|
+
description=enum_object.description,
|
|
436
|
+
)
|
|
437
|
+
self.results.append(data_model_type)
|
|
438
|
+
|
|
439
|
+
def parse_enum_as_enum_class(self, enum_object: graphql.GraphQLEnumType) -> None:
|
|
440
|
+
"""Parse enum values as an Enum class."""
|
|
441
|
+
enum_fields: list[DataModelFieldBase] = []
|
|
442
|
+
exclude_field_names: set[str] = set()
|
|
443
|
+
|
|
444
|
+
for value_name, value in enum_object.values.items():
|
|
445
|
+
default = f"'{value_name.translate(escape_characters)}'" if isinstance(value_name, str) else value_name
|
|
446
|
+
|
|
447
|
+
field_name = self.model_resolver.get_valid_field_name(
|
|
448
|
+
value_name, excludes=exclude_field_names, model_type=ModelType.ENUM
|
|
449
|
+
)
|
|
450
|
+
exclude_field_names.add(field_name)
|
|
451
|
+
|
|
452
|
+
enum_fields.append(
|
|
453
|
+
self.data_model_field_type(
|
|
454
|
+
name=field_name,
|
|
455
|
+
data_type=self.data_type_manager.get_data_type(
|
|
456
|
+
Types.string,
|
|
457
|
+
),
|
|
458
|
+
default=default,
|
|
459
|
+
required=True,
|
|
460
|
+
strip_default_none=self.strip_default_none,
|
|
461
|
+
has_default=True,
|
|
462
|
+
use_field_description=value.description is not None,
|
|
463
|
+
original_name=None,
|
|
464
|
+
)
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
enum_cls: type[Enum] = Enum
|
|
468
|
+
if (
|
|
469
|
+
self.target_python_version.has_strenum
|
|
470
|
+
and self.use_specialized_enum
|
|
471
|
+
and (specialized_type := SPECIALIZED_ENUM_TYPE_MATCH.get(Types.string))
|
|
472
|
+
):
|
|
473
|
+
# If specialized enum is available in the target Python version, use it
|
|
474
|
+
enum_cls = specialized_type
|
|
475
|
+
|
|
476
|
+
enum: Enum = enum_cls(
|
|
477
|
+
reference=self.references[enum_object.name],
|
|
478
|
+
fields=enum_fields,
|
|
479
|
+
path=self.current_source_path,
|
|
480
|
+
description=enum_object.description,
|
|
481
|
+
type_=Types.string if self.use_subclass_enum else None,
|
|
482
|
+
custom_template_dir=self.custom_template_dir,
|
|
483
|
+
)
|
|
484
|
+
self.results.append(enum)
|
|
485
|
+
|
|
486
|
+
def parse_field(
|
|
487
|
+
self,
|
|
488
|
+
field_name: str,
|
|
489
|
+
alias: str | None,
|
|
490
|
+
field: graphql.GraphQLField | graphql.GraphQLInputField,
|
|
491
|
+
) -> DataModelFieldBase:
|
|
492
|
+
"""Parse a GraphQL field and return a data model field."""
|
|
493
|
+
final_data_type = DataType(
|
|
494
|
+
is_optional=True,
|
|
495
|
+
use_union_operator=self.use_union_operator,
|
|
496
|
+
use_standard_collections=self.use_standard_collections,
|
|
497
|
+
)
|
|
498
|
+
data_type = final_data_type
|
|
499
|
+
obj = field.type
|
|
500
|
+
|
|
501
|
+
while graphql.is_list_type(obj) or graphql.is_non_null_type(obj):
|
|
502
|
+
if graphql.is_list_type(obj):
|
|
503
|
+
data_type.is_list = True
|
|
504
|
+
|
|
505
|
+
new_data_type = DataType(
|
|
506
|
+
is_optional=True,
|
|
507
|
+
use_union_operator=self.use_union_operator,
|
|
508
|
+
use_standard_collections=self.use_standard_collections,
|
|
509
|
+
)
|
|
510
|
+
data_type.data_types = [new_data_type]
|
|
511
|
+
|
|
512
|
+
data_type = new_data_type
|
|
513
|
+
elif graphql.is_non_null_type(obj): # pragma: no cover
|
|
514
|
+
data_type.is_optional = False
|
|
515
|
+
|
|
516
|
+
obj = graphql.assert_wrapping_type(obj)
|
|
517
|
+
obj = obj.of_type
|
|
518
|
+
|
|
519
|
+
obj = graphql.assert_named_type(obj)
|
|
520
|
+
if obj.name in self.references:
|
|
521
|
+
data_type.reference = self.references[obj.name]
|
|
522
|
+
else: # pragma: no cover
|
|
523
|
+
# Only happens for Query and Mutation root types
|
|
524
|
+
data_type.type = obj.name
|
|
525
|
+
|
|
526
|
+
required = (not self.force_optional_for_required_fields) and (not final_data_type.is_optional)
|
|
527
|
+
|
|
528
|
+
default = self._get_default(field, final_data_type, required=required)
|
|
529
|
+
extras = {} if self.default_field_extras is None else self.default_field_extras.copy()
|
|
530
|
+
|
|
531
|
+
if field.description is not None: # pragma: no cover
|
|
532
|
+
extras["description"] = field.description
|
|
533
|
+
|
|
534
|
+
return self.data_model_field_type(
|
|
535
|
+
name=field_name,
|
|
536
|
+
default=default,
|
|
537
|
+
data_type=final_data_type,
|
|
538
|
+
required=required,
|
|
539
|
+
extras=extras,
|
|
540
|
+
alias=alias,
|
|
541
|
+
strip_default_none=self.strip_default_none,
|
|
542
|
+
use_annotated=self.use_annotated,
|
|
543
|
+
use_serialize_as_any=self.use_serialize_as_any,
|
|
544
|
+
use_field_description=self.use_field_description,
|
|
545
|
+
use_inline_field_description=self.use_inline_field_description,
|
|
546
|
+
use_default_kwarg=self.use_default_kwarg,
|
|
547
|
+
original_name=field_name,
|
|
548
|
+
has_default=default is not None,
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
def parse_object_like(
|
|
552
|
+
self,
|
|
553
|
+
obj: graphql.GraphQLInterfaceType | graphql.GraphQLObjectType | graphql.GraphQLInputObjectType,
|
|
554
|
+
) -> None:
|
|
555
|
+
"""Parse a GraphQL object-like type and add it to results."""
|
|
556
|
+
fields = []
|
|
557
|
+
exclude_field_names: set[str] = set()
|
|
558
|
+
|
|
559
|
+
for field_name, field in obj.fields.items():
|
|
560
|
+
field_name_, alias = self.model_resolver.get_valid_field_name_and_alias(
|
|
561
|
+
field_name,
|
|
562
|
+
excludes=exclude_field_names,
|
|
563
|
+
model_type=self.field_name_model_type,
|
|
564
|
+
class_name=obj.name,
|
|
565
|
+
)
|
|
566
|
+
exclude_field_names.add(field_name_)
|
|
567
|
+
|
|
568
|
+
data_model_field_type = self.parse_field(field_name_, alias, field)
|
|
569
|
+
fields.append(data_model_field_type)
|
|
570
|
+
|
|
571
|
+
fields.append(self._typename_field(obj.name))
|
|
572
|
+
|
|
573
|
+
base_classes = []
|
|
574
|
+
if hasattr(obj, "interfaces"): # pragma: no cover
|
|
575
|
+
base_classes = [self.references[i.name] for i in obj.interfaces] # pyright: ignore[reportAttributeAccessIssue]
|
|
576
|
+
|
|
577
|
+
data_model_type = self._create_data_model(
|
|
578
|
+
reference=self.references[obj.name],
|
|
579
|
+
fields=fields,
|
|
580
|
+
base_classes=base_classes,
|
|
581
|
+
custom_base_class=self.base_class,
|
|
582
|
+
custom_template_dir=self.custom_template_dir,
|
|
583
|
+
extra_template_data=self.extra_template_data,
|
|
584
|
+
path=self.current_source_path,
|
|
585
|
+
description=obj.description,
|
|
586
|
+
keyword_only=self.keyword_only,
|
|
587
|
+
treat_dot_as_module=self.treat_dot_as_module,
|
|
588
|
+
dataclass_arguments=self.dataclass_arguments,
|
|
589
|
+
)
|
|
590
|
+
self.results.append(data_model_type)
|
|
591
|
+
|
|
592
|
+
def parse_interface(self, interface_graphql_object: graphql.GraphQLInterfaceType) -> None:
|
|
593
|
+
"""Parse a GraphQL interface type and add it to results."""
|
|
594
|
+
self.parse_object_like(interface_graphql_object)
|
|
595
|
+
|
|
596
|
+
def parse_object(self, graphql_object: graphql.GraphQLObjectType) -> None:
|
|
597
|
+
"""Parse a GraphQL object type and add it to results."""
|
|
598
|
+
self.parse_object_like(graphql_object)
|
|
599
|
+
|
|
600
|
+
def parse_input_object(self, input_graphql_object: graphql.GraphQLInputObjectType) -> None:
|
|
601
|
+
"""Parse a GraphQL input object type and add it to results."""
|
|
602
|
+
self.parse_object_like(input_graphql_object) # pragma: no cover
|
|
603
|
+
|
|
604
|
+
def parse_union(self, union_object: graphql.GraphQLUnionType) -> None:
|
|
605
|
+
"""Parse a GraphQL union type and add it to results."""
|
|
606
|
+
fields = [self.data_model_field_type(name=type_.name, data_type=DataType()) for type_ in union_object.types]
|
|
607
|
+
|
|
608
|
+
data_model_type = self.data_model_union_type(
|
|
609
|
+
reference=self.references[union_object.name],
|
|
610
|
+
fields=fields,
|
|
611
|
+
custom_base_class=self.base_class,
|
|
612
|
+
custom_template_dir=self.custom_template_dir,
|
|
613
|
+
extra_template_data=self.extra_template_data,
|
|
614
|
+
path=self.current_source_path,
|
|
615
|
+
description=union_object.description,
|
|
616
|
+
)
|
|
617
|
+
self.results.append(data_model_type)
|
|
618
|
+
|
|
619
|
+
def parse_raw(self) -> None:
|
|
620
|
+
"""Parse the raw GraphQL schema and generate all data models."""
|
|
621
|
+
self.all_graphql_objects = {}
|
|
622
|
+
self.references: dict[str, Reference] = {}
|
|
623
|
+
|
|
624
|
+
self.support_graphql_types = {
|
|
625
|
+
graphql.type.introspection.TypeKind.SCALAR: [],
|
|
626
|
+
graphql.type.introspection.TypeKind.ENUM: [],
|
|
627
|
+
graphql.type.introspection.TypeKind.UNION: [],
|
|
628
|
+
graphql.type.introspection.TypeKind.INTERFACE: [],
|
|
629
|
+
graphql.type.introspection.TypeKind.OBJECT: [],
|
|
630
|
+
graphql.type.introspection.TypeKind.INPUT_OBJECT: [],
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
# may be as a parameter in the future (??)
|
|
634
|
+
mapper_from_graphql_type_to_parser_method = {
|
|
635
|
+
graphql.type.introspection.TypeKind.SCALAR: self.parse_scalar,
|
|
636
|
+
graphql.type.introspection.TypeKind.ENUM: self.parse_enum,
|
|
637
|
+
graphql.type.introspection.TypeKind.INTERFACE: self.parse_interface,
|
|
638
|
+
graphql.type.introspection.TypeKind.OBJECT: self.parse_object,
|
|
639
|
+
graphql.type.introspection.TypeKind.INPUT_OBJECT: self.parse_input_object,
|
|
640
|
+
graphql.type.introspection.TypeKind.UNION: self.parse_union,
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
for source, path_parts in self._get_context_source_path_parts():
|
|
644
|
+
schema: graphql.GraphQLSchema = build_graphql_schema(source.text)
|
|
645
|
+
self.raw_obj = schema
|
|
646
|
+
|
|
647
|
+
self._resolve_types(path_parts, schema)
|
|
648
|
+
|
|
649
|
+
for next_type in self.parse_order:
|
|
650
|
+
for obj in self.support_graphql_types[next_type]:
|
|
651
|
+
parser_ = mapper_from_graphql_type_to_parser_method[next_type]
|
|
652
|
+
parser_(obj)
|