@typespec/http-client-python 0.35.0 → 0.36.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/README.md +6 -0
- package/dist/emitter/code-model.d.ts +1 -1
- package/dist/emitter/code-model.d.ts.map +1 -1
- package/dist/emitter/code-model.js.map +1 -1
- package/dist/emitter/emitter.d.ts +2 -2
- package/dist/emitter/emitter.d.ts.map +1 -1
- package/dist/emitter/emitter.js +23 -2
- package/dist/emitter/emitter.js.map +1 -1
- package/dist/emitter/external-process.d.ts +1 -1
- package/dist/emitter/external-process.d.ts.map +1 -1
- package/dist/emitter/external-process.js.map +1 -1
- package/dist/emitter/http.d.ts +2 -2
- package/dist/emitter/http.d.ts.map +1 -1
- package/dist/emitter/http.js +1 -1
- package/dist/emitter/http.js.map +1 -1
- package/dist/emitter/index.d.ts +2 -1
- package/dist/emitter/index.d.ts.map +1 -1
- package/dist/emitter/index.js.map +1 -1
- package/dist/emitter/lib.d.ts +3 -2
- package/dist/emitter/lib.d.ts.map +1 -1
- package/dist/emitter/lib.js +6 -1
- package/dist/emitter/lib.js.map +1 -1
- package/dist/emitter/node-runner.browser.d.ts +1 -1
- package/dist/emitter/node-runner.browser.d.ts.map +1 -1
- package/dist/emitter/node-runner.browser.js.map +1 -1
- package/dist/emitter/node-runner.d.ts +3 -3
- package/dist/emitter/node-runner.d.ts.map +1 -1
- package/dist/emitter/node-runner.js.map +1 -1
- package/dist/emitter/system-requirements.d.ts.map +1 -1
- package/dist/emitter/system-requirements.js.map +1 -1
- package/dist/emitter/types.d.ts +4 -4
- package/dist/emitter/types.d.ts.map +1 -1
- package/dist/emitter/types.js +15 -3
- package/dist/emitter/types.js.map +1 -1
- package/dist/emitter/utils.d.ts +2 -2
- package/dist/emitter/utils.d.ts.map +1 -1
- package/dist/emitter/utils.js +1 -1
- package/dist/emitter/utils.js.map +1 -1
- package/emitter/src/code-model.ts +4 -2
- package/emitter/src/emitter.ts +30 -6
- package/emitter/src/external-process.ts +2 -1
- package/emitter/src/http.ts +5 -5
- package/emitter/src/index.ts +2 -1
- package/emitter/src/lib.ts +11 -6
- package/emitter/src/node-runner.browser.ts +2 -1
- package/emitter/src/node-runner.ts +6 -3
- package/emitter/src/system-requirements.ts +2 -1
- package/emitter/src/types.ts +23 -8
- package/emitter/src/utils.ts +5 -4
- package/emitter/temp/tsconfig.tsbuildinfo +1 -1
- package/emitter/test/browser-pyodide.test.ts +25 -0
- package/emitter/test/types.test.ts +72 -0
- package/eng/scripts/ci/dev_requirements.txt +1 -1
- package/eng/scripts/ci/regenerate-common.ts +29 -100
- package/eng/scripts/ci/regenerate.ts +3 -4
- package/eng/scripts/ci/run-tests.ts +2 -1
- package/eng/scripts/setup/__pycache__/package_manager.cpython-311.pyc +0 -0
- package/eng/scripts/setup/__pycache__/venvtools.cpython-311.pyc +0 -0
- package/eng/scripts/setup/system-requirements.ts +2 -1
- package/generator/build/lib/pygen/__init__.py +24 -5
- package/generator/build/lib/pygen/codegen/models/__init__.py +3 -1
- package/generator/build/lib/pygen/codegen/models/code_model.py +7 -1
- package/generator/build/lib/pygen/codegen/models/enum_type.py +7 -7
- package/generator/build/lib/pygen/codegen/models/list_type.py +1 -0
- package/generator/build/lib/pygen/codegen/models/model_type.py +1 -3
- package/generator/build/lib/pygen/codegen/models/operation.py +7 -2
- package/generator/build/lib/pygen/codegen/models/parameter.py +3 -1
- package/generator/build/lib/pygen/codegen/models/utils.py +14 -0
- package/generator/build/lib/pygen/codegen/serializers/__init__.py +6 -6
- package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +28 -14
- package/generator/build/lib/pygen/codegen/serializers/client_serializer.py +13 -6
- package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +21 -1
- package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +9 -3
- package/generator/build/lib/pygen/codegen/serializers/parameter_serializer.py +17 -3
- package/generator/build/lib/pygen/codegen/serializers/types_serializer.py +5 -12
- package/generator/build/lib/pygen/codegen/templates/validation.py.jinja2 +1 -1
- package/generator/build/lib/pygen/preprocess/__init__.py +68 -17
- package/generator/build/lib/pygen/utils.py +15 -0
- package/generator/component-detection-pip-report.json +7 -7
- package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
- package/generator/pygen/__init__.py +24 -5
- package/generator/pygen/codegen/models/__init__.py +3 -1
- package/generator/pygen/codegen/models/code_model.py +7 -1
- package/generator/pygen/codegen/models/enum_type.py +7 -7
- package/generator/pygen/codegen/models/list_type.py +1 -0
- package/generator/pygen/codegen/models/model_type.py +1 -3
- package/generator/pygen/codegen/models/operation.py +7 -2
- package/generator/pygen/codegen/models/parameter.py +3 -1
- package/generator/pygen/codegen/models/utils.py +14 -0
- package/generator/pygen/codegen/serializers/__init__.py +6 -6
- package/generator/pygen/codegen/serializers/builder_serializer.py +28 -14
- package/generator/pygen/codegen/serializers/client_serializer.py +13 -6
- package/generator/pygen/codegen/serializers/general_serializer.py +21 -1
- package/generator/pygen/codegen/serializers/model_serializer.py +9 -3
- package/generator/pygen/codegen/serializers/parameter_serializer.py +17 -3
- package/generator/pygen/codegen/serializers/types_serializer.py +5 -12
- package/generator/pygen/codegen/templates/validation.py.jinja2 +1 -1
- package/generator/pygen/preprocess/__init__.py +68 -17
- package/generator/pygen/utils.py +15 -0
- package/generator/pygen.egg-info/PKG-INFO +1 -1
- package/generator/pygen.egg-info/requires.txt +1 -1
- package/generator/setup.py +1 -1
- package/package.json +37 -35
|
@@ -32,7 +32,7 @@ from ..models import (
|
|
|
32
32
|
ParameterListType,
|
|
33
33
|
ByteArraySchema,
|
|
34
34
|
)
|
|
35
|
-
from ..models.utils import NamespaceType
|
|
35
|
+
from ..models.utils import NamespaceType, escape_sphinx_field_name
|
|
36
36
|
from .parameter_serializer import ParameterSerializer, PopKwargType, check_body_optional
|
|
37
37
|
from ..models.parameter_list import ParameterType
|
|
38
38
|
from . import utils
|
|
@@ -311,16 +311,15 @@ class _BuilderBaseSerializer(Generic[BuilderType]):
|
|
|
311
311
|
or param.method_location == ParameterMethodLocation.KWARG
|
|
312
312
|
):
|
|
313
313
|
continue
|
|
314
|
+
escaped_name = escape_sphinx_field_name(param.client_name)
|
|
314
315
|
description_list.extend(
|
|
315
|
-
f":{param.description_keyword} {
|
|
316
|
-
"\n"
|
|
317
|
-
)
|
|
316
|
+
f":{param.description_keyword} {escaped_name}: {param.description}".replace("\n", "\n ").split("\n")
|
|
318
317
|
)
|
|
319
318
|
docstring_type = param.docstring_type(
|
|
320
319
|
async_mode=self.async_mode,
|
|
321
320
|
serialize_namespace=self.serialize_namespace,
|
|
322
321
|
)
|
|
323
|
-
description_list.append(f":{param.docstring_type_keyword} {
|
|
322
|
+
description_list.append(f":{param.docstring_type_keyword} {escaped_name}: {docstring_type}")
|
|
324
323
|
return description_list
|
|
325
324
|
|
|
326
325
|
def param_description_and_response_docstring(self, builder: BuilderType) -> list[str]:
|
|
@@ -462,6 +461,7 @@ class RequestBuilderSerializer(_BuilderBaseSerializer[RequestBuilderType]):
|
|
|
462
461
|
check_kwarg_dict=True,
|
|
463
462
|
pop_headers_kwarg=(PopKwargType.CASE_INSENSITIVE if bool(builder.parameters.headers) else PopKwargType.NO),
|
|
464
463
|
pop_params_kwarg=(PopKwargType.CASE_INSENSITIVE if bool(builder.parameters.query) else PopKwargType.NO),
|
|
464
|
+
is_body_optional=builder.parameters.has_body and builder.parameters.body_parameter.optional,
|
|
465
465
|
)
|
|
466
466
|
|
|
467
467
|
@staticmethod
|
|
@@ -491,6 +491,7 @@ class RequestBuilderSerializer(_BuilderBaseSerializer[RequestBuilderType]):
|
|
|
491
491
|
for h in builder.parameters.headers
|
|
492
492
|
if not builder.has_form_data_body or h.wire_name.lower() != "content-type"
|
|
493
493
|
]
|
|
494
|
+
is_body_optional = builder.parameters.has_body and builder.parameters.body_parameter.optional
|
|
494
495
|
retval = ["# Construct headers"] if headers else []
|
|
495
496
|
for header in headers:
|
|
496
497
|
retval.extend(
|
|
@@ -499,6 +500,7 @@ class RequestBuilderSerializer(_BuilderBaseSerializer[RequestBuilderType]):
|
|
|
499
500
|
"headers",
|
|
500
501
|
self.serializer_name,
|
|
501
502
|
self.code_model.is_legacy,
|
|
503
|
+
is_body_optional=is_body_optional,
|
|
502
504
|
)
|
|
503
505
|
)
|
|
504
506
|
return retval
|
|
@@ -712,7 +714,9 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
712
714
|
send_xml = builder.parameters.body_parameter.type.is_xml
|
|
713
715
|
xml_serialization_ctxt = body_param.type.xml_serialization_ctxt if send_xml else None
|
|
714
716
|
ser_ctxt_name = "serialization_ctxt"
|
|
715
|
-
if xml_serialization_ctxt and
|
|
717
|
+
if xml_serialization_ctxt and (
|
|
718
|
+
self.code_model.options["models-mode"] or self.code_model.generate_typeddict_only
|
|
719
|
+
):
|
|
716
720
|
retval.append(f'{ser_ctxt_name} = {{"xml": {{{xml_serialization_ctxt}}}}}')
|
|
717
721
|
if self.code_model.options["models-mode"] == "msrest":
|
|
718
722
|
is_xml_cmd = _xml_config(send_xml, builder.parameters.body_parameter.content_types)
|
|
@@ -722,7 +726,7 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
722
726
|
f"_{body_kwarg_name} = self._serialize.body({body_param.client_name}, "
|
|
723
727
|
f"'{serialization_type}'{is_xml_cmd}{serialization_ctxt_cmd})"
|
|
724
728
|
)
|
|
725
|
-
elif self.code_model.
|
|
729
|
+
elif self.code_model.generate_typeddict_only:
|
|
726
730
|
# TypedDict-only models are plain dicts — no serialization needed
|
|
727
731
|
create_body_call = f"_{body_kwarg_name} = {body_param.client_name}"
|
|
728
732
|
elif self.code_model.options["models-mode"] == "dpg":
|
|
@@ -841,7 +845,9 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
841
845
|
overload.request_builder.parameters.body_parameter.client_name for overload in builder.overloads
|
|
842
846
|
]
|
|
843
847
|
all_dpg_model_overloads = False
|
|
844
|
-
if
|
|
848
|
+
if (
|
|
849
|
+
self.code_model.options["models-mode"] == "dpg" or self.code_model.generate_typeddict_only
|
|
850
|
+
) and builder.overloads:
|
|
845
851
|
all_dpg_model_overloads = all(
|
|
846
852
|
_is_dpg_or_typeddict_body(o.parameters.body_parameter) for o in builder.overloads
|
|
847
853
|
)
|
|
@@ -1128,8 +1134,8 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
1128
1134
|
retval.extend([f" {l}" for l in response_read])
|
|
1129
1135
|
retval.append(" map_error(status_code=response.status_code, response=response, error_map=error_map)")
|
|
1130
1136
|
error_model = ""
|
|
1131
|
-
if ( # pylint: disable=too-many-nested-blocks
|
|
1132
|
-
|
|
1137
|
+
if builder.non_default_errors and ( # pylint: disable=too-many-nested-blocks
|
|
1138
|
+
self.code_model.options["models-mode"] or self.code_model.generate_typeddict_only
|
|
1133
1139
|
):
|
|
1134
1140
|
error_model = ", model=error"
|
|
1135
1141
|
condition = "if"
|
|
@@ -1211,7 +1217,9 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
1211
1217
|
condition = "elif"
|
|
1212
1218
|
# default error handling
|
|
1213
1219
|
default_error_deserialization = builder.default_error_deserialization(self.serialize_namespace)
|
|
1214
|
-
if default_error_deserialization and
|
|
1220
|
+
if default_error_deserialization and (
|
|
1221
|
+
self.code_model.options["models-mode"] or self.code_model.generate_typeddict_only
|
|
1222
|
+
):
|
|
1215
1223
|
error_model = ", model=error"
|
|
1216
1224
|
indent = " " if builder.non_default_errors else " "
|
|
1217
1225
|
if builder.non_default_errors:
|
|
@@ -1290,7 +1298,11 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
1290
1298
|
else:
|
|
1291
1299
|
retval.extend(self.response_headers_and_deserialization(builder, builder.responses[0]))
|
|
1292
1300
|
retval.append("")
|
|
1293
|
-
if
|
|
1301
|
+
if (
|
|
1302
|
+
builder.has_optional_return_type
|
|
1303
|
+
or self.code_model.options["models-mode"]
|
|
1304
|
+
or self.code_model.generate_typeddict_only
|
|
1305
|
+
):
|
|
1294
1306
|
deserialized = "deserialized"
|
|
1295
1307
|
else:
|
|
1296
1308
|
deserialized = f"cast({builder.response_type_annotation(async_mode=self.async_mode)}, deserialized)"
|
|
@@ -1326,7 +1338,9 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
1326
1338
|
|
|
1327
1339
|
def error_map(self, builder: OperationType) -> list[str]:
|
|
1328
1340
|
retval = ["error_map: MutableMapping = {"]
|
|
1329
|
-
if builder.non_default_errors and
|
|
1341
|
+
if builder.non_default_errors and (
|
|
1342
|
+
self.code_model.options["models-mode"] or self.code_model.generate_typeddict_only
|
|
1343
|
+
):
|
|
1330
1344
|
# TODO: we should decide whether to add the build-in error map when there is a customized default error type
|
|
1331
1345
|
if self._need_specific_error_map(401, builder):
|
|
1332
1346
|
retval.append(" 401: ClientAuthenticationError,")
|
|
@@ -1690,7 +1704,7 @@ class _LROOperationSerializer(_OperationSerializer[LROOperationType]):
|
|
|
1690
1704
|
if builder.lro_response.headers:
|
|
1691
1705
|
retval.append(" response_headers = {}")
|
|
1692
1706
|
if (
|
|
1693
|
-
not self.code_model.options["models-mode"]
|
|
1707
|
+
(not self.code_model.options["models-mode"] and not self.code_model.generate_typeddict_only)
|
|
1694
1708
|
or self.code_model.options["models-mode"] == "dpg"
|
|
1695
1709
|
or builder.lro_response.headers
|
|
1696
1710
|
):
|
|
@@ -8,6 +8,7 @@ from typing import cast
|
|
|
8
8
|
from . import utils
|
|
9
9
|
from ..models import Client, ParameterMethodLocation, Parameter, ParameterLocation
|
|
10
10
|
from .parameter_serializer import ParameterSerializer, PopKwargType
|
|
11
|
+
from ..models.utils import escape_sphinx_field_name
|
|
11
12
|
from ...utils import build_policies
|
|
12
13
|
|
|
13
14
|
|
|
@@ -61,13 +62,16 @@ class ClientSerializer:
|
|
|
61
62
|
retval: list[str] = []
|
|
62
63
|
operations_folder = ".aio.operations." if async_mode else ".operations."
|
|
63
64
|
for og in [og for og in self.client.operation_groups if not og.is_mixin]:
|
|
64
|
-
retval.append(f":ivar {og.property_name}: {og.class_name} operations")
|
|
65
|
+
retval.append(f":ivar {escape_sphinx_field_name(og.property_name)}: {og.class_name} operations")
|
|
65
66
|
property_type = f"{self.client.code_model.namespace}{operations_folder}{og.class_name}"
|
|
66
|
-
retval.append(f":vartype {og.property_name}: {property_type}")
|
|
67
|
+
retval.append(f":vartype {escape_sphinx_field_name(og.property_name)}: {property_type}")
|
|
67
68
|
for param in self.client.parameters.method:
|
|
68
|
-
retval.append(f":{param.description_keyword} {param.client_name}: {param.description}")
|
|
69
69
|
retval.append(
|
|
70
|
-
f":{param.
|
|
70
|
+
f":{param.description_keyword} {escape_sphinx_field_name(param.client_name)}: {param.description}"
|
|
71
|
+
)
|
|
72
|
+
retval.append(
|
|
73
|
+
f":{param.docstring_type_keyword} {escape_sphinx_field_name(param.client_name)}: "
|
|
74
|
+
f"{param.docstring_type(async_mode=async_mode)}"
|
|
71
75
|
)
|
|
72
76
|
if self.client.has_public_lro_operations:
|
|
73
77
|
retval.append(
|
|
@@ -315,7 +319,10 @@ class ConfigSerializer:
|
|
|
315
319
|
def property_descriptions(self, async_mode: bool) -> list[str]:
|
|
316
320
|
retval: list[str] = []
|
|
317
321
|
for p in self.client.config.parameters.method:
|
|
318
|
-
retval.append(f":{p.description_keyword} {p.client_name}: {p.description}")
|
|
319
|
-
retval.append(
|
|
322
|
+
retval.append(f":{p.description_keyword} {escape_sphinx_field_name(p.client_name)}: {p.description}")
|
|
323
|
+
retval.append(
|
|
324
|
+
f":{p.docstring_type_keyword} {escape_sphinx_field_name(p.client_name)}: "
|
|
325
|
+
f"{p.docstring_type(async_mode=async_mode)}"
|
|
326
|
+
)
|
|
320
327
|
retval.append('"""')
|
|
321
328
|
return retval
|
|
@@ -320,7 +320,27 @@ class GeneralSerializer(BaseSerializer):
|
|
|
320
320
|
|
|
321
321
|
def serialize_validation_file(self) -> str:
|
|
322
322
|
template = self.env.get_template("validation.py.jinja2")
|
|
323
|
-
return template.render(
|
|
323
|
+
return template.render(
|
|
324
|
+
code_model=self.code_model,
|
|
325
|
+
client_api_version_name=self._api_version_config_attr_name(),
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
def _api_version_config_attr_name(self) -> str:
|
|
329
|
+
"""Name of the config attribute that stores the API version.
|
|
330
|
+
|
|
331
|
+
The attribute name is derived from the API-version parameter's ``client_name``
|
|
332
|
+
(e.g. ``apiVersion`` -> ``api_version``, or Storage's ``version`` -> ``version``).
|
|
333
|
+
``_validation.py`` holds a single shared decorator for the whole package, so when
|
|
334
|
+
every client agrees on the name we bake it in directly; otherwise we fall back to
|
|
335
|
+
the conventional ``api_version``.
|
|
336
|
+
"""
|
|
337
|
+
names = {
|
|
338
|
+
parameter.client_name
|
|
339
|
+
for client in self.code_model.clients
|
|
340
|
+
for parameter in client.config.parameters.parameters
|
|
341
|
+
if parameter.is_api_version
|
|
342
|
+
}
|
|
343
|
+
return next(iter(names)) if len(names) == 1 else "api_version"
|
|
324
344
|
|
|
325
345
|
def serialize_cross_language_definition_file(self) -> str:
|
|
326
346
|
cross_langauge_def_dict = {
|
|
@@ -23,7 +23,7 @@ from ..models.primitive_types import (
|
|
|
23
23
|
)
|
|
24
24
|
from .import_serializer import FileImportSerializer
|
|
25
25
|
from .base_serializer import BaseSerializer
|
|
26
|
-
from ..models.utils import NamespaceType, add_to_pylint_disable
|
|
26
|
+
from ..models.utils import NamespaceType, add_to_pylint_disable, escape_sphinx_field_name
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def _get_xml_deserializer_name(prop: Property) -> Optional[str]: # pylint: disable=too-many-return-statements
|
|
@@ -77,7 +77,13 @@ def _documentation_string(
|
|
|
77
77
|
prop: Property, description_keyword: str, docstring_type_keyword: str, **kwargs: Any
|
|
78
78
|
) -> list[str]:
|
|
79
79
|
retval: list[str] = []
|
|
80
|
-
|
|
80
|
+
doc_name = (
|
|
81
|
+
prop.wire_name if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE else prop.client_name
|
|
82
|
+
)
|
|
83
|
+
# Escape names that Sphinx would otherwise misinterpret in the info field target
|
|
84
|
+
# (e.g. a wire name with a leading "@" such as "@search.facets").
|
|
85
|
+
doc_name = escape_sphinx_field_name(doc_name)
|
|
86
|
+
sphinx_prefix = f":{description_keyword} {doc_name}:"
|
|
81
87
|
description = prop.description(is_operation_file=False).replace("\\", "\\\\")
|
|
82
88
|
retval.append(f"{sphinx_prefix} {description}" if description else sphinx_prefix)
|
|
83
89
|
# In the types file, use type_annotation (the serialized form) for docstrings
|
|
@@ -85,7 +91,7 @@ def _documentation_string(
|
|
|
85
91
|
doc_type = prop.type.type_annotation(**kwargs)
|
|
86
92
|
else:
|
|
87
93
|
doc_type = prop.type.docstring_type(**kwargs)
|
|
88
|
-
retval.append(f":{docstring_type_keyword} {
|
|
94
|
+
retval.append(f":{docstring_type_keyword} {doc_name}: {doc_type}")
|
|
89
95
|
return retval
|
|
90
96
|
|
|
91
97
|
|
|
@@ -139,6 +139,8 @@ class ParameterSerializer:
|
|
|
139
139
|
kwarg_name: str,
|
|
140
140
|
serializer_name: str,
|
|
141
141
|
is_legacy: bool,
|
|
142
|
+
*,
|
|
143
|
+
is_body_optional: bool = False,
|
|
142
144
|
) -> list[str]:
|
|
143
145
|
if (
|
|
144
146
|
not is_legacy
|
|
@@ -166,7 +168,11 @@ class ParameterSerializer:
|
|
|
166
168
|
param.wire_name,
|
|
167
169
|
self.serialize_parameter(param, serializer_name),
|
|
168
170
|
)
|
|
169
|
-
|
|
171
|
+
# When the body is optional, the content-type may legitimately be ``None`` (no body
|
|
172
|
+
# was sent). In that case the header must be omitted instead of serialized, otherwise
|
|
173
|
+
# ``_SERIALIZER.header`` raises ``ValueError: No value for given attribute``.
|
|
174
|
+
is_content_type_optional = getattr(param, "is_content_type", False) and is_body_optional
|
|
175
|
+
if not param.optional and not is_content_type_optional and (param.in_method_signature or param.constant):
|
|
170
176
|
retval = [set_parameter]
|
|
171
177
|
else:
|
|
172
178
|
retval = [
|
|
@@ -184,6 +190,7 @@ class ParameterSerializer:
|
|
|
184
190
|
check_client_input: bool = False,
|
|
185
191
|
*,
|
|
186
192
|
body_parameter: Optional[BodyParameterType] = None,
|
|
193
|
+
is_body_optional: Optional[bool] = None,
|
|
187
194
|
) -> list[str]:
|
|
188
195
|
retval = []
|
|
189
196
|
|
|
@@ -195,7 +202,8 @@ class ParameterSerializer:
|
|
|
195
202
|
|
|
196
203
|
append_pop_kwarg("headers", pop_headers_kwarg)
|
|
197
204
|
append_pop_kwarg("params", pop_params_kwarg)
|
|
198
|
-
is_body_optional
|
|
205
|
+
if is_body_optional is None:
|
|
206
|
+
is_body_optional = check_body_optional(body_parameter)
|
|
199
207
|
if pop_headers_kwarg != PopKwargType.NO or pop_params_kwarg != PopKwargType.NO:
|
|
200
208
|
retval.append("")
|
|
201
209
|
for kwarg in parameters:
|
|
@@ -206,7 +214,13 @@ class ParameterSerializer:
|
|
|
206
214
|
if is_content_type_optional and not type_annotation.startswith("Optional[")
|
|
207
215
|
else type_annotation
|
|
208
216
|
)
|
|
209
|
-
if
|
|
217
|
+
if (
|
|
218
|
+
kwarg.client_default_value is not None
|
|
219
|
+
or kwarg.optional
|
|
220
|
+
or kwarg.constant
|
|
221
|
+
or kwarg.is_api_version
|
|
222
|
+
or is_content_type_optional
|
|
223
|
+
):
|
|
210
224
|
if check_client_input and kwarg.check_client_input:
|
|
211
225
|
default_value = f"self._config.{kwarg.client_name}"
|
|
212
226
|
elif kwarg.constant:
|
|
@@ -103,21 +103,16 @@ class TypesSerializer(BaseSerializer):
|
|
|
103
103
|
|
|
104
104
|
TypedDicts (and discriminated-base union aliases) in ``types.py`` describe request-body
|
|
105
105
|
(*input*) shapes. Output-only models already render as classes in ``models/`` and are
|
|
106
|
-
referenced via ``_models.*``, so a
|
|
107
|
-
dead code.
|
|
108
|
-
|
|
109
|
-
* a typeddict copy (``base == "typeddict"``) — these only exist as input body overloads
|
|
110
|
-
(their ``usage`` may carry ``Spread``/``Json`` rather than ``Input``), or
|
|
111
|
-
* ``is_typed_dict_only`` — includes every model in full ``typeddict`` mode (responses too),
|
|
112
|
-
and input-only anonymous bodies, or
|
|
113
|
-
* used as input (``is_usage_input``) — e.g. a model shared between request and response.
|
|
106
|
+
referenced via ``_models.*``, so a model that no operation references through ``types.py``
|
|
107
|
+
would be dead code. TypedDict copies and models in full TypedDict mode volunteer through
|
|
108
|
+
``is_used_in_operations_via_types``.
|
|
114
109
|
|
|
115
110
|
The full set rendered in types.py is the transitive closure of these seeds over base
|
|
116
111
|
classes, discriminated subtypes and property types (see :attr:`_types_file_model_names`), so
|
|
117
112
|
an output-only model that *is* referenced by an input model (e.g. ARM ``SystemData`` on
|
|
118
113
|
``Resource``) is still rendered.
|
|
119
114
|
"""
|
|
120
|
-
return m.
|
|
115
|
+
return m.is_used_in_operations_via_types
|
|
121
116
|
|
|
122
117
|
@staticmethod
|
|
123
118
|
def _iter_referenced_models(base_type: Any):
|
|
@@ -189,9 +184,7 @@ class TypesSerializer(BaseSerializer):
|
|
|
189
184
|
property — are kept, ensuring no forward reference is left undefined.
|
|
190
185
|
"""
|
|
191
186
|
needed = self._types_file_model_names
|
|
192
|
-
candidates = [
|
|
193
|
-
m for m in self._models if m.base != "json" and not m.discriminated_subtypes and m.name in needed
|
|
194
|
-
]
|
|
187
|
+
candidates = [m for m in self._models if m.base != "json" and not m.discriminated_subtypes and m.name in needed]
|
|
195
188
|
seen_names: dict[str, "ModelType"] = {}
|
|
196
189
|
result: list["ModelType"] = []
|
|
197
190
|
for m in candidates:
|
|
@@ -29,7 +29,7 @@ def api_version_validation(**kwargs):
|
|
|
29
29
|
try:
|
|
30
30
|
# this assumes the client has an _api_version attribute
|
|
31
31
|
client = args[0]
|
|
32
|
-
client_api_version = client._config.
|
|
32
|
+
client_api_version = client._config.{{ client_api_version_name }} # pylint: disable=protected-access
|
|
33
33
|
except AttributeError:
|
|
34
34
|
return func(*args, **kwargs)
|
|
35
35
|
|
|
@@ -81,8 +81,17 @@ def add_overload(yaml_data: dict[str, Any], body_type: dict[str, Any], for_flatt
|
|
|
81
81
|
return overload
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
def add_overloads_for_body_param(yaml_data: dict[str, Any]) -> None:
|
|
85
|
-
"""If we added a body parameter type, add overloads for that type
|
|
84
|
+
def add_overloads_for_body_param(yaml_data: dict[str, Any], skip_single_body_json: bool = False) -> None:
|
|
85
|
+
"""If we added a body parameter type, add overloads for that type.
|
|
86
|
+
|
|
87
|
+
``skip_single_body_json`` is the authoritative signal, computed by
|
|
88
|
+
``add_body_param_type``, for whether a TypedDict-style overload was inserted
|
|
89
|
+
to replace the single-body raw-JSON overload on the spread (``base: json``)
|
|
90
|
+
path. It is True for both models-mode: dpg (generate-typeddict on) and
|
|
91
|
+
the TypedDict-only mode (models-mode: none with generate-typeddict on), and
|
|
92
|
+
False when TypedDict generation is disabled (in which case the single-body
|
|
93
|
+
raw-JSON overload is kept, matching pre-TypedDict behavior).
|
|
94
|
+
"""
|
|
86
95
|
body_parameter = yaml_data["bodyParameter"]
|
|
87
96
|
if not (
|
|
88
97
|
body_parameter["type"]["type"] == "combined"
|
|
@@ -94,8 +103,8 @@ def add_overloads_for_body_param(yaml_data: dict[str, Any]) -> None:
|
|
|
94
103
|
continue
|
|
95
104
|
if body_type.get("type") == "model" and body_type.get("base") == "json":
|
|
96
105
|
yaml_data["overloads"].append(add_overload(yaml_data, body_type, for_flatten_params=True))
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
if skip_single_body_json:
|
|
107
|
+
continue
|
|
99
108
|
yaml_data["overloads"].append(add_overload(yaml_data, body_type))
|
|
100
109
|
content_type_param = next(p for p in yaml_data["parameters"] if p["wireName"].lower() == "content-type")
|
|
101
110
|
content_type_param["inOverload"] = False
|
|
@@ -306,6 +315,10 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
306
315
|
def is_tsp(self) -> bool:
|
|
307
316
|
return self.options.get("tsp_file", False)
|
|
308
317
|
|
|
318
|
+
@property
|
|
319
|
+
def generate_typeddict(self) -> bool:
|
|
320
|
+
return self.options.get("generate-typeddict", True)
|
|
321
|
+
|
|
309
322
|
@staticmethod
|
|
310
323
|
def _find_existing_typeddict(
|
|
311
324
|
code_model: dict[str, Any],
|
|
@@ -394,11 +407,39 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
394
407
|
if not existing_td:
|
|
395
408
|
code_model["types"].append(td_elem)
|
|
396
409
|
|
|
410
|
+
@staticmethod
|
|
411
|
+
def _insert_json_overload(
|
|
412
|
+
body_parameter: dict[str, Any],
|
|
413
|
+
origin_type: str,
|
|
414
|
+
) -> None:
|
|
415
|
+
"""Insert a raw-JSON (any-object) type into the body parameter's combined types.
|
|
416
|
+
|
|
417
|
+
This restores the pre-TypedDict dict-body overload used when TypedDict
|
|
418
|
+
generation is disabled.
|
|
419
|
+
"""
|
|
420
|
+
if origin_type == "model":
|
|
421
|
+
body_parameter["type"]["types"].insert(1, KNOWN_TYPES["any-object"])
|
|
422
|
+
else:
|
|
423
|
+
# dict or list: copy the original container type and swap its element
|
|
424
|
+
# type for the raw-JSON any-object.
|
|
425
|
+
any_obj_list_or_dict = copy.deepcopy(body_parameter["type"]["types"][0])
|
|
426
|
+
any_obj_list_or_dict["elementType"] = KNOWN_TYPES["any-object"]
|
|
427
|
+
body_parameter["type"]["types"].insert(1, any_obj_list_or_dict)
|
|
428
|
+
|
|
397
429
|
def add_body_param_type(
|
|
398
430
|
self,
|
|
399
431
|
code_model: dict[str, Any],
|
|
400
432
|
body_parameter: dict[str, Any],
|
|
401
|
-
):
|
|
433
|
+
) -> bool:
|
|
434
|
+
"""Build the combined body-parameter type and its overload variants.
|
|
435
|
+
|
|
436
|
+
Returns whether a TypedDict-style overload was inserted in place of the
|
|
437
|
+
single-body raw-JSON overload on the spread (``base: json``) path. The
|
|
438
|
+
caller passes this to ``add_overloads_for_body_param`` as
|
|
439
|
+
``skip_single_body_json``. It is False when TypedDict generation is
|
|
440
|
+
disabled, so the pre-TypedDict single-body raw-JSON overload is kept.
|
|
441
|
+
"""
|
|
442
|
+
skip_single_body_json = False
|
|
402
443
|
# For a binary `bytes` body (e.g. content type application/octet-stream or a custom
|
|
403
444
|
# binary media type), add an IO overload alongside the `bytes` one. This keeps backward
|
|
404
445
|
# compatibility for services migrating from swagger, whose binary bodies were typed as IO.
|
|
@@ -408,7 +449,7 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
408
449
|
body_parameter
|
|
409
450
|
and body_parameter["type"]["type"] == "bytes"
|
|
410
451
|
and is_binary_body
|
|
411
|
-
and self.options
|
|
452
|
+
and not self.options.generate_typeddict_only
|
|
412
453
|
and not any(t for t in ["flattened", "groupedBy"] if body_parameter.get(t))
|
|
413
454
|
):
|
|
414
455
|
body_parameter["type"] = {
|
|
@@ -416,7 +457,7 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
416
457
|
"types": [body_parameter["type"], KNOWN_TYPES["binary"]],
|
|
417
458
|
}
|
|
418
459
|
code_model["types"].append(body_parameter["type"])
|
|
419
|
-
return
|
|
460
|
+
return skip_single_body_json
|
|
420
461
|
|
|
421
462
|
# only add overload for special content type
|
|
422
463
|
if ( # pylint: disable=too-many-boolean-expressions
|
|
@@ -432,7 +473,7 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
432
473
|
)
|
|
433
474
|
is_dpg_model = model_type.get("base") == "dpg"
|
|
434
475
|
is_json_model = model_type.get("base") == "json"
|
|
435
|
-
is_typeddict_only = self.options
|
|
476
|
+
is_typeddict_only = self.options.generate_typeddict_only
|
|
436
477
|
|
|
437
478
|
body_parameter["type"] = {
|
|
438
479
|
"type": "combined",
|
|
@@ -442,11 +483,16 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
442
483
|
if not (self.is_tsp and has_multi_part_content_type(body_parameter)) and not is_typeddict_only:
|
|
443
484
|
body_parameter["type"]["types"].append(KNOWN_TYPES["binary"])
|
|
444
485
|
|
|
445
|
-
# Add
|
|
486
|
+
# Add the dict-body overload for non-spread dpg models: a TypedDict when
|
|
487
|
+
# enabled, otherwise the raw-JSON overload (pre-TypedDict behavior).
|
|
446
488
|
if self.options["models-mode"] == "dpg" and is_dpg_model:
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
489
|
+
if self.generate_typeddict:
|
|
490
|
+
cross_lang_id = model_type.get("crossLanguageDefinitionId")
|
|
491
|
+
existing_td = self._find_existing_typeddict(code_model, cross_lang_id, model_type.get("name"))
|
|
492
|
+
self._insert_typeddict_overload(code_model, body_parameter, model_type, origin_type, existing_td)
|
|
493
|
+
skip_single_body_json = True
|
|
494
|
+
else:
|
|
495
|
+
self._insert_json_overload(body_parameter, origin_type)
|
|
450
496
|
|
|
451
497
|
# For spread bodies (json base), add a typeddict overload that references
|
|
452
498
|
# the original model. This replaces the JSON single-body overload.
|
|
@@ -456,17 +502,20 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
456
502
|
|
|
457
503
|
if is_typeddict_only and original:
|
|
458
504
|
# In typeddict-only mode, the original dpg model already renders
|
|
459
|
-
# as a TypedDict — reference it directly, no copy needed.
|
|
505
|
+
# as a TypedDict — reference it directly, no copy needed. It also
|
|
506
|
+
# replaces the single-body JSON overload.
|
|
507
|
+
skip_single_body_json = True
|
|
460
508
|
if origin_type == "model":
|
|
461
509
|
body_parameter["type"]["types"].insert(1, original)
|
|
462
510
|
else:
|
|
463
511
|
td_list_or_dict = copy.deepcopy(body_parameter["type"]["types"][0])
|
|
464
512
|
td_list_or_dict["elementType"] = original
|
|
465
513
|
body_parameter["type"]["types"].insert(1, td_list_or_dict)
|
|
466
|
-
|
|
514
|
+
elif self.generate_typeddict:
|
|
467
515
|
source = original or model_type
|
|
468
516
|
existing_td = self._find_existing_typeddict(code_model, cross_lang_id, source.get("name"))
|
|
469
517
|
self._insert_typeddict_overload(code_model, body_parameter, source, origin_type, existing_td)
|
|
518
|
+
skip_single_body_json = True
|
|
470
519
|
|
|
471
520
|
if len(body_parameter["type"]["types"]) == 1:
|
|
472
521
|
# Only one body variant remains (e.g. typeddict-only mode where the
|
|
@@ -474,10 +523,12 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
474
523
|
# wrapper back to the single type so we don't emit a lone
|
|
475
524
|
# ``@overload`` (mypy rejects a single overload definition).
|
|
476
525
|
body_parameter["type"] = body_parameter["type"]["types"][0]
|
|
477
|
-
return
|
|
526
|
+
return skip_single_body_json
|
|
478
527
|
|
|
479
528
|
code_model["types"].append(body_parameter["type"])
|
|
480
529
|
|
|
530
|
+
return skip_single_body_json
|
|
531
|
+
|
|
481
532
|
def pad_reserved_words(self, name: str, pad_type: PadType, yaml_type: dict[str, Any]) -> str:
|
|
482
533
|
# we want to pad hidden variables as well
|
|
483
534
|
if not name:
|
|
@@ -649,8 +700,8 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
649
700
|
response["discriminator"] = "operation"
|
|
650
701
|
if body_parameter and not is_overload:
|
|
651
702
|
# if we have a JSON body, we add a binary overload
|
|
652
|
-
self.add_body_param_type(code_model, body_parameter)
|
|
653
|
-
add_overloads_for_body_param(yaml_data)
|
|
703
|
+
skip_single_body_json = self.add_body_param_type(code_model, body_parameter)
|
|
704
|
+
add_overloads_for_body_param(yaml_data, skip_single_body_json=skip_single_body_json)
|
|
654
705
|
|
|
655
706
|
def _update_lro_operation_helper(self, yaml_data: dict[str, Any]) -> None:
|
|
656
707
|
for response in yaml_data.get("responses", []):
|
|
@@ -28,6 +28,21 @@ def description_ends_with_code_block(description: str) -> bool:
|
|
|
28
28
|
return all(not line.strip() or line.startswith((" ", "\t")) for line in lines[directives[-1] + 1 :])
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def is_typeddict_only(options: Any) -> bool:
|
|
32
|
+
"""Whether generation is TypedDict-only.
|
|
33
|
+
|
|
34
|
+
True for TypeSpec input where no concrete models mode is selected (``models-mode: none``,
|
|
35
|
+
normalized to a falsy value by :class:`OptionsDict`) and ``generate-typeddict`` is enabled.
|
|
36
|
+
Swagger ``models-mode: none`` is left as a plain no-models mode because it never sets
|
|
37
|
+
``tsp_file``.
|
|
38
|
+
"""
|
|
39
|
+
return (
|
|
40
|
+
bool(options.get("tsp_file"))
|
|
41
|
+
and not options.get("models-mode")
|
|
42
|
+
and bool(options.get("generate-typeddict", True))
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
31
46
|
def update_enum_value(name: str, value: Any, description: str, enum_type: dict[str, Any]) -> dict[str, Any]:
|
|
32
47
|
return {
|
|
33
48
|
"name": name,
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1",
|
|
3
|
-
"pip_version": "26.1
|
|
3
|
+
"pip_version": "26.2.1",
|
|
4
4
|
"install": [
|
|
5
5
|
{
|
|
6
6
|
"download_info": {
|
|
7
|
-
"url": "https://files.pythonhosted.org/packages/
|
|
7
|
+
"url": "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl",
|
|
8
8
|
"archive_info": {
|
|
9
9
|
"hashes": {
|
|
10
|
-
"sha256": "
|
|
10
|
+
"sha256": "51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670"
|
|
11
11
|
},
|
|
12
|
-
"hash": "sha256=
|
|
12
|
+
"hash": "sha256=51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"is_direct": false,
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"metadata": {
|
|
19
19
|
"metadata_version": "2.4",
|
|
20
20
|
"name": "setuptools",
|
|
21
|
-
"version": "
|
|
21
|
+
"version": "84.0.0",
|
|
22
22
|
"dynamic": [
|
|
23
23
|
"license-file"
|
|
24
24
|
],
|
|
@@ -125,9 +125,9 @@
|
|
|
125
125
|
"implementation_version": "3.11.15",
|
|
126
126
|
"os_name": "posix",
|
|
127
127
|
"platform_machine": "x86_64",
|
|
128
|
-
"platform_release": "6.17.0-
|
|
128
|
+
"platform_release": "6.17.0-1020-azure",
|
|
129
129
|
"platform_system": "Linux",
|
|
130
|
-
"platform_version": "#
|
|
130
|
+
"platform_version": "#20~24.04.1-Ubuntu SMP Fri Jun 19 20:09:14 UTC 2026",
|
|
131
131
|
"python_full_version": "3.11.15",
|
|
132
132
|
"platform_python_implementation": "CPython",
|
|
133
133
|
"python_version": "3.11",
|
|
Binary file
|
|
@@ -12,7 +12,7 @@ from abc import ABC, abstractmethod
|
|
|
12
12
|
from typing import Any, Iterator, Optional, Union
|
|
13
13
|
|
|
14
14
|
import yaml
|
|
15
|
-
from .utils import TYPESPEC_PACKAGE_MODE, VALID_PACKAGE_MODE
|
|
15
|
+
from .utils import TYPESPEC_PACKAGE_MODE, VALID_PACKAGE_MODE, is_typeddict_only
|
|
16
16
|
|
|
17
17
|
from ._version import VERSION
|
|
18
18
|
|
|
@@ -36,6 +36,7 @@ class OptionsDict(MutableMapping):
|
|
|
36
36
|
"low-level-client": False,
|
|
37
37
|
"no-async": False,
|
|
38
38
|
"no-namespace-folders": False,
|
|
39
|
+
"generate-typeddict": True,
|
|
39
40
|
"polymorphic-examples": 5,
|
|
40
41
|
"validate-versioning": True,
|
|
41
42
|
"version-tolerant": True,
|
|
@@ -45,10 +46,20 @@ class OptionsDict(MutableMapping):
|
|
|
45
46
|
|
|
46
47
|
def __init__(self, options: Optional[dict[str, Any]] = None) -> None:
|
|
47
48
|
self._data = options.copy() if options else {}
|
|
49
|
+
# 'models-mode: typeddict' is deprecated. Represent it internally as 'none' models-mode with
|
|
50
|
+
# TypedDict generation enabled, so the rest of the codebase reasons in terms of models-mode
|
|
51
|
+
# plus the generate-typeddict flag.
|
|
52
|
+
if self._data.get("models-mode") == "typeddict":
|
|
53
|
+
self._data["generate-typeddict"] = True
|
|
48
54
|
for key in list(self._data):
|
|
49
55
|
self._data[key] = self._validate_and_transform(key, self._data[key])
|
|
50
56
|
self._validate_combinations()
|
|
51
57
|
|
|
58
|
+
@property
|
|
59
|
+
def generate_typeddict_only(self) -> bool:
|
|
60
|
+
"""Whether this is TypedDict-only generation ('models-mode: none' + TypedDicts)."""
|
|
61
|
+
return is_typeddict_only(self)
|
|
62
|
+
|
|
52
63
|
def __getitem__(self, key: str) -> Any: # pylint: disable=too-many-return-statements
|
|
53
64
|
if key == "head-as-boolean" and self.get("azure-arm"):
|
|
54
65
|
# override to always true if azure-arm is set
|
|
@@ -167,14 +178,22 @@ class OptionsDict(MutableMapping):
|
|
|
167
178
|
if key == "builders-visibility" and value not in ["public", "hidden", "embedded"]:
|
|
168
179
|
raise ValueError("The value of --builders-visibility must be either 'public', 'hidden', or 'embedded'")
|
|
169
180
|
|
|
181
|
+
if key == "models-mode" and value == "typeddict":
|
|
182
|
+
# Deprecated: keep accepting it for back-compat but store it as 'none' (falsy) with
|
|
183
|
+
# TypedDict generation enabled (see OptionsDict.__init__ and generate_typeddict_only).
|
|
184
|
+
_LOGGER.warning(
|
|
185
|
+
"'models-mode: typeddict' is deprecated. Use 'models-mode: none' with "
|
|
186
|
+
"'generate-typeddict: true' (the default) instead."
|
|
187
|
+
)
|
|
188
|
+
value = False
|
|
189
|
+
|
|
170
190
|
if key == "models-mode" and value == "none":
|
|
171
191
|
value = False # switch to falsy value for easier code writing
|
|
172
192
|
|
|
173
|
-
if key == "models-mode" and value not in ["msrest", "dpg",
|
|
193
|
+
if key == "models-mode" and value not in ["msrest", "dpg", False]:
|
|
174
194
|
raise ValueError(
|
|
175
|
-
"--models-mode can only be '
|
|
176
|
-
"Pass in '
|
|
177
|
-
"'none' if you don't want any."
|
|
195
|
+
"--models-mode can only be 'dpg' or 'none'. "
|
|
196
|
+
"Pass in 'dpg' for DPG models, or 'none' if you don't want any."
|
|
178
197
|
)
|
|
179
198
|
if key == "package-mode":
|
|
180
199
|
if (
|