@typespec/http-client-python 0.33.0 → 0.34.0
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.
- package/dist/emitter/types.d.ts.map +1 -1
- package/dist/emitter/types.js +2 -0
- package/dist/emitter/types.js.map +1 -1
- package/emitter/src/types.ts +2 -0
- package/emitter/temp/tsconfig.tsbuildinfo +1 -1
- package/eng/scripts/ci/regenerate-common.ts +40 -15
- package/eng/scripts/setup/__pycache__/package_manager.cpython-311.pyc +0 -0
- package/eng/scripts/setup/__pycache__/venvtools.cpython-311.pyc +0 -0
- package/generator/build/lib/pygen/__init__.py +3 -3
- package/generator/build/lib/pygen/codegen/models/__init__.py +4 -2
- package/generator/build/lib/pygen/codegen/models/code_model.py +27 -5
- package/generator/build/lib/pygen/codegen/models/combined_type.py +3 -3
- package/generator/build/lib/pygen/codegen/models/enum_type.py +75 -25
- package/generator/build/lib/pygen/codegen/models/list_type.py +10 -4
- package/generator/build/lib/pygen/codegen/models/model_type.py +145 -4
- package/generator/build/lib/pygen/codegen/models/operation.py +1 -0
- package/generator/build/lib/pygen/codegen/models/paging_operation.py +5 -1
- package/generator/build/lib/pygen/codegen/models/parameter.py +2 -2
- package/generator/build/lib/pygen/codegen/models/primitive_types.py +30 -6
- package/generator/build/lib/pygen/codegen/models/property.py +21 -8
- package/generator/build/lib/pygen/codegen/models/response.py +16 -3
- package/generator/build/lib/pygen/codegen/models/utils.py +3 -6
- package/generator/build/lib/pygen/codegen/serializers/__init__.py +46 -11
- package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +48 -15
- package/generator/build/lib/pygen/codegen/serializers/model_init_serializer.py +1 -1
- package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +127 -3
- package/generator/build/lib/pygen/codegen/serializers/types_serializer.py +263 -12
- package/generator/build/lib/pygen/codegen/serializers/unions_serializer.py +44 -0
- package/generator/build/lib/pygen/codegen/templates/model_container.py.jinja2 +1 -1
- package/generator/build/lib/pygen/codegen/templates/types.py.jinja2 +51 -2
- package/generator/build/lib/pygen/codegen/templates/unions.py.jinja2 +9 -0
- package/generator/build/lib/pygen/preprocess/__init__.py +137 -162
- package/generator/build/lib/pygen/utils.py +6 -24
- package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
- package/generator/pygen/__init__.py +3 -3
- package/generator/pygen/codegen/models/__init__.py +4 -2
- package/generator/pygen/codegen/models/code_model.py +27 -5
- package/generator/pygen/codegen/models/combined_type.py +3 -3
- package/generator/pygen/codegen/models/enum_type.py +75 -25
- package/generator/pygen/codegen/models/list_type.py +10 -4
- package/generator/pygen/codegen/models/model_type.py +145 -4
- package/generator/pygen/codegen/models/operation.py +1 -0
- package/generator/pygen/codegen/models/paging_operation.py +5 -1
- package/generator/pygen/codegen/models/parameter.py +2 -2
- package/generator/pygen/codegen/models/primitive_types.py +30 -6
- package/generator/pygen/codegen/models/property.py +21 -8
- package/generator/pygen/codegen/models/response.py +16 -3
- package/generator/pygen/codegen/models/utils.py +3 -6
- package/generator/pygen/codegen/serializers/__init__.py +46 -11
- package/generator/pygen/codegen/serializers/builder_serializer.py +48 -15
- package/generator/pygen/codegen/serializers/model_init_serializer.py +1 -1
- package/generator/pygen/codegen/serializers/model_serializer.py +127 -3
- package/generator/pygen/codegen/serializers/types_serializer.py +263 -12
- package/generator/pygen/codegen/serializers/unions_serializer.py +44 -0
- package/generator/pygen/codegen/templates/model_container.py.jinja2 +1 -1
- package/generator/pygen/codegen/templates/types.py.jinja2 +51 -2
- package/generator/pygen/codegen/templates/unions.py.jinja2 +9 -0
- package/generator/pygen/preprocess/__init__.py +137 -162
- package/generator/pygen/utils.py +6 -24
- package/generator/pygen.egg-info/SOURCES.txt +2 -0
- package/package.json +5 -5
|
@@ -10,7 +10,7 @@ from .constant_type import ConstantType
|
|
|
10
10
|
from .enum_type import EnumType
|
|
11
11
|
from .base import BaseType
|
|
12
12
|
from .imports import FileImport, ImportType
|
|
13
|
-
from .utils import add_to_description, add_to_pylint_disable
|
|
13
|
+
from .utils import add_to_description, add_to_pylint_disable, NamespaceType
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
16
|
from .code_model import CodeModel
|
|
@@ -29,6 +29,7 @@ class Property(BaseModel): # pylint: disable=too-many-instance-attributes
|
|
|
29
29
|
self.client_name: str = self.yaml_data["clientName"]
|
|
30
30
|
self.type = type
|
|
31
31
|
self.optional: bool = self.yaml_data["optional"]
|
|
32
|
+
self.nullable: bool = self.yaml_data.get("nullable", False)
|
|
32
33
|
self.readonly: bool = self.yaml_data.get("readonly", False)
|
|
33
34
|
self.visibility: list[str] = self.yaml_data.get("visibility", [])
|
|
34
35
|
self.is_polymorphic: bool = self.yaml_data.get("isPolymorphic", False)
|
|
@@ -110,6 +111,12 @@ class Property(BaseModel): # pylint: disable=too-many-instance-attributes
|
|
|
110
111
|
if self.is_base_discriminator:
|
|
111
112
|
return "str"
|
|
112
113
|
types_type_annotation = self.type.type_annotation(is_operation_file=is_operation_file, **kwargs)
|
|
114
|
+
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
115
|
+
# In TypedDict types.py, Optional means nullable (not "not required" — that's handled by Required/total=False)
|
|
116
|
+
if serialize_namespace_type == NamespaceType.TYPES_FILE:
|
|
117
|
+
if self.nullable:
|
|
118
|
+
return f"Optional[{types_type_annotation}]"
|
|
119
|
+
return types_type_annotation
|
|
113
120
|
if (self.optional and self.client_default_value is None) or self.readonly:
|
|
114
121
|
return f"Optional[{types_type_annotation}]"
|
|
115
122
|
return types_type_annotation
|
|
@@ -152,15 +159,21 @@ class Property(BaseModel): # pylint: disable=too-many-instance-attributes
|
|
|
152
159
|
if self.is_discriminator and isinstance(self.type, EnumType):
|
|
153
160
|
return file_import
|
|
154
161
|
file_import.merge(self.type.imports(**kwargs))
|
|
155
|
-
|
|
162
|
+
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
163
|
+
if serialize_namespace_type == NamespaceType.TYPES_FILE:
|
|
164
|
+
# In TypedDict types.py, Optional means nullable
|
|
165
|
+
if self.nullable:
|
|
166
|
+
file_import.add_submodule_import("typing", "Optional", ImportType.STDLIB)
|
|
167
|
+
elif (self.optional and self.client_default_value is None) or self.readonly:
|
|
156
168
|
file_import.add_submodule_import("typing", "Optional", ImportType.STDLIB)
|
|
157
169
|
if self.code_model.options["models-mode"] == "dpg":
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
170
|
+
if serialize_namespace_type != NamespaceType.TYPES_FILE:
|
|
171
|
+
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
172
|
+
file_import.add_submodule_import(
|
|
173
|
+
self.code_model.get_relative_import_path(serialize_namespace, module_name="_utils.model_base"),
|
|
174
|
+
"rest_discriminator" if self.is_discriminator else "rest_field",
|
|
175
|
+
ImportType.LOCAL,
|
|
176
|
+
)
|
|
164
177
|
return file_import
|
|
165
178
|
|
|
166
179
|
@classmethod
|
|
@@ -68,7 +68,12 @@ class Response(BaseModel):
|
|
|
68
68
|
|
|
69
69
|
def get_polymorphic_subtypes(self, polymorphic_subtypes: list["ModelType"]) -> None:
|
|
70
70
|
if self.type:
|
|
71
|
-
self.type
|
|
71
|
+
if isinstance(self.type, CombinedType):
|
|
72
|
+
target = self.type.target_model_subtype((ModelType,))
|
|
73
|
+
if target:
|
|
74
|
+
target.get_polymorphic_subtypes(polymorphic_subtypes)
|
|
75
|
+
else:
|
|
76
|
+
self.type.get_polymorphic_subtypes(polymorphic_subtypes)
|
|
72
77
|
|
|
73
78
|
def get_json_template_representation(self) -> Any:
|
|
74
79
|
if not self.type:
|
|
@@ -95,6 +100,7 @@ class Response(BaseModel):
|
|
|
95
100
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
96
101
|
if self.type:
|
|
97
102
|
kwargs["is_operation_file"] = True
|
|
103
|
+
kwargs["is_response"] = True
|
|
98
104
|
type_annotation = self.type.type_annotation(**kwargs)
|
|
99
105
|
if self.nullable:
|
|
100
106
|
return f"Optional[{type_annotation}]"
|
|
@@ -102,11 +108,13 @@ class Response(BaseModel):
|
|
|
102
108
|
return "None"
|
|
103
109
|
|
|
104
110
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
111
|
+
kwargs["is_response"] = True
|
|
105
112
|
if self.nullable and self.type:
|
|
106
113
|
return f"{self.type.docstring_text(**kwargs)} or None"
|
|
107
114
|
return self.type.docstring_text(**kwargs) if self.type else "None"
|
|
108
115
|
|
|
109
116
|
def docstring_type(self, **kwargs: Any) -> str:
|
|
117
|
+
kwargs["is_response"] = True
|
|
110
118
|
if self.nullable and self.type:
|
|
111
119
|
return f"{self.type.docstring_type(**kwargs)} or None"
|
|
112
120
|
return self.type.docstring_type(**kwargs) if self.type else "None"
|
|
@@ -121,7 +129,7 @@ class Response(BaseModel):
|
|
|
121
129
|
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
122
130
|
file_import.add_submodule_import(
|
|
123
131
|
self.code_model.get_relative_import_path(serialize_namespace),
|
|
124
|
-
"
|
|
132
|
+
"_unions",
|
|
125
133
|
ImportType.LOCAL,
|
|
126
134
|
TypingSection.TYPING,
|
|
127
135
|
)
|
|
@@ -165,7 +173,12 @@ class PagingResponse(Response):
|
|
|
165
173
|
)
|
|
166
174
|
|
|
167
175
|
def get_polymorphic_subtypes(self, polymorphic_subtypes: list["ModelType"]) -> None:
|
|
168
|
-
|
|
176
|
+
if isinstance(self.item_type, CombinedType):
|
|
177
|
+
target = self.item_type.target_model_subtype((ModelType,))
|
|
178
|
+
if target:
|
|
179
|
+
target.get_polymorphic_subtypes(polymorphic_subtypes)
|
|
180
|
+
else:
|
|
181
|
+
self.item_type.get_polymorphic_subtypes(polymorphic_subtypes)
|
|
169
182
|
|
|
170
183
|
def get_json_template_representation(self) -> Any:
|
|
171
184
|
return self.item_type.get_json_template_representation()
|
|
@@ -41,14 +41,11 @@ class NamespaceType(str, Enum):
|
|
|
41
41
|
OPERATION = "operation"
|
|
42
42
|
CLIENT = "client"
|
|
43
43
|
TYPES_FILE = "types_file"
|
|
44
|
+
UNIONS_FILE = "unions_file"
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
LOCALS_LENGTH_LIMIT = 25
|
|
47
48
|
|
|
48
|
-
REQUEST_BUILDER_BODY_VARIABLES_LENGTH =
|
|
49
|
-
6 # how many body variables are present in a request builder
|
|
50
|
-
)
|
|
49
|
+
REQUEST_BUILDER_BODY_VARIABLES_LENGTH = 6 # how many body variables are present in a request builder
|
|
51
50
|
|
|
52
|
-
OPERATION_BODY_VARIABLES_LENGTH =
|
|
53
|
-
14 # how many body variables are present in an operation
|
|
54
|
-
)
|
|
51
|
+
OPERATION_BODY_VARIABLES_LENGTH = 14 # how many body variables are present in an operation
|
|
@@ -33,6 +33,7 @@ from .patch_serializer import PatchSerializer
|
|
|
33
33
|
from .sample_serializer import SampleSerializer
|
|
34
34
|
from .test_serializer import TestSerializer, TestGeneralSerializer
|
|
35
35
|
from .types_serializer import TypesSerializer
|
|
36
|
+
from .unions_serializer import UnionsSerializer
|
|
36
37
|
from ...utils import to_snake_case, VALID_PACKAGE_MODE
|
|
37
38
|
from .utils import extract_sample_name, get_namespace_from_package_name, get_namespace_config, hash_file_import
|
|
38
39
|
|
|
@@ -120,7 +121,7 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
120
121
|
# If parsing the version fails, we assume the version file is not valid and overwrite.
|
|
121
122
|
return False
|
|
122
123
|
|
|
123
|
-
# pylint: disable=too-many-branches
|
|
124
|
+
# pylint: disable=too-many-branches,too-many-statements
|
|
124
125
|
def serialize(self) -> None:
|
|
125
126
|
# remove existing folders when generate from tsp
|
|
126
127
|
if self.code_model.is_tsp and self.code_model.options.get("clear-output-folder"):
|
|
@@ -200,12 +201,13 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
200
201
|
general_serializer.serialize_pkgutil_init_file(),
|
|
201
202
|
)
|
|
202
203
|
|
|
203
|
-
# _utils/py.typed/
|
|
204
|
+
# _utils/py.typed/_unions.py/types.py/_validation.py
|
|
204
205
|
# is always put in top level namespace
|
|
205
206
|
if self.code_model.is_top_namespace(client_namespace):
|
|
206
207
|
self._serialize_and_write_top_level_folder(env=env, namespace=client_namespace)
|
|
207
208
|
|
|
208
209
|
# add models folder if there are models in this namespace
|
|
210
|
+
is_typeddict_mode = self.code_model.options["models-mode"] == "typeddict"
|
|
209
211
|
if (
|
|
210
212
|
self.code_model.has_non_json_models(client_namespace_type.models) or client_namespace_type.enums
|
|
211
213
|
) and self.code_model.options["models-mode"]:
|
|
@@ -213,7 +215,27 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
213
215
|
env=env,
|
|
214
216
|
namespace=client_namespace,
|
|
215
217
|
models=client_namespace_type.models,
|
|
216
|
-
enums=client_namespace_type.enums,
|
|
218
|
+
enums=[] if is_typeddict_mode else client_namespace_type.enums,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# write types.py per namespace (alongside models/)
|
|
222
|
+
# Only generate types.py if at least one model/enum would be imported via types
|
|
223
|
+
# in operations (the model itself volunteers this via is_used_in_operations_via_types)
|
|
224
|
+
has_types_models = any(
|
|
225
|
+
m.is_used_in_operations_via_types for m in client_namespace_type.models if m.base != "json"
|
|
226
|
+
)
|
|
227
|
+
has_types_enums = any(e.is_typeddict_mode for e in client_namespace_type.enums)
|
|
228
|
+
if has_types_models or has_types_enums:
|
|
229
|
+
generation_dir = self.code_model.get_generation_dir(client_namespace)
|
|
230
|
+
self.write_file(
|
|
231
|
+
generation_dir / Path("types.py"),
|
|
232
|
+
TypesSerializer(
|
|
233
|
+
code_model=self.code_model,
|
|
234
|
+
env=env,
|
|
235
|
+
client_namespace=client_namespace,
|
|
236
|
+
models=client_namespace_type.models,
|
|
237
|
+
enums=client_namespace_type.enums if is_typeddict_mode else None,
|
|
238
|
+
).serialize(),
|
|
217
239
|
)
|
|
218
240
|
|
|
219
241
|
if not self.code_model.options["models-mode"]:
|
|
@@ -298,11 +320,19 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
298
320
|
) -> None:
|
|
299
321
|
# Write the models folder
|
|
300
322
|
models_path = self.code_model.get_generation_dir(namespace) / "models"
|
|
301
|
-
|
|
302
|
-
if
|
|
323
|
+
models_mode = self.code_model.options["models-mode"]
|
|
324
|
+
if models_mode in ("dpg", "typeddict"):
|
|
325
|
+
serializer = DpgModelSerializer
|
|
326
|
+
else:
|
|
327
|
+
serializer = MsrestModelSerializer
|
|
328
|
+
# Filter out typed-dict-only models and typeddict copies — they only appear in types.py, not as model classes
|
|
329
|
+
class_models = [m for m in models if not m.is_typed_dict_only and m.base != "typeddict"]
|
|
330
|
+
if self.code_model.has_non_json_models(class_models):
|
|
303
331
|
self.write_file(
|
|
304
332
|
models_path / Path(f"{self.code_model.models_filename}.py"),
|
|
305
|
-
serializer(
|
|
333
|
+
serializer(
|
|
334
|
+
code_model=self.code_model, env=env, client_namespace=namespace, models=class_models
|
|
335
|
+
).serialize(),
|
|
306
336
|
)
|
|
307
337
|
if enums:
|
|
308
338
|
self.write_file(
|
|
@@ -313,7 +343,7 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
313
343
|
)
|
|
314
344
|
self.write_file(
|
|
315
345
|
models_path / Path("__init__.py"),
|
|
316
|
-
ModelInitSerializer(code_model=self.code_model, env=env, models=
|
|
346
|
+
ModelInitSerializer(code_model=self.code_model, env=env, models=class_models, enums=enums).serialize(),
|
|
317
347
|
)
|
|
318
348
|
|
|
319
349
|
self._keep_patch_file(models_path / Path("_patch.py"), env)
|
|
@@ -442,7 +472,9 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
442
472
|
# when there is client.py, there must be __init__.py
|
|
443
473
|
self.write_file(
|
|
444
474
|
generation_path / Path(f"{async_path}__init__.py"),
|
|
445
|
-
general_serializer.serialize_init_file(
|
|
475
|
+
general_serializer.serialize_init_file(
|
|
476
|
+
[c for c in clients if c.has_operations],
|
|
477
|
+
),
|
|
446
478
|
)
|
|
447
479
|
|
|
448
480
|
# if there was a patch file before, we keep it
|
|
@@ -519,11 +551,14 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
519
551
|
general_serializer.serialize_validation_file(),
|
|
520
552
|
)
|
|
521
553
|
|
|
522
|
-
# write
|
|
554
|
+
# write _unions.py
|
|
523
555
|
if self.code_model.named_unions:
|
|
524
556
|
self.write_file(
|
|
525
|
-
generation_dir / Path("
|
|
526
|
-
|
|
557
|
+
generation_dir / Path("_unions.py"),
|
|
558
|
+
UnionsSerializer(
|
|
559
|
+
code_model=self.code_model,
|
|
560
|
+
env=env,
|
|
561
|
+
).serialize(),
|
|
527
562
|
)
|
|
528
563
|
|
|
529
564
|
# pylint: disable=line-too-long
|
|
@@ -196,6 +196,16 @@ def is_json_model_type(parameters: ParameterListType) -> bool:
|
|
|
196
196
|
)
|
|
197
197
|
|
|
198
198
|
|
|
199
|
+
def _is_dpg_or_typeddict_body(body_param: BodyParameter) -> bool:
|
|
200
|
+
"""Check if a body parameter is a DPG model or a CombinedType wrapping one."""
|
|
201
|
+
body_type = body_param.type
|
|
202
|
+
if isinstance(body_type, DPGModelType):
|
|
203
|
+
return True
|
|
204
|
+
if isinstance(body_type, CombinedType):
|
|
205
|
+
return body_type.target_model_subtype((DPGModelType,)) is not None
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
|
|
199
209
|
class _BuilderBaseSerializer(Generic[BuilderType]):
|
|
200
210
|
def __init__(self, code_model: CodeModel, async_mode: bool, client_namespace: str) -> None:
|
|
201
211
|
self.code_model = code_model
|
|
@@ -712,8 +722,20 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
712
722
|
f"_{body_kwarg_name} = self._serialize.body({body_param.client_name}, "
|
|
713
723
|
f"'{serialization_type}'{is_xml_cmd}{serialization_ctxt_cmd})"
|
|
714
724
|
)
|
|
725
|
+
elif self.code_model.options["models-mode"] == "typeddict":
|
|
726
|
+
# TypedDict-only models are plain dicts — no serialization needed
|
|
727
|
+
create_body_call = f"_{body_kwarg_name} = {body_param.client_name}"
|
|
715
728
|
elif self.code_model.options["models-mode"] == "dpg":
|
|
716
|
-
if
|
|
729
|
+
# Check if this is a typeddict-only model within dpg mode — skip serialization
|
|
730
|
+
body_model_type = body_param.type
|
|
731
|
+
if isinstance(body_model_type, CombinedType):
|
|
732
|
+
body_model_type = body_model_type.target_model_subtype((DPGModelType,))
|
|
733
|
+
is_typeddict_only_body = isinstance(body_model_type, DPGModelType) and getattr(
|
|
734
|
+
body_model_type, "is_typed_dict_only", False
|
|
735
|
+
)
|
|
736
|
+
if is_typeddict_only_body:
|
|
737
|
+
create_body_call = f"_{body_kwarg_name} = {body_param.client_name}"
|
|
738
|
+
elif json_serializable(body_param.default_content_type):
|
|
717
739
|
if hasattr(body_param.type, "encode") and body_param.type.encode: # type: ignore
|
|
718
740
|
create_body_call = (
|
|
719
741
|
f"_{body_kwarg_name} = json.dumps({body_param.client_name}, "
|
|
@@ -791,9 +813,9 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
791
813
|
overload.request_builder.parameters.body_parameter.client_name for overload in builder.overloads
|
|
792
814
|
]
|
|
793
815
|
all_dpg_model_overloads = False
|
|
794
|
-
if self.code_model.options["models-mode"]
|
|
816
|
+
if self.code_model.options["models-mode"] in ("dpg", "typeddict") and builder.overloads:
|
|
795
817
|
all_dpg_model_overloads = all(
|
|
796
|
-
|
|
818
|
+
_is_dpg_or_typeddict_body(o.parameters.body_parameter) for o in builder.overloads
|
|
797
819
|
)
|
|
798
820
|
if not all_dpg_model_overloads:
|
|
799
821
|
for v in sorted(set(client_names), key=client_names.index):
|
|
@@ -1002,6 +1024,12 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
1002
1024
|
elif self.code_model.options["models-mode"] == "dpg":
|
|
1003
1025
|
if builder.has_stream_response:
|
|
1004
1026
|
deserialize_code.append("deserialized = response.content")
|
|
1027
|
+
elif isinstance(response.type, ModelType) and response.type.is_typed_dict_only:
|
|
1028
|
+
# Typed-dict-only models skip deserialization — return raw JSON
|
|
1029
|
+
deserialize_code.append("if response.content:")
|
|
1030
|
+
deserialize_code.append(" deserialized = response.json()")
|
|
1031
|
+
deserialize_code.append("else:")
|
|
1032
|
+
deserialize_code.append(" deserialized = None")
|
|
1005
1033
|
else:
|
|
1006
1034
|
format_filed = (
|
|
1007
1035
|
f', format="{response.type.encode}"'
|
|
@@ -1437,18 +1465,23 @@ class _PagingOperationSerializer(_OperationSerializer[PagingOperationType]):
|
|
|
1437
1465
|
)
|
|
1438
1466
|
pylint_disable = ""
|
|
1439
1467
|
if self.code_model.options["models-mode"] == "dpg":
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1468
|
+
is_item_typed_dict_only = isinstance(builder.item_type, ModelType) and builder.item_type.is_typed_dict_only
|
|
1469
|
+
if is_item_typed_dict_only:
|
|
1470
|
+
# Typed-dict-only models skip deserialization — return raw JSON items
|
|
1471
|
+
list_of_elem_deserialized = [f"deserialized{access}"]
|
|
1472
|
+
else:
|
|
1473
|
+
item_type = builder.item_type.type_annotation(
|
|
1474
|
+
is_operation_file=True, serialize_namespace=self.serialize_namespace
|
|
1475
|
+
)
|
|
1476
|
+
pylint_disable = (
|
|
1477
|
+
" # pylint: disable=protected-access" if getattr(builder.item_type, "internal", False) else ""
|
|
1478
|
+
)
|
|
1479
|
+
list_of_elem_deserialized = [
|
|
1480
|
+
"_deserialize(",
|
|
1481
|
+
f"{item_type},{pylint_disable}",
|
|
1482
|
+
f"deserialized{access},",
|
|
1483
|
+
")",
|
|
1484
|
+
]
|
|
1452
1485
|
else:
|
|
1453
1486
|
list_of_elem_deserialized = [f"deserialized{access}"]
|
|
1454
1487
|
list_of_elem_deserialized_str = "\n ".join(list_of_elem_deserialized)
|
|
@@ -32,7 +32,7 @@ class ModelInitSerializer:
|
|
|
32
32
|
", ".join(model_enum_name_intersection)
|
|
33
33
|
)
|
|
34
34
|
)
|
|
35
|
-
has_models = self.models
|
|
35
|
+
has_models = self.code_model.has_non_json_models(self.models)
|
|
36
36
|
has_enums = self.enums
|
|
37
37
|
template = self.env.get_template("model_init.py.jinja2")
|
|
38
38
|
return template.render(
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Licensed under the MIT License. See License.txt in the project root for
|
|
4
4
|
# license information.
|
|
5
5
|
# --------------------------------------------------------------------------
|
|
6
|
-
from typing import Optional
|
|
6
|
+
from typing import Any, Optional
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
|
|
9
9
|
from ..models import ModelType, Property, ConstantType, EnumValue, EnumType
|
|
@@ -73,12 +73,19 @@ def _get_xml_deserializer_enum_type(prop: Property) -> Optional[EnumType]:
|
|
|
73
73
|
return prop_type if isinstance(prop_type, EnumType) else None
|
|
74
74
|
|
|
75
75
|
|
|
76
|
-
def _documentation_string(
|
|
76
|
+
def _documentation_string(
|
|
77
|
+
prop: Property, description_keyword: str, docstring_type_keyword: str, **kwargs: Any
|
|
78
|
+
) -> list[str]:
|
|
77
79
|
retval: list[str] = []
|
|
78
80
|
sphinx_prefix = f":{description_keyword} {prop.client_name}:"
|
|
79
81
|
description = prop.description(is_operation_file=False).replace("\\", "\\\\")
|
|
80
82
|
retval.append(f"{sphinx_prefix} {description}" if description else sphinx_prefix)
|
|
81
|
-
|
|
83
|
+
# In the types file, use type_annotation (the serialized form) for docstrings
|
|
84
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
85
|
+
doc_type = prop.type.type_annotation(**kwargs)
|
|
86
|
+
else:
|
|
87
|
+
doc_type = prop.type.docstring_type(**kwargs)
|
|
88
|
+
retval.append(f":{docstring_type_keyword} {prop.client_name}: {doc_type}")
|
|
82
89
|
return retval
|
|
83
90
|
|
|
84
91
|
|
|
@@ -494,3 +501,120 @@ class DpgModelSerializer(_ModelSerializer):
|
|
|
494
501
|
if final_result:
|
|
495
502
|
return "# pylint: disable=" + ", ".join(final_result)
|
|
496
503
|
return ""
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
class TypedDictModelSerializer(_ModelSerializer):
|
|
507
|
+
def _is_parent_discriminated_base(self, model: ModelType) -> bool:
|
|
508
|
+
"""Check if any parent of this model is a discriminated base (has discriminated_subtypes)."""
|
|
509
|
+
return any(p.discriminated_subtypes for p in model.parents)
|
|
510
|
+
|
|
511
|
+
def _reorder_models(self, models: list[ModelType]) -> list[ModelType]:
|
|
512
|
+
"""Reorder so discriminated base Union aliases come after all their subtypes."""
|
|
513
|
+
bases = [m for m in models if m.discriminated_subtypes]
|
|
514
|
+
non_bases = [m for m in models if not m.discriminated_subtypes]
|
|
515
|
+
return non_bases + bases
|
|
516
|
+
|
|
517
|
+
def serialize(self) -> str:
|
|
518
|
+
template = self.env.get_template("model_container.py.jinja2")
|
|
519
|
+
return template.render(
|
|
520
|
+
code_model=self.code_model,
|
|
521
|
+
imports=FileImportSerializer(self.imports()),
|
|
522
|
+
str=str,
|
|
523
|
+
serializer=self,
|
|
524
|
+
models=self._reorder_models(self.models),
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
def imports(self) -> FileImport:
|
|
528
|
+
file_import = FileImport(self.code_model)
|
|
529
|
+
has_required = False
|
|
530
|
+
has_discriminated_union = False
|
|
531
|
+
for model in self.models:
|
|
532
|
+
if model.base == "json":
|
|
533
|
+
continue
|
|
534
|
+
if model.discriminated_subtypes:
|
|
535
|
+
has_discriminated_union = True
|
|
536
|
+
file_import.merge(
|
|
537
|
+
model.imports(
|
|
538
|
+
is_operation_file=False,
|
|
539
|
+
serialize_namespace=self.serialize_namespace,
|
|
540
|
+
serialize_namespace_type=NamespaceType.MODEL,
|
|
541
|
+
)
|
|
542
|
+
)
|
|
543
|
+
for prop in model.properties:
|
|
544
|
+
file_import.merge(
|
|
545
|
+
prop.imports(
|
|
546
|
+
serialize_namespace=self.serialize_namespace,
|
|
547
|
+
serialize_namespace_type=NamespaceType.MODEL,
|
|
548
|
+
called_by_property=True,
|
|
549
|
+
)
|
|
550
|
+
)
|
|
551
|
+
if not (prop.optional or prop.client_default_value is not None):
|
|
552
|
+
has_required = True
|
|
553
|
+
for parent in model.parents:
|
|
554
|
+
if parent.client_namespace != model.client_namespace and not parent.discriminated_subtypes:
|
|
555
|
+
file_import.add_submodule_import(
|
|
556
|
+
self.code_model.get_relative_import_path(
|
|
557
|
+
self.serialize_namespace,
|
|
558
|
+
self.code_model.get_imported_namespace_for_model(parent.client_namespace),
|
|
559
|
+
),
|
|
560
|
+
parent.name,
|
|
561
|
+
ImportType.LOCAL,
|
|
562
|
+
)
|
|
563
|
+
file_import.add_submodule_import("typing_extensions", "TypedDict", ImportType.STDLIB)
|
|
564
|
+
if has_required:
|
|
565
|
+
file_import.add_submodule_import("typing_extensions", "Required", ImportType.STDLIB)
|
|
566
|
+
if has_discriminated_union:
|
|
567
|
+
file_import.add_submodule_import("typing", "Union", ImportType.STDLIB)
|
|
568
|
+
return file_import
|
|
569
|
+
|
|
570
|
+
def declare_model(self, model: ModelType) -> str:
|
|
571
|
+
# If the model's parent is a discriminated base, don't inherit from it
|
|
572
|
+
non_discriminated_parents = [p for p in model.parents if not p.discriminated_subtypes]
|
|
573
|
+
if non_discriminated_parents:
|
|
574
|
+
basename = ", ".join([m.name for m in non_discriminated_parents])
|
|
575
|
+
return f"class {model.name}({basename}):{model.pylint_disable()}"
|
|
576
|
+
return f"class {model.name}(TypedDict, total=False):{model.pylint_disable()}"
|
|
577
|
+
|
|
578
|
+
@staticmethod
|
|
579
|
+
def get_properties_to_declare(model: ModelType) -> list[Property]:
|
|
580
|
+
# Only exclude inherited properties from non-discriminated parents
|
|
581
|
+
non_discriminated_parents = [p for p in model.parents if not p.discriminated_subtypes]
|
|
582
|
+
if non_discriminated_parents:
|
|
583
|
+
parent_properties = [p for bm in non_discriminated_parents for p in bm.properties]
|
|
584
|
+
properties_to_declare = [
|
|
585
|
+
p
|
|
586
|
+
for p in model.properties
|
|
587
|
+
if not any(
|
|
588
|
+
p.client_name == pp.client_name
|
|
589
|
+
and p.type_annotation() == pp.type_annotation()
|
|
590
|
+
and not p.is_base_discriminator
|
|
591
|
+
for pp in parent_properties
|
|
592
|
+
)
|
|
593
|
+
]
|
|
594
|
+
else:
|
|
595
|
+
properties_to_declare = model.properties
|
|
596
|
+
return properties_to_declare
|
|
597
|
+
|
|
598
|
+
def declare_property(self, prop: Property) -> str:
|
|
599
|
+
type_annotation = prop.type_annotation(serialize_namespace=self.serialize_namespace)
|
|
600
|
+
is_optional = prop.optional or prop.client_default_value is not None
|
|
601
|
+
if is_optional:
|
|
602
|
+
return f"{prop.wire_name}: {type_annotation}"
|
|
603
|
+
return f"{prop.wire_name}: Required[{type_annotation}]"
|
|
604
|
+
|
|
605
|
+
def initialize_properties(self, model: ModelType) -> list[str]:
|
|
606
|
+
return []
|
|
607
|
+
|
|
608
|
+
def need_init(self, model: ModelType) -> bool:
|
|
609
|
+
return False
|
|
610
|
+
|
|
611
|
+
def discriminated_subtypes_union(self, model: ModelType) -> str:
|
|
612
|
+
subtypes = list(model.discriminated_subtypes.values())
|
|
613
|
+
subtype_names = [s.name for s in subtypes]
|
|
614
|
+
return f"{model.name} = Union[{', '.join(subtype_names)}]"
|
|
615
|
+
|
|
616
|
+
def is_discriminated_base(self, model: ModelType) -> bool:
|
|
617
|
+
return bool(model.discriminated_subtypes)
|
|
618
|
+
|
|
619
|
+
def global_pylint_disables(self) -> str:
|
|
620
|
+
return ""
|