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
|
@@ -1,49 +1,65 @@
|
|
|
1
|
+
"""OpenAPI and Swagger specification parser.
|
|
2
|
+
|
|
3
|
+
Extends JsonSchemaParser to handle OpenAPI 2.0 (Swagger), 3.0, and 3.1
|
|
4
|
+
specifications, including paths, operations, parameters, and request/response bodies.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
1
9
|
import re
|
|
2
10
|
from collections import defaultdict
|
|
11
|
+
from contextlib import nullcontext
|
|
3
12
|
from enum import Enum
|
|
4
13
|
from pathlib import Path
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
DefaultDict,
|
|
9
|
-
Dict,
|
|
10
|
-
Iterable,
|
|
11
|
-
List,
|
|
12
|
-
Mapping,
|
|
13
|
-
Optional,
|
|
14
|
-
Pattern,
|
|
15
|
-
Sequence,
|
|
16
|
-
Set,
|
|
17
|
-
Tuple,
|
|
18
|
-
Type,
|
|
19
|
-
Union,
|
|
20
|
-
)
|
|
21
|
-
from urllib.parse import ParseResult
|
|
14
|
+
from re import Pattern
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional, TypeVar, Union
|
|
16
|
+
from warnings import warn
|
|
22
17
|
|
|
23
|
-
from pydantic import
|
|
18
|
+
from pydantic import Field
|
|
24
19
|
|
|
25
20
|
from datamodel_code_generator import (
|
|
26
|
-
|
|
21
|
+
DEFAULT_SHARED_MODULE_NAME,
|
|
22
|
+
AllOfMergeMode,
|
|
23
|
+
DataclassArguments,
|
|
24
|
+
Error,
|
|
27
25
|
LiteralType,
|
|
28
26
|
OpenAPIScope,
|
|
29
27
|
PythonVersion,
|
|
30
|
-
|
|
28
|
+
PythonVersionMin,
|
|
29
|
+
ReadOnlyWriteOnlyModelType,
|
|
30
|
+
ReuseScope,
|
|
31
|
+
YamlValue,
|
|
32
|
+
load_yaml_dict,
|
|
31
33
|
snooper_to_methods,
|
|
32
34
|
)
|
|
35
|
+
from datamodel_code_generator.format import DEFAULT_FORMATTERS, DatetimeClassType, Formatter
|
|
33
36
|
from datamodel_code_generator.model import DataModel, DataModelFieldBase
|
|
34
37
|
from datamodel_code_generator.model import pydantic as pydantic_model
|
|
38
|
+
from datamodel_code_generator.parser.base import get_special_path
|
|
35
39
|
from datamodel_code_generator.parser.jsonschema import (
|
|
36
40
|
JsonSchemaObject,
|
|
37
41
|
JsonSchemaParser,
|
|
38
42
|
get_model_by_path,
|
|
39
|
-
get_special_path,
|
|
40
43
|
)
|
|
41
|
-
from datamodel_code_generator.reference import snake_to_upper_camel
|
|
42
|
-
from datamodel_code_generator.types import
|
|
44
|
+
from datamodel_code_generator.reference import FieldNameResolver, is_url, snake_to_upper_camel
|
|
45
|
+
from datamodel_code_generator.types import (
|
|
46
|
+
DataType,
|
|
47
|
+
DataTypeManager,
|
|
48
|
+
EmptyDataType,
|
|
49
|
+
StrictTypes,
|
|
50
|
+
)
|
|
51
|
+
from datamodel_code_generator.util import BaseModel
|
|
43
52
|
|
|
44
|
-
|
|
53
|
+
if TYPE_CHECKING:
|
|
54
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
55
|
+
from urllib.parse import ParseResult
|
|
45
56
|
|
|
46
|
-
|
|
57
|
+
from datamodel_code_generator.parser import DefaultPutDict
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
RE_APPLICATION_JSON_PATTERN: Pattern[str] = re.compile(r"^application/.*json$")
|
|
61
|
+
|
|
62
|
+
OPERATION_NAMES: list[str] = [
|
|
47
63
|
"get",
|
|
48
64
|
"put",
|
|
49
65
|
"post",
|
|
@@ -56,131 +72,209 @@ OPERATION_NAMES: List[str] = [
|
|
|
56
72
|
|
|
57
73
|
|
|
58
74
|
class ParameterLocation(Enum):
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
75
|
+
"""Represent OpenAPI parameter locations."""
|
|
76
|
+
|
|
77
|
+
query = "query"
|
|
78
|
+
header = "header"
|
|
79
|
+
path = "path"
|
|
80
|
+
cookie = "cookie"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
BaseModelT = TypeVar("BaseModelT", bound=BaseModel)
|
|
63
84
|
|
|
64
85
|
|
|
65
86
|
class ReferenceObject(BaseModel):
|
|
66
|
-
|
|
87
|
+
"""Represent an OpenAPI reference object ($ref)."""
|
|
88
|
+
|
|
89
|
+
ref: str = Field(..., alias="$ref")
|
|
67
90
|
|
|
68
91
|
|
|
69
92
|
class ExampleObject(BaseModel):
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
93
|
+
"""Represent an OpenAPI example object."""
|
|
94
|
+
|
|
95
|
+
summary: Optional[str] = None # noqa: UP045
|
|
96
|
+
description: Optional[str] = None # noqa: UP045
|
|
97
|
+
value: YamlValue = None
|
|
98
|
+
externalValue: Optional[str] = None # noqa: N815, UP045
|
|
74
99
|
|
|
75
100
|
|
|
76
101
|
class MediaObject(BaseModel):
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
example:
|
|
81
|
-
examples: Union[str, ReferenceObject, ExampleObject,
|
|
102
|
+
"""Represent an OpenAPI media type object."""
|
|
103
|
+
|
|
104
|
+
schema_: Optional[Union[ReferenceObject, JsonSchemaObject]] = Field(None, alias="schema") # noqa: UP007, UP045
|
|
105
|
+
example: YamlValue = None
|
|
106
|
+
examples: Optional[Union[str, ReferenceObject, ExampleObject]] = None # noqa: UP007, UP045
|
|
82
107
|
|
|
83
108
|
|
|
84
109
|
class ParameterObject(BaseModel):
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
110
|
+
"""Represent an OpenAPI parameter object."""
|
|
111
|
+
|
|
112
|
+
name: Optional[str] = None # noqa: UP045
|
|
113
|
+
in_: Optional[ParameterLocation] = Field(None, alias="in") # noqa: UP045
|
|
114
|
+
description: Optional[str] = None # noqa: UP045
|
|
88
115
|
required: bool = False
|
|
89
116
|
deprecated: bool = False
|
|
90
|
-
schema_: Optional[JsonSchemaObject] = Field(None, alias=
|
|
91
|
-
example:
|
|
92
|
-
examples: Union[str, ReferenceObject, ExampleObject,
|
|
93
|
-
content:
|
|
117
|
+
schema_: Optional[JsonSchemaObject] = Field(None, alias="schema") # noqa: UP045
|
|
118
|
+
example: YamlValue = None
|
|
119
|
+
examples: Optional[Union[str, ReferenceObject, ExampleObject]] = None # noqa: UP007, UP045
|
|
120
|
+
content: dict[str, MediaObject] = {} # noqa: RUF012
|
|
94
121
|
|
|
95
122
|
|
|
96
123
|
class HeaderObject(BaseModel):
|
|
97
|
-
|
|
124
|
+
"""Represent an OpenAPI header object."""
|
|
125
|
+
|
|
126
|
+
description: Optional[str] = None # noqa: UP045
|
|
98
127
|
required: bool = False
|
|
99
128
|
deprecated: bool = False
|
|
100
|
-
schema_: Optional[JsonSchemaObject] = Field(None, alias=
|
|
101
|
-
example:
|
|
102
|
-
examples: Union[str, ReferenceObject, ExampleObject,
|
|
103
|
-
content:
|
|
129
|
+
schema_: Optional[JsonSchemaObject] = Field(None, alias="schema") # noqa: UP045
|
|
130
|
+
example: YamlValue = None
|
|
131
|
+
examples: Optional[Union[str, ReferenceObject, ExampleObject]] = None # noqa: UP007, UP045
|
|
132
|
+
content: dict[str, MediaObject] = {} # noqa: RUF012
|
|
104
133
|
|
|
105
134
|
|
|
106
135
|
class RequestBodyObject(BaseModel):
|
|
107
|
-
|
|
108
|
-
|
|
136
|
+
"""Represent an OpenAPI request body object."""
|
|
137
|
+
|
|
138
|
+
description: Optional[str] = None # noqa: UP045
|
|
139
|
+
content: dict[str, MediaObject] = {} # noqa: RUF012
|
|
109
140
|
required: bool = False
|
|
110
141
|
|
|
111
142
|
|
|
112
143
|
class ResponseObject(BaseModel):
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
144
|
+
"""Represent an OpenAPI response object."""
|
|
145
|
+
|
|
146
|
+
description: Optional[str] = None # noqa: UP045
|
|
147
|
+
headers: dict[str, ParameterObject] = {} # noqa: RUF012
|
|
148
|
+
content: dict[Union[str, int], MediaObject] = {} # noqa: RUF012, UP007
|
|
116
149
|
|
|
117
150
|
|
|
118
151
|
class Operation(BaseModel):
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
152
|
+
"""Represent an OpenAPI operation object."""
|
|
153
|
+
|
|
154
|
+
tags: list[str] = [] # noqa: RUF012
|
|
155
|
+
summary: Optional[str] = None # noqa: UP045
|
|
156
|
+
description: Optional[str] = None # noqa: UP045
|
|
157
|
+
operationId: Optional[str] = None # noqa: N815, UP045
|
|
158
|
+
parameters: list[Union[ReferenceObject, ParameterObject]] = [] # noqa: RUF012, UP007
|
|
159
|
+
requestBody: Optional[Union[ReferenceObject, RequestBodyObject]] = None # noqa: N815, UP007, UP045
|
|
160
|
+
responses: dict[Union[str, int], Union[ReferenceObject, ResponseObject]] = {} # noqa: RUF012, UP007
|
|
126
161
|
deprecated: bool = False
|
|
127
162
|
|
|
128
163
|
|
|
129
164
|
class ComponentsObject(BaseModel):
|
|
130
|
-
|
|
131
|
-
responses: Dict[str, Union[ReferenceObject, ResponseObject]] = {}
|
|
132
|
-
examples: Dict[str, Union[ReferenceObject, ExampleObject]] = {}
|
|
133
|
-
requestBodies: Dict[str, Union[ReferenceObject, RequestBodyObject]] = {}
|
|
134
|
-
headers: Dict[str, Union[ReferenceObject, HeaderObject]] = {}
|
|
165
|
+
"""Represent an OpenAPI components object."""
|
|
135
166
|
|
|
167
|
+
schemas: dict[str, Union[ReferenceObject, JsonSchemaObject]] = {} # noqa: RUF012, UP007
|
|
168
|
+
responses: dict[str, Union[ReferenceObject, ResponseObject]] = {} # noqa: RUF012, UP007
|
|
169
|
+
examples: dict[str, Union[ReferenceObject, ExampleObject]] = {} # noqa: RUF012, UP007
|
|
170
|
+
requestBodies: dict[str, Union[ReferenceObject, RequestBodyObject]] = {} # noqa: N815, RUF012, UP007
|
|
171
|
+
headers: dict[str, Union[ReferenceObject, HeaderObject]] = {} # noqa: RUF012, UP007
|
|
136
172
|
|
|
137
|
-
|
|
173
|
+
|
|
174
|
+
@snooper_to_methods()
|
|
138
175
|
class OpenAPIParser(JsonSchemaParser):
|
|
139
|
-
|
|
176
|
+
"""Parser for OpenAPI 2.0/3.0/3.1 and Swagger specifications."""
|
|
177
|
+
|
|
178
|
+
SCHEMA_PATHS: ClassVar[list[str]] = ["#/components/schemas"]
|
|
179
|
+
|
|
180
|
+
def __init__( # noqa: PLR0913
|
|
140
181
|
self,
|
|
141
|
-
source:
|
|
182
|
+
source: str | Path | list[Path] | ParseResult,
|
|
142
183
|
*,
|
|
143
|
-
data_model_type:
|
|
144
|
-
data_model_root_type:
|
|
145
|
-
data_type_manager_type:
|
|
146
|
-
data_model_field_type:
|
|
147
|
-
base_class:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
184
|
+
data_model_type: type[DataModel] = pydantic_model.BaseModel,
|
|
185
|
+
data_model_root_type: type[DataModel] = pydantic_model.CustomRootType,
|
|
186
|
+
data_type_manager_type: type[DataTypeManager] = pydantic_model.DataTypeManager,
|
|
187
|
+
data_model_field_type: type[DataModelFieldBase] = pydantic_model.DataModelField,
|
|
188
|
+
base_class: str | None = None,
|
|
189
|
+
additional_imports: list[str] | None = None,
|
|
190
|
+
custom_template_dir: Path | None = None,
|
|
191
|
+
extra_template_data: defaultdict[str, dict[str, Any]] | None = None,
|
|
192
|
+
target_python_version: PythonVersion = PythonVersionMin,
|
|
193
|
+
dump_resolve_reference_action: Callable[[Iterable[str]], str] | None = None,
|
|
152
194
|
validation: bool = False,
|
|
153
195
|
field_constraints: bool = False,
|
|
154
196
|
snake_case_field: bool = False,
|
|
155
197
|
strip_default_none: bool = False,
|
|
156
|
-
aliases:
|
|
198
|
+
aliases: Mapping[str, str] | None = None,
|
|
157
199
|
allow_population_by_field_name: bool = False,
|
|
200
|
+
allow_extra_fields: bool = False,
|
|
201
|
+
extra_fields: str | None = None,
|
|
158
202
|
apply_default_values_for_required_fields: bool = False,
|
|
159
203
|
force_optional_for_required_fields: bool = False,
|
|
160
|
-
class_name:
|
|
204
|
+
class_name: str | None = None,
|
|
161
205
|
use_standard_collections: bool = False,
|
|
162
|
-
base_path:
|
|
206
|
+
base_path: Path | None = None,
|
|
163
207
|
use_schema_description: bool = False,
|
|
208
|
+
use_field_description: bool = False,
|
|
209
|
+
use_attribute_docstrings: bool = False,
|
|
210
|
+
use_inline_field_description: bool = False,
|
|
211
|
+
use_default_kwarg: bool = False,
|
|
164
212
|
reuse_model: bool = False,
|
|
165
|
-
|
|
166
|
-
|
|
213
|
+
reuse_scope: ReuseScope | None = None,
|
|
214
|
+
shared_module_name: str = DEFAULT_SHARED_MODULE_NAME,
|
|
215
|
+
encoding: str = "utf-8",
|
|
216
|
+
enum_field_as_literal: LiteralType | None = None,
|
|
217
|
+
use_one_literal_as_default: bool = False,
|
|
218
|
+
use_enum_values_in_discriminator: bool = False,
|
|
167
219
|
set_default_enum_member: bool = False,
|
|
220
|
+
use_subclass_enum: bool = False,
|
|
221
|
+
use_specialized_enum: bool = True,
|
|
168
222
|
strict_nullable: bool = False,
|
|
169
223
|
use_generic_container_types: bool = False,
|
|
170
224
|
enable_faux_immutability: bool = False,
|
|
171
|
-
remote_text_cache:
|
|
225
|
+
remote_text_cache: DefaultPutDict[str, str] | None = None,
|
|
172
226
|
disable_appending_item_suffix: bool = False,
|
|
173
|
-
strict_types:
|
|
174
|
-
empty_enum_field_name:
|
|
175
|
-
custom_class_name_generator:
|
|
176
|
-
field_extra_keys:
|
|
227
|
+
strict_types: Sequence[StrictTypes] | None = None,
|
|
228
|
+
empty_enum_field_name: str | None = None,
|
|
229
|
+
custom_class_name_generator: Callable[[str], str] | None = None,
|
|
230
|
+
field_extra_keys: set[str] | None = None,
|
|
177
231
|
field_include_all_keys: bool = False,
|
|
178
|
-
|
|
179
|
-
|
|
232
|
+
field_extra_keys_without_x_prefix: set[str] | None = None,
|
|
233
|
+
openapi_scopes: list[OpenAPIScope] | None = None,
|
|
234
|
+
include_path_parameters: bool = False,
|
|
235
|
+
wrap_string_literal: bool | None = False,
|
|
180
236
|
use_title_as_name: bool = False,
|
|
181
|
-
|
|
237
|
+
use_operation_id_as_name: bool = False,
|
|
238
|
+
use_unique_items_as_set: bool = False,
|
|
239
|
+
allof_merge_mode: AllOfMergeMode = AllOfMergeMode.Constraints,
|
|
240
|
+
http_headers: Sequence[tuple[str, str]] | None = None,
|
|
241
|
+
http_ignore_tls: bool = False,
|
|
182
242
|
use_annotated: bool = False,
|
|
183
|
-
|
|
243
|
+
use_serialize_as_any: bool = False,
|
|
244
|
+
use_non_positive_negative_number_constrained_types: bool = False,
|
|
245
|
+
use_decimal_for_multiple_of: bool = False,
|
|
246
|
+
original_field_name_delimiter: str | None = None,
|
|
247
|
+
use_double_quotes: bool = False,
|
|
248
|
+
use_union_operator: bool = False,
|
|
249
|
+
allow_responses_without_content: bool = False,
|
|
250
|
+
collapse_root_models: bool = False,
|
|
251
|
+
skip_root_model: bool = False,
|
|
252
|
+
use_type_alias: bool = False,
|
|
253
|
+
special_field_name_prefix: str | None = None,
|
|
254
|
+
remove_special_field_name_prefix: bool = False,
|
|
255
|
+
capitalise_enum_members: bool = False,
|
|
256
|
+
keep_model_order: bool = False,
|
|
257
|
+
known_third_party: list[str] | None = None,
|
|
258
|
+
custom_formatters: list[str] | None = None,
|
|
259
|
+
custom_formatters_kwargs: dict[str, Any] | None = None,
|
|
260
|
+
use_pendulum: bool = False,
|
|
261
|
+
http_query_parameters: Sequence[tuple[str, str]] | None = None,
|
|
262
|
+
treat_dot_as_module: bool = False,
|
|
263
|
+
use_exact_imports: bool = False,
|
|
264
|
+
default_field_extras: dict[str, Any] | None = None,
|
|
265
|
+
target_datetime_class: DatetimeClassType | None = None,
|
|
266
|
+
keyword_only: bool = False,
|
|
267
|
+
frozen_dataclasses: bool = False,
|
|
268
|
+
no_alias: bool = False,
|
|
269
|
+
formatters: list[Formatter] = DEFAULT_FORMATTERS,
|
|
270
|
+
parent_scoped_naming: bool = False,
|
|
271
|
+
dataclass_arguments: DataclassArguments | None = None,
|
|
272
|
+
type_mappings: list[str] | None = None,
|
|
273
|
+
read_only_write_only_model_type: ReadOnlyWriteOnlyModelType | None = None,
|
|
274
|
+
use_frozen_field: bool = False,
|
|
275
|
+
) -> None:
|
|
276
|
+
"""Initialize the OpenAPI parser with extensive configuration options."""
|
|
277
|
+
target_datetime_class = target_datetime_class or DatetimeClassType.Awaredatetime
|
|
184
278
|
super().__init__(
|
|
185
279
|
source=source,
|
|
186
280
|
data_model_type=data_model_type,
|
|
@@ -188,6 +282,7 @@ class OpenAPIParser(JsonSchemaParser):
|
|
|
188
282
|
data_type_manager_type=data_type_manager_type,
|
|
189
283
|
data_model_field_type=data_model_field_type,
|
|
190
284
|
base_class=base_class,
|
|
285
|
+
additional_imports=additional_imports,
|
|
191
286
|
custom_template_dir=custom_template_dir,
|
|
192
287
|
extra_template_data=extra_template_data,
|
|
193
288
|
target_python_version=target_python_version,
|
|
@@ -198,16 +293,28 @@ class OpenAPIParser(JsonSchemaParser):
|
|
|
198
293
|
strip_default_none=strip_default_none,
|
|
199
294
|
aliases=aliases,
|
|
200
295
|
allow_population_by_field_name=allow_population_by_field_name,
|
|
296
|
+
allow_extra_fields=allow_extra_fields,
|
|
297
|
+
extra_fields=extra_fields,
|
|
201
298
|
apply_default_values_for_required_fields=apply_default_values_for_required_fields,
|
|
202
299
|
force_optional_for_required_fields=force_optional_for_required_fields,
|
|
203
300
|
class_name=class_name,
|
|
204
301
|
use_standard_collections=use_standard_collections,
|
|
205
302
|
base_path=base_path,
|
|
206
303
|
use_schema_description=use_schema_description,
|
|
304
|
+
use_field_description=use_field_description,
|
|
305
|
+
use_attribute_docstrings=use_attribute_docstrings,
|
|
306
|
+
use_inline_field_description=use_inline_field_description,
|
|
307
|
+
use_default_kwarg=use_default_kwarg,
|
|
207
308
|
reuse_model=reuse_model,
|
|
309
|
+
reuse_scope=reuse_scope,
|
|
310
|
+
shared_module_name=shared_module_name,
|
|
208
311
|
encoding=encoding,
|
|
209
312
|
enum_field_as_literal=enum_field_as_literal,
|
|
313
|
+
use_one_literal_as_default=use_one_literal_as_default,
|
|
314
|
+
use_enum_values_in_discriminator=use_enum_values_in_discriminator,
|
|
210
315
|
set_default_enum_member=set_default_enum_member,
|
|
316
|
+
use_subclass_enum=use_subclass_enum,
|
|
317
|
+
use_specialized_enum=use_specialized_enum,
|
|
211
318
|
strict_nullable=strict_nullable,
|
|
212
319
|
use_generic_container_types=use_generic_container_types,
|
|
213
320
|
enable_faux_immutability=enable_faux_immutability,
|
|
@@ -218,50 +325,197 @@ class OpenAPIParser(JsonSchemaParser):
|
|
|
218
325
|
custom_class_name_generator=custom_class_name_generator,
|
|
219
326
|
field_extra_keys=field_extra_keys,
|
|
220
327
|
field_include_all_keys=field_include_all_keys,
|
|
328
|
+
field_extra_keys_without_x_prefix=field_extra_keys_without_x_prefix,
|
|
221
329
|
wrap_string_literal=wrap_string_literal,
|
|
222
330
|
use_title_as_name=use_title_as_name,
|
|
331
|
+
use_operation_id_as_name=use_operation_id_as_name,
|
|
332
|
+
use_unique_items_as_set=use_unique_items_as_set,
|
|
333
|
+
allof_merge_mode=allof_merge_mode,
|
|
223
334
|
http_headers=http_headers,
|
|
335
|
+
http_ignore_tls=http_ignore_tls,
|
|
224
336
|
use_annotated=use_annotated,
|
|
337
|
+
use_serialize_as_any=use_serialize_as_any,
|
|
338
|
+
use_non_positive_negative_number_constrained_types=use_non_positive_negative_number_constrained_types,
|
|
339
|
+
use_decimal_for_multiple_of=use_decimal_for_multiple_of,
|
|
340
|
+
original_field_name_delimiter=original_field_name_delimiter,
|
|
341
|
+
use_double_quotes=use_double_quotes,
|
|
342
|
+
use_union_operator=use_union_operator,
|
|
343
|
+
allow_responses_without_content=allow_responses_without_content,
|
|
344
|
+
collapse_root_models=collapse_root_models,
|
|
345
|
+
skip_root_model=skip_root_model,
|
|
346
|
+
use_type_alias=use_type_alias,
|
|
347
|
+
special_field_name_prefix=special_field_name_prefix,
|
|
348
|
+
remove_special_field_name_prefix=remove_special_field_name_prefix,
|
|
349
|
+
capitalise_enum_members=capitalise_enum_members,
|
|
350
|
+
keep_model_order=keep_model_order,
|
|
351
|
+
known_third_party=known_third_party,
|
|
352
|
+
custom_formatters=custom_formatters,
|
|
353
|
+
custom_formatters_kwargs=custom_formatters_kwargs,
|
|
354
|
+
use_pendulum=use_pendulum,
|
|
355
|
+
http_query_parameters=http_query_parameters,
|
|
356
|
+
treat_dot_as_module=treat_dot_as_module,
|
|
357
|
+
use_exact_imports=use_exact_imports,
|
|
358
|
+
default_field_extras=default_field_extras,
|
|
359
|
+
target_datetime_class=target_datetime_class,
|
|
360
|
+
keyword_only=keyword_only,
|
|
361
|
+
frozen_dataclasses=frozen_dataclasses,
|
|
362
|
+
no_alias=no_alias,
|
|
363
|
+
formatters=formatters,
|
|
364
|
+
parent_scoped_naming=parent_scoped_naming,
|
|
365
|
+
dataclass_arguments=dataclass_arguments,
|
|
366
|
+
type_mappings=type_mappings,
|
|
367
|
+
read_only_write_only_model_type=read_only_write_only_model_type,
|
|
368
|
+
use_frozen_field=use_frozen_field,
|
|
225
369
|
)
|
|
226
|
-
self.open_api_scopes:
|
|
227
|
-
|
|
228
|
-
]
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
370
|
+
self.open_api_scopes: list[OpenAPIScope] = openapi_scopes or [OpenAPIScope.Schemas]
|
|
371
|
+
self.include_path_parameters: bool = include_path_parameters
|
|
372
|
+
self._discriminator_schemas: dict[str, dict[str, Any]] = {}
|
|
373
|
+
self._discriminator_subtypes: dict[str, list[str]] = defaultdict(list)
|
|
374
|
+
|
|
375
|
+
def get_ref_model(self, ref: str) -> dict[str, Any]:
|
|
376
|
+
"""Resolve a reference to its model definition."""
|
|
377
|
+
ref_file, ref_path = self.model_resolver.resolve_ref(ref).split("#", 1)
|
|
378
|
+
ref_body = self._get_ref_body(ref_file) if ref_file else self.raw_obj
|
|
379
|
+
return get_model_by_path(ref_body, ref_path.split("/")[1:])
|
|
380
|
+
|
|
381
|
+
def get_data_type(self, obj: JsonSchemaObject) -> DataType:
|
|
382
|
+
"""Get data type from JSON schema object, handling OpenAPI nullable semantics."""
|
|
383
|
+
# OpenAPI 3.0 doesn't allow `null` in the `type` field and list of types
|
|
384
|
+
# https://swagger.io/docs/specification/data-models/data-types/#null
|
|
385
|
+
# OpenAPI 3.1 does allow `null` in the `type` field and is equivalent to
|
|
386
|
+
# a `nullable` flag on the property itself
|
|
387
|
+
if obj.nullable and self.strict_nullable and isinstance(obj.type, str):
|
|
388
|
+
obj.type = [obj.type, "null"]
|
|
389
|
+
|
|
390
|
+
return super().get_data_type(obj)
|
|
391
|
+
|
|
392
|
+
def _get_discriminator_union_type(self, ref: str) -> DataType | None:
|
|
393
|
+
"""Create a union type for discriminator subtypes if available."""
|
|
394
|
+
subtypes = self._discriminator_subtypes.get(ref, [])
|
|
395
|
+
if not subtypes:
|
|
396
|
+
return None
|
|
397
|
+
refs = map(self.model_resolver.add_ref, subtypes)
|
|
398
|
+
return self.data_type(data_types=[self.data_type(reference=r) for r in refs])
|
|
399
|
+
|
|
400
|
+
def get_ref_data_type(self, ref: str) -> DataType:
|
|
401
|
+
"""Get data type for a reference, handling discriminator polymorphism."""
|
|
402
|
+
if ref in self._discriminator_schemas and (union_type := self._get_discriminator_union_type(ref)):
|
|
403
|
+
return union_type
|
|
404
|
+
return super().get_ref_data_type(ref)
|
|
405
|
+
|
|
406
|
+
def parse_object_fields(
|
|
407
|
+
self,
|
|
408
|
+
obj: JsonSchemaObject,
|
|
409
|
+
path: list[str],
|
|
410
|
+
module_name: Optional[str] = None, # noqa: UP045
|
|
411
|
+
class_name: Optional[str] = None, # noqa: UP045
|
|
412
|
+
) -> list[DataModelFieldBase]:
|
|
413
|
+
"""Parse object fields, adding discriminator info for allOf polymorphism."""
|
|
414
|
+
fields = super().parse_object_fields(obj, path, module_name, class_name=class_name)
|
|
415
|
+
properties = obj.properties or {}
|
|
416
|
+
|
|
417
|
+
result_fields: list[DataModelFieldBase] = []
|
|
418
|
+
for field_obj in fields:
|
|
419
|
+
field = properties.get(field_obj.original_name)
|
|
420
|
+
|
|
421
|
+
if (
|
|
422
|
+
isinstance(field, JsonSchemaObject)
|
|
423
|
+
and field.ref
|
|
424
|
+
and (discriminator := self._discriminator_schemas.get(field.ref))
|
|
425
|
+
):
|
|
426
|
+
new_field_type = self._get_discriminator_union_type(field.ref) or field_obj.data_type
|
|
427
|
+
field_obj = self.data_model_field_type(**{ # noqa: PLW2901
|
|
428
|
+
**field_obj.__dict__,
|
|
429
|
+
"data_type": new_field_type,
|
|
430
|
+
"extras": {**field_obj.extras, "discriminator": discriminator},
|
|
431
|
+
})
|
|
432
|
+
result_fields.append(field_obj)
|
|
433
|
+
|
|
434
|
+
return result_fields
|
|
435
|
+
|
|
436
|
+
def resolve_object(self, obj: ReferenceObject | BaseModelT, object_type: type[BaseModelT]) -> BaseModelT:
|
|
437
|
+
"""Resolve a reference object to its actual type or return the object as-is."""
|
|
438
|
+
if isinstance(obj, ReferenceObject):
|
|
439
|
+
ref_obj = self.get_ref_model(obj.ref)
|
|
440
|
+
return object_type.parse_obj(ref_obj)
|
|
441
|
+
return obj
|
|
442
|
+
|
|
443
|
+
def _parse_schema_or_ref(
|
|
444
|
+
self,
|
|
445
|
+
name: str,
|
|
446
|
+
schema: JsonSchemaObject | ReferenceObject | None,
|
|
447
|
+
path: list[str],
|
|
448
|
+
) -> DataType | None:
|
|
449
|
+
"""Parse a schema object or resolve a reference to get DataType."""
|
|
450
|
+
if schema is None:
|
|
451
|
+
return None
|
|
452
|
+
if isinstance(schema, JsonSchemaObject):
|
|
453
|
+
return self.parse_schema(name, schema, path)
|
|
454
|
+
self.resolve_ref(schema.ref)
|
|
455
|
+
return self.get_ref_data_type(schema.ref)
|
|
456
|
+
|
|
457
|
+
def _process_path_items( # noqa: PLR0913
|
|
458
|
+
self,
|
|
459
|
+
items: dict[str, dict[str, Any]],
|
|
460
|
+
base_path: list[str],
|
|
461
|
+
scope_name: str,
|
|
462
|
+
global_parameters: list[dict[str, Any]],
|
|
463
|
+
security: list[dict[str, list[str]]] | None,
|
|
464
|
+
*,
|
|
465
|
+
strip_leading_slash: bool = True,
|
|
466
|
+
) -> None:
|
|
467
|
+
"""Process path or webhook items with operations."""
|
|
468
|
+
scope_path = [*base_path, f"#/{scope_name}"]
|
|
469
|
+
for item_name, methods_ in items.items():
|
|
470
|
+
item_ref = methods_.get("$ref")
|
|
471
|
+
if item_ref:
|
|
472
|
+
methods = self.get_ref_model(item_ref)
|
|
473
|
+
# Extract base path from reference for external file resolution
|
|
474
|
+
resolved_ref = self.model_resolver.resolve_ref(item_ref)
|
|
475
|
+
ref_file = resolved_ref.split("#")[0] if "#" in resolved_ref else resolved_ref
|
|
476
|
+
ref_base_path = Path(ref_file).parent if ref_file and not is_url(ref_file) else None
|
|
477
|
+
else:
|
|
478
|
+
methods = methods_
|
|
479
|
+
ref_base_path = None
|
|
480
|
+
|
|
481
|
+
item_parameters = global_parameters.copy()
|
|
482
|
+
if "parameters" in methods:
|
|
483
|
+
item_parameters.extend(methods["parameters"])
|
|
484
|
+
|
|
485
|
+
relative_name = item_name[1:] if strip_leading_slash else item_name.removeprefix("/")
|
|
486
|
+
path = [*scope_path, relative_name] if relative_name else get_special_path("root", scope_path)
|
|
487
|
+
|
|
488
|
+
base_path_context = (
|
|
489
|
+
self.model_resolver.current_base_path_context(ref_base_path) if ref_base_path else nullcontext()
|
|
490
|
+
)
|
|
491
|
+
with base_path_context:
|
|
492
|
+
for operation_name, raw_operation in methods.items():
|
|
493
|
+
if operation_name not in OPERATION_NAMES:
|
|
494
|
+
continue
|
|
495
|
+
if item_parameters:
|
|
496
|
+
if "parameters" in raw_operation:
|
|
497
|
+
raw_operation["parameters"].extend(item_parameters)
|
|
498
|
+
else:
|
|
499
|
+
raw_operation["parameters"] = item_parameters.copy()
|
|
500
|
+
if security is not None and "security" not in raw_operation:
|
|
501
|
+
raw_operation["security"] = security
|
|
502
|
+
self.parse_operation(raw_operation, [*path, operation_name])
|
|
249
503
|
|
|
250
504
|
def parse_schema(
|
|
251
505
|
self,
|
|
252
506
|
name: str,
|
|
253
507
|
obj: JsonSchemaObject,
|
|
254
|
-
path:
|
|
508
|
+
path: list[str],
|
|
255
509
|
) -> DataType:
|
|
510
|
+
"""Parse a JSON schema object into a data type."""
|
|
256
511
|
if obj.is_array:
|
|
257
|
-
data_type
|
|
258
|
-
name, obj, [*path, name], False
|
|
259
|
-
).data_type
|
|
260
|
-
# TODO: The List model is not created by this method. Some scenarios may necessitate it.
|
|
512
|
+
data_type = self.parse_array(name, obj, [*path, name])
|
|
261
513
|
elif obj.allOf: # pragma: no cover
|
|
262
514
|
data_type = self.parse_all_of(name, obj, path)
|
|
263
|
-
elif obj.oneOf: # pragma: no cover
|
|
515
|
+
elif obj.oneOf or obj.anyOf: # pragma: no cover
|
|
264
516
|
data_type = self.parse_root_type(name, obj, path)
|
|
517
|
+
if isinstance(data_type, EmptyDataType) and obj.properties:
|
|
518
|
+
self.parse_object(name, obj, path)
|
|
265
519
|
elif obj.is_object:
|
|
266
520
|
data_type = self.parse_object(name, obj, path)
|
|
267
521
|
elif obj.enum: # pragma: no cover
|
|
@@ -277,66 +531,197 @@ class OpenAPIParser(JsonSchemaParser):
|
|
|
277
531
|
self,
|
|
278
532
|
name: str,
|
|
279
533
|
request_body: RequestBodyObject,
|
|
280
|
-
path:
|
|
281
|
-
) ->
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if
|
|
287
|
-
|
|
534
|
+
path: list[str],
|
|
535
|
+
) -> dict[str, DataType]:
|
|
536
|
+
"""Parse request body content into data types by media type."""
|
|
537
|
+
data_types: dict[str, DataType] = {}
|
|
538
|
+
for media_type, media_obj in request_body.content.items():
|
|
539
|
+
data_type = self._parse_schema_or_ref(name, media_obj.schema_, [*path, media_type])
|
|
540
|
+
if data_type:
|
|
541
|
+
data_types[media_type] = data_type
|
|
542
|
+
return data_types
|
|
288
543
|
|
|
289
544
|
def parse_responses(
|
|
290
545
|
self,
|
|
291
546
|
name: str,
|
|
292
|
-
responses:
|
|
293
|
-
path:
|
|
294
|
-
) ->
|
|
295
|
-
|
|
547
|
+
responses: dict[str | int, ReferenceObject | ResponseObject],
|
|
548
|
+
path: list[str],
|
|
549
|
+
) -> dict[str | int, dict[str, DataType]]:
|
|
550
|
+
"""Parse response objects into data types by status code and content type."""
|
|
551
|
+
data_types: defaultdict[str | int, dict[str, DataType]] = defaultdict(dict)
|
|
296
552
|
for status_code, detail in responses.items():
|
|
297
553
|
if isinstance(detail, ReferenceObject):
|
|
298
554
|
if not detail.ref: # pragma: no cover
|
|
299
555
|
continue
|
|
300
556
|
ref_model = self.get_ref_model(detail.ref)
|
|
301
|
-
content = {
|
|
302
|
-
k: MediaObject.parse_obj(v)
|
|
303
|
-
for k, v in ref_model.get("content", {}).items()
|
|
304
|
-
}
|
|
557
|
+
content = {k: MediaObject.parse_obj(v) for k, v in ref_model.get("content", {}).items()}
|
|
305
558
|
else:
|
|
306
559
|
content = detail.content
|
|
560
|
+
|
|
561
|
+
if self.allow_responses_without_content and not content:
|
|
562
|
+
data_types[status_code]["application/json"] = DataType(type="None")
|
|
563
|
+
|
|
307
564
|
for content_type, obj in content.items():
|
|
565
|
+
response_path: list[str] = [*path, str(status_code), str(content_type)]
|
|
566
|
+
data_type = self._parse_schema_or_ref(name, obj.schema_, response_path)
|
|
567
|
+
if data_type:
|
|
568
|
+
data_types[status_code][content_type] = data_type # pyright: ignore[reportArgumentType]
|
|
308
569
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
570
|
+
return data_types
|
|
571
|
+
|
|
572
|
+
@classmethod
|
|
573
|
+
def parse_tags(
|
|
574
|
+
cls,
|
|
575
|
+
name: str, # noqa: ARG003
|
|
576
|
+
tags: list[str],
|
|
577
|
+
path: list[str], # noqa: ARG003
|
|
578
|
+
) -> list[str]:
|
|
579
|
+
"""Parse operation tags."""
|
|
580
|
+
return tags
|
|
581
|
+
|
|
582
|
+
_field_name_resolver: FieldNameResolver = FieldNameResolver()
|
|
583
|
+
|
|
584
|
+
@classmethod
|
|
585
|
+
def _get_model_name(cls, path_name: str, method: str, suffix: str) -> str:
|
|
586
|
+
normalized = cls._field_name_resolver.get_valid_name(path_name, ignore_snake_case_field=True)
|
|
587
|
+
camel_path_name = snake_to_upper_camel(normalized)
|
|
588
|
+
return f"{camel_path_name}{method.capitalize()}{suffix}"
|
|
589
|
+
|
|
590
|
+
def parse_all_parameters(
|
|
591
|
+
self,
|
|
592
|
+
name: str,
|
|
593
|
+
parameters: list[ReferenceObject | ParameterObject],
|
|
594
|
+
path: list[str],
|
|
595
|
+
) -> DataType | None:
|
|
596
|
+
"""Parse all operation parameters into a data model."""
|
|
597
|
+
fields: list[DataModelFieldBase] = []
|
|
598
|
+
exclude_field_names: set[str] = set()
|
|
599
|
+
reference = self.model_resolver.add(path, name, class_name=True, unique=True)
|
|
600
|
+
for parameter_ in parameters:
|
|
601
|
+
parameter = self.resolve_object(parameter_, ParameterObject)
|
|
602
|
+
parameter_name = parameter.name
|
|
603
|
+
if (
|
|
604
|
+
not parameter_name
|
|
605
|
+
or parameter.in_ not in {ParameterLocation.query, ParameterLocation.path}
|
|
606
|
+
or (parameter.in_ == ParameterLocation.path and not self.include_path_parameters)
|
|
607
|
+
):
|
|
608
|
+
continue
|
|
609
|
+
|
|
610
|
+
if any(field.original_name == parameter_name for field in fields):
|
|
611
|
+
msg = f"Parameter name '{parameter_name}' is used more than once."
|
|
612
|
+
raise Exception(msg) # noqa: TRY002
|
|
613
|
+
|
|
614
|
+
field_name, alias = self.model_resolver.get_valid_field_name_and_alias(
|
|
615
|
+
field_name=parameter_name,
|
|
616
|
+
excludes=exclude_field_names,
|
|
617
|
+
model_type=self.field_name_model_type,
|
|
618
|
+
class_name=name,
|
|
619
|
+
)
|
|
620
|
+
if parameter.schema_:
|
|
621
|
+
fields.append(
|
|
622
|
+
self.get_object_field(
|
|
623
|
+
field_name=field_name,
|
|
624
|
+
field=parameter.schema_,
|
|
625
|
+
field_type=self.parse_item(field_name, parameter.schema_, [*path, name, parameter_name]),
|
|
626
|
+
original_field_name=parameter_name,
|
|
627
|
+
required=parameter.required,
|
|
628
|
+
alias=alias,
|
|
629
|
+
)
|
|
630
|
+
)
|
|
631
|
+
else:
|
|
632
|
+
data_types: list[DataType] = []
|
|
633
|
+
object_schema: JsonSchemaObject | None = None
|
|
634
|
+
for (
|
|
635
|
+
media_type,
|
|
636
|
+
media_obj,
|
|
637
|
+
) in parameter.content.items():
|
|
638
|
+
if not media_obj.schema_:
|
|
639
|
+
continue
|
|
640
|
+
object_schema = self.resolve_object(media_obj.schema_, JsonSchemaObject)
|
|
641
|
+
data_types.append(
|
|
642
|
+
self.parse_item(
|
|
643
|
+
field_name,
|
|
644
|
+
object_schema,
|
|
645
|
+
[*path, name, parameter_name, media_type],
|
|
646
|
+
)
|
|
315
647
|
)
|
|
648
|
+
|
|
649
|
+
if not data_types:
|
|
650
|
+
continue
|
|
651
|
+
if len(data_types) == 1:
|
|
652
|
+
data_type = data_types[0]
|
|
316
653
|
else:
|
|
317
|
-
|
|
318
|
-
|
|
654
|
+
data_type = self.data_type(data_types=data_types)
|
|
655
|
+
# multiple data_type parse as non-constraints field
|
|
656
|
+
object_schema = None
|
|
657
|
+
fields.append(
|
|
658
|
+
self.data_model_field_type(
|
|
659
|
+
name=field_name,
|
|
660
|
+
default=object_schema.default if object_schema else None,
|
|
661
|
+
data_type=data_type,
|
|
662
|
+
required=parameter.required,
|
|
663
|
+
alias=alias,
|
|
664
|
+
constraints=object_schema.dict()
|
|
665
|
+
if object_schema and self.is_constraints_field(object_schema)
|
|
666
|
+
else None,
|
|
667
|
+
nullable=object_schema.nullable
|
|
668
|
+
if object_schema and self.strict_nullable and (object_schema.has_default or parameter.required)
|
|
669
|
+
else None,
|
|
670
|
+
strip_default_none=self.strip_default_none,
|
|
671
|
+
extras=self.get_field_extras(object_schema) if object_schema else {},
|
|
672
|
+
use_annotated=self.use_annotated,
|
|
673
|
+
use_serialize_as_any=self.use_serialize_as_any,
|
|
674
|
+
use_field_description=self.use_field_description,
|
|
675
|
+
use_inline_field_description=self.use_inline_field_description,
|
|
676
|
+
use_default_kwarg=self.use_default_kwarg,
|
|
677
|
+
original_name=parameter_name,
|
|
678
|
+
has_default=object_schema.has_default if object_schema else False,
|
|
679
|
+
type_has_null=object_schema.type_has_null if object_schema else None,
|
|
319
680
|
)
|
|
681
|
+
)
|
|
320
682
|
|
|
321
|
-
|
|
683
|
+
if OpenAPIScope.Parameters in self.open_api_scopes and fields:
|
|
684
|
+
# Using _create_data_model from parent class JsonSchemaParser
|
|
685
|
+
# This method automatically adds frozen=True for DataClass types
|
|
686
|
+
self.results.append(
|
|
687
|
+
self._create_data_model(
|
|
688
|
+
fields=fields,
|
|
689
|
+
reference=reference,
|
|
690
|
+
custom_base_class=self.base_class,
|
|
691
|
+
custom_template_dir=self.custom_template_dir,
|
|
692
|
+
keyword_only=self.keyword_only,
|
|
693
|
+
treat_dot_as_module=self.treat_dot_as_module,
|
|
694
|
+
dataclass_arguments=self.dataclass_arguments,
|
|
695
|
+
)
|
|
696
|
+
)
|
|
697
|
+
return self.data_type(reference=reference)
|
|
322
698
|
|
|
323
|
-
|
|
324
|
-
def _get_model_name(cls, path_name: str, method: str, suffix: str) -> str:
|
|
325
|
-
camel_path_name = snake_to_upper_camel(path_name.replace('/', '_'))
|
|
326
|
-
return f'{camel_path_name}{method.capitalize()}{suffix}'
|
|
699
|
+
return None
|
|
327
700
|
|
|
328
701
|
def parse_operation(
|
|
329
702
|
self,
|
|
330
|
-
raw_operation:
|
|
331
|
-
path:
|
|
703
|
+
raw_operation: dict[str, Any],
|
|
704
|
+
path: list[str],
|
|
332
705
|
) -> None:
|
|
706
|
+
"""Parse an OpenAPI operation including parameters, request body, and responses."""
|
|
333
707
|
operation = Operation.parse_obj(raw_operation)
|
|
334
|
-
for parameters in operation.parameters:
|
|
335
|
-
if isinstance(parameters, ReferenceObject):
|
|
336
|
-
ref_parameter = self.get_ref_model(parameters.ref)
|
|
337
|
-
parameters = ParameterObject.parse_obj(ref_parameter)
|
|
338
|
-
self.parse_parameters(parameters=parameters, path=[*path, 'parameters'])
|
|
339
708
|
path_name, method = path[-2:]
|
|
709
|
+
if self.use_operation_id_as_name:
|
|
710
|
+
if not operation.operationId:
|
|
711
|
+
msg = (
|
|
712
|
+
f"All operations must have an operationId when --use_operation_id_as_name is set."
|
|
713
|
+
f"The following path was missing an operationId: {path_name}"
|
|
714
|
+
)
|
|
715
|
+
raise Error(msg)
|
|
716
|
+
path_name = operation.operationId
|
|
717
|
+
method = ""
|
|
718
|
+
self.parse_all_parameters(
|
|
719
|
+
self._get_model_name(
|
|
720
|
+
path_name, method, suffix="Parameters" if self.include_path_parameters else "ParametersQuery"
|
|
721
|
+
),
|
|
722
|
+
operation.parameters,
|
|
723
|
+
[*path, "parameters"],
|
|
724
|
+
)
|
|
340
725
|
if operation.requestBody:
|
|
341
726
|
if isinstance(operation.requestBody, ReferenceObject):
|
|
342
727
|
ref_model = self.get_ref_model(operation.requestBody.ref)
|
|
@@ -344,78 +729,102 @@ class OpenAPIParser(JsonSchemaParser):
|
|
|
344
729
|
else:
|
|
345
730
|
request_body = operation.requestBody
|
|
346
731
|
self.parse_request_body(
|
|
347
|
-
name=self._get_model_name(path_name, method, suffix=
|
|
732
|
+
name=self._get_model_name(path_name, method, suffix="Request"),
|
|
348
733
|
request_body=request_body,
|
|
349
|
-
path=[*path,
|
|
734
|
+
path=[*path, "requestBody"],
|
|
350
735
|
)
|
|
351
736
|
self.parse_responses(
|
|
352
|
-
name=self._get_model_name(path_name, method, suffix=
|
|
737
|
+
name=self._get_model_name(path_name, method, suffix="Response"),
|
|
353
738
|
responses=operation.responses,
|
|
354
|
-
path=[*path,
|
|
739
|
+
path=[*path, "responses"],
|
|
355
740
|
)
|
|
741
|
+
if OpenAPIScope.Tags in self.open_api_scopes:
|
|
742
|
+
self.parse_tags(
|
|
743
|
+
name=self._get_model_name(path_name, method, suffix="Tags"),
|
|
744
|
+
tags=operation.tags,
|
|
745
|
+
path=[*path, "tags"],
|
|
746
|
+
)
|
|
356
747
|
|
|
357
748
|
def parse_raw(self) -> None:
|
|
358
|
-
|
|
749
|
+
"""Parse OpenAPI specification including schemas, paths, and operations."""
|
|
750
|
+
for source, path_parts in self._get_context_source_path_parts():
|
|
359
751
|
if self.validation:
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
backend='openapi-spec-validator',
|
|
365
|
-
encoding=self.encoding,
|
|
752
|
+
warn(
|
|
753
|
+
"Deprecated: `--validation` option is deprecated. the option will be removed in a future "
|
|
754
|
+
"release. please use another tool to validate OpenAPI.\n",
|
|
755
|
+
stacklevel=2,
|
|
366
756
|
)
|
|
367
|
-
|
|
757
|
+
|
|
758
|
+
try:
|
|
759
|
+
from prance import BaseParser # noqa: PLC0415
|
|
760
|
+
|
|
761
|
+
BaseParser(
|
|
762
|
+
spec_string=source.text,
|
|
763
|
+
backend="openapi-spec-validator",
|
|
764
|
+
encoding=self.encoding,
|
|
765
|
+
)
|
|
766
|
+
except ImportError: # pragma: no cover
|
|
767
|
+
warn(
|
|
768
|
+
"Warning: Validation was skipped for OpenAPI. `prance` or `openapi-spec-validator` are not "
|
|
769
|
+
"installed.\n"
|
|
770
|
+
"To use --validation option after datamodel-code-generator 0.24.0, Please run `$pip install "
|
|
771
|
+
"'datamodel-code-generator[validation]'`.\n",
|
|
772
|
+
stacklevel=2,
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
specification: dict[str, Any] = load_yaml_dict(source.text)
|
|
368
776
|
self.raw_obj = specification
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
)
|
|
372
|
-
security:
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
777
|
+
self._collect_discriminator_schemas()
|
|
778
|
+
schemas: dict[str, Any] = specification.get("components", {}).get("schemas", {})
|
|
779
|
+
paths: dict[str, Any] = specification.get("paths", {})
|
|
780
|
+
security: list[dict[str, list[str]]] | None = specification.get("security")
|
|
781
|
+
# Warn if schemas is empty but paths exist and only Schemas scope is used
|
|
782
|
+
if not schemas and self.open_api_scopes == [OpenAPIScope.Schemas] and paths:
|
|
783
|
+
warn(
|
|
784
|
+
"No schemas found in components/schemas. If your schemas are defined in "
|
|
785
|
+
"external files referenced from paths, consider using --openapi-scopes paths",
|
|
786
|
+
stacklevel=2,
|
|
787
|
+
)
|
|
788
|
+
if OpenAPIScope.Schemas in self.open_api_scopes:
|
|
789
|
+
for obj_name, raw_obj in schemas.items():
|
|
790
|
+
self.parse_raw_obj(
|
|
382
791
|
obj_name,
|
|
383
792
|
raw_obj,
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
793
|
+
[*path_parts, "#/components", "schemas", obj_name],
|
|
794
|
+
)
|
|
795
|
+
if OpenAPIScope.Paths in self.open_api_scopes:
|
|
796
|
+
# Resolve $ref in global parameter list
|
|
797
|
+
global_parameters = [
|
|
798
|
+
self._get_ref_body(p["$ref"]) if isinstance(p, dict) and "$ref" in p else p
|
|
799
|
+
for p in paths.get("parameters", [])
|
|
800
|
+
if isinstance(p, dict)
|
|
801
|
+
]
|
|
802
|
+
self._process_path_items(paths, path_parts, "paths", global_parameters, security)
|
|
803
|
+
|
|
804
|
+
if OpenAPIScope.Webhooks in self.open_api_scopes:
|
|
805
|
+
webhooks: dict[str, dict[str, Any]] = specification.get("webhooks", {})
|
|
806
|
+
self._process_path_items(webhooks, path_parts, "webhooks", [], security, strip_leading_slash=False)
|
|
807
|
+
|
|
808
|
+
self._resolve_unparsed_json_pointer()
|
|
809
|
+
|
|
810
|
+
def _collect_discriminator_schemas(self) -> None:
|
|
811
|
+
"""Collect schemas with discriminators but no oneOf/anyOf, and find their subtypes."""
|
|
812
|
+
schemas: dict[str, Any] = self.raw_obj.get("components", {}).get("schemas", {})
|
|
813
|
+
|
|
814
|
+
for schema_name, schema in schemas.items():
|
|
815
|
+
discriminator = schema.get("discriminator")
|
|
816
|
+
if not discriminator:
|
|
817
|
+
continue
|
|
818
|
+
|
|
819
|
+
if schema.get("oneOf") or schema.get("anyOf"):
|
|
820
|
+
continue
|
|
821
|
+
|
|
822
|
+
ref = f"#/components/schemas/{schema_name}"
|
|
823
|
+
self._discriminator_schemas[ref] = discriminator
|
|
824
|
+
|
|
825
|
+
for schema_name, schema in schemas.items():
|
|
826
|
+
for all_of_item in schema.get("allOf", []):
|
|
827
|
+
ref_in_allof = all_of_item.get("$ref")
|
|
828
|
+
if ref_in_allof and ref_in_allof in self._discriminator_schemas:
|
|
829
|
+
subtype_ref = f"#/components/schemas/{schema_name}"
|
|
830
|
+
self._discriminator_subtypes[ref_in_allof].append(subtype_ref)
|