@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
|
@@ -9,6 +9,7 @@ from typing import Any, Optional, Union, TYPE_CHECKING
|
|
|
9
9
|
|
|
10
10
|
from .base import BaseType
|
|
11
11
|
from .imports import FileImport, ImportType, TypingSection
|
|
12
|
+
from .utils import NamespaceType
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
from .code_model import CodeModel
|
|
@@ -271,6 +272,8 @@ class DecimalType(NumberType):
|
|
|
271
272
|
return "~" + self.type_annotation()
|
|
272
273
|
|
|
273
274
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
275
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
276
|
+
return "float"
|
|
274
277
|
return "decimal.Decimal"
|
|
275
278
|
|
|
276
279
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
@@ -281,7 +284,8 @@ class DecimalType(NumberType):
|
|
|
281
284
|
|
|
282
285
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
283
286
|
file_import = FileImport(self.code_model)
|
|
284
|
-
|
|
287
|
+
if kwargs.get("serialize_namespace_type") != NamespaceType.TYPES_FILE:
|
|
288
|
+
file_import.add_import("decimal", ImportType.STDLIB)
|
|
285
289
|
return file_import
|
|
286
290
|
|
|
287
291
|
@property
|
|
@@ -357,6 +361,8 @@ class DatetimeType(PrimitiveType):
|
|
|
357
361
|
return "~" + self.type_annotation()
|
|
358
362
|
|
|
359
363
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
364
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
365
|
+
return "str"
|
|
360
366
|
return "datetime.datetime"
|
|
361
367
|
|
|
362
368
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
@@ -370,7 +376,8 @@ class DatetimeType(PrimitiveType):
|
|
|
370
376
|
|
|
371
377
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
372
378
|
file_import = FileImport(self.code_model)
|
|
373
|
-
|
|
379
|
+
if kwargs.get("serialize_namespace_type") != NamespaceType.TYPES_FILE:
|
|
380
|
+
file_import.add_import("datetime", ImportType.STDLIB)
|
|
374
381
|
return file_import
|
|
375
382
|
|
|
376
383
|
@property
|
|
@@ -399,6 +406,8 @@ class TimeType(PrimitiveType):
|
|
|
399
406
|
return "~" + self.type_annotation()
|
|
400
407
|
|
|
401
408
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
409
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
410
|
+
return "str"
|
|
402
411
|
return "datetime.time"
|
|
403
412
|
|
|
404
413
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
@@ -412,7 +421,8 @@ class TimeType(PrimitiveType):
|
|
|
412
421
|
|
|
413
422
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
414
423
|
file_import = FileImport(self.code_model)
|
|
415
|
-
|
|
424
|
+
if kwargs.get("serialize_namespace_type") != NamespaceType.TYPES_FILE:
|
|
425
|
+
file_import.add_import("datetime", ImportType.STDLIB)
|
|
416
426
|
return file_import
|
|
417
427
|
|
|
418
428
|
@property
|
|
@@ -445,6 +455,8 @@ class UnixTimeType(PrimitiveType):
|
|
|
445
455
|
return "~" + self.type_annotation()
|
|
446
456
|
|
|
447
457
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
458
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
459
|
+
return "int"
|
|
448
460
|
return "datetime.datetime"
|
|
449
461
|
|
|
450
462
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
@@ -458,7 +470,8 @@ class UnixTimeType(PrimitiveType):
|
|
|
458
470
|
|
|
459
471
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
460
472
|
file_import = FileImport(self.code_model)
|
|
461
|
-
|
|
473
|
+
if kwargs.get("serialize_namespace_type") != NamespaceType.TYPES_FILE:
|
|
474
|
+
file_import.add_import("datetime", ImportType.STDLIB)
|
|
462
475
|
return file_import
|
|
463
476
|
|
|
464
477
|
@property
|
|
@@ -487,6 +500,8 @@ class DateType(PrimitiveType):
|
|
|
487
500
|
return "~" + self.type_annotation()
|
|
488
501
|
|
|
489
502
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
503
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
504
|
+
return "str"
|
|
490
505
|
return "datetime.date"
|
|
491
506
|
|
|
492
507
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
@@ -500,7 +515,8 @@ class DateType(PrimitiveType):
|
|
|
500
515
|
|
|
501
516
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
502
517
|
file_import = FileImport(self.code_model)
|
|
503
|
-
|
|
518
|
+
if kwargs.get("serialize_namespace_type") != NamespaceType.TYPES_FILE:
|
|
519
|
+
file_import.add_import("datetime", ImportType.STDLIB)
|
|
504
520
|
return file_import
|
|
505
521
|
|
|
506
522
|
@property
|
|
@@ -543,6 +559,8 @@ class DurationType(PrimitiveType):
|
|
|
543
559
|
return "~" + self.type_annotation()
|
|
544
560
|
|
|
545
561
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
562
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
563
|
+
return "str"
|
|
546
564
|
return "datetime.timedelta"
|
|
547
565
|
|
|
548
566
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
@@ -556,7 +574,8 @@ class DurationType(PrimitiveType):
|
|
|
556
574
|
|
|
557
575
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
558
576
|
file_import = FileImport(self.code_model)
|
|
559
|
-
|
|
577
|
+
if kwargs.get("serialize_namespace_type") != NamespaceType.TYPES_FILE:
|
|
578
|
+
file_import.add_import("datetime", ImportType.STDLIB)
|
|
560
579
|
return file_import
|
|
561
580
|
|
|
562
581
|
@property
|
|
@@ -590,6 +609,11 @@ class ByteArraySchema(PrimitiveType):
|
|
|
590
609
|
def docstring_type(self, **kwargs: Any) -> str:
|
|
591
610
|
return "bytes"
|
|
592
611
|
|
|
612
|
+
def type_annotation(self, **kwargs: Any) -> str:
|
|
613
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
614
|
+
return "str"
|
|
615
|
+
return "bytes"
|
|
616
|
+
|
|
593
617
|
def get_declaration(self, value: str) -> str:
|
|
594
618
|
return f'bytes("{value}", encoding="utf-8")'
|
|
595
619
|
|
|
@@ -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 ""
|