@typespec/http-client-python 0.35.1 → 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 +6 -2
- package/eng/scripts/ci/regenerate.ts +1 -2
- 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 +25 -14
- package/generator/build/lib/pygen/codegen/serializers/client_serializer.py +13 -6
- package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +4 -1
- package/generator/build/lib/pygen/codegen/serializers/types_serializer.py +4 -9
- 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 +5 -5
- 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 +25 -14
- package/generator/pygen/codegen/serializers/client_serializer.py +13 -6
- package/generator/pygen/codegen/serializers/model_serializer.py +4 -1
- package/generator/pygen/codegen/serializers/types_serializer.py +4 -9
- 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 +33 -33
|
@@ -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):
|
|
@@ -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.2",
|
|
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
|
],
|
|
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 (
|
|
@@ -169,7 +169,9 @@ def build_type(yaml_data: dict[str, Any], code_model: CodeModel) -> BaseType:
|
|
|
169
169
|
# need to special case model to avoid recursion
|
|
170
170
|
if yaml_data["base"] == "typeddict":
|
|
171
171
|
model_type = TypedDictModelType # type: ignore
|
|
172
|
-
elif yaml_data["base"] == "json" or
|
|
172
|
+
elif yaml_data["base"] == "json" or (
|
|
173
|
+
not code_model.options["models-mode"] and not code_model.generate_typeddict_only
|
|
174
|
+
):
|
|
173
175
|
model_type = JSONModelType
|
|
174
176
|
elif yaml_data["base"] == "dpg":
|
|
175
177
|
model_type = DPGModelType # type: ignore
|
|
@@ -17,6 +17,7 @@ from .operation_group import OperationGroup
|
|
|
17
17
|
from .utils import NamespaceType
|
|
18
18
|
from .._utils import DEFAULT_HEADER_TEXT, DEFAULT_LICENSE_DESCRIPTION
|
|
19
19
|
from ... import OptionsDict
|
|
20
|
+
from ...utils import is_typeddict_only
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _is_legacy(options) -> bool:
|
|
@@ -89,7 +90,7 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
|
|
|
89
90
|
self.clients: list[Client] = [
|
|
90
91
|
Client.from_yaml(client_yaml_data, self) for client_yaml_data in yaml_data["clients"]
|
|
91
92
|
]
|
|
92
|
-
if self.options["models-mode"] and self.model_types:
|
|
93
|
+
if (self.options["models-mode"] or self.generate_typeddict_only) and self.model_types:
|
|
93
94
|
self.sort_model_types()
|
|
94
95
|
self.named_unions: list[CombinedType] = [
|
|
95
96
|
t for t in self.types_map.values() if isinstance(t, CombinedType) and t.name
|
|
@@ -174,6 +175,11 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
|
|
|
174
175
|
def get_unique_types_alias(self, serialize_namespace: str, imported_namespace: str) -> str:
|
|
175
176
|
return self._get_unique_import_alias(serialize_namespace, imported_namespace, "types")
|
|
176
177
|
|
|
178
|
+
@property
|
|
179
|
+
def generate_typeddict_only(self) -> bool:
|
|
180
|
+
"""Whether this is TypedDict-only generation ('models-mode: none' + TypedDicts)."""
|
|
181
|
+
return is_typeddict_only(self.options)
|
|
182
|
+
|
|
177
183
|
@property
|
|
178
184
|
def client_namespace_types(self) -> dict[str, ClientNamespaceType]:
|
|
179
185
|
if not self._client_namespace_types:
|
|
@@ -40,7 +40,7 @@ class EnumValue(BaseType):
|
|
|
40
40
|
|
|
41
41
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
42
42
|
"""The python type used for type annotation"""
|
|
43
|
-
if self.code_model.
|
|
43
|
+
if self.code_model.generate_typeddict_only:
|
|
44
44
|
# A single constant enum value must be
|
|
45
45
|
# annotated with its literal value directly (e.g. ``Literal["red"]``).
|
|
46
46
|
return f"Literal[{self.value_type.get_declaration(self.value)}]"
|
|
@@ -83,7 +83,7 @@ class EnumValue(BaseType):
|
|
|
83
83
|
file_import = FileImport(self.code_model)
|
|
84
84
|
file_import.merge(self.value_type.imports(**kwargs))
|
|
85
85
|
file_import.add_submodule_import("typing", "Literal", ImportType.STDLIB, TypingSection.REGULAR)
|
|
86
|
-
if self.code_model.
|
|
86
|
+
if self.code_model.generate_typeddict_only:
|
|
87
87
|
# In typeddict mode the enums module (``_enums.py``) is never generated
|
|
88
88
|
return file_import
|
|
89
89
|
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
@@ -176,7 +176,7 @@ class EnumType(BaseType):
|
|
|
176
176
|
|
|
177
177
|
@property
|
|
178
178
|
def is_typeddict_mode(self) -> bool:
|
|
179
|
-
return self.code_model.
|
|
179
|
+
return self.code_model.generate_typeddict_only
|
|
180
180
|
|
|
181
181
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
182
182
|
"""The python type used for type annotation
|
|
@@ -184,7 +184,7 @@ class EnumType(BaseType):
|
|
|
184
184
|
:return: The type annotation for this schema
|
|
185
185
|
:rtype: str
|
|
186
186
|
"""
|
|
187
|
-
if self.code_model.options["models-mode"]:
|
|
187
|
+
if self.code_model.options["models-mode"] or self.code_model.generate_typeddict_only:
|
|
188
188
|
if self.is_typeddict_mode:
|
|
189
189
|
# In typeddict mode, enums are Literal aliases defined in types.py
|
|
190
190
|
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
@@ -221,13 +221,13 @@ class EnumType(BaseType):
|
|
|
221
221
|
return self.value_type.get_declaration(value)
|
|
222
222
|
|
|
223
223
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
224
|
-
if self.code_model.options["models-mode"]:
|
|
224
|
+
if self.code_model.options["models-mode"] or self.code_model.generate_typeddict_only:
|
|
225
225
|
return self.name
|
|
226
226
|
return self.value_type.type_annotation(**kwargs)
|
|
227
227
|
|
|
228
228
|
def docstring_type(self, **kwargs: Any) -> str:
|
|
229
229
|
"""The python type used for RST syntax input and type annotation."""
|
|
230
|
-
if self.code_model.options["models-mode"]:
|
|
230
|
+
if self.code_model.options["models-mode"] or self.code_model.generate_typeddict_only:
|
|
231
231
|
type_annotation = self.value_type.type_annotation(**kwargs)
|
|
232
232
|
enum_type_annotation = f"{self.client_namespace}.models.{self.name}"
|
|
233
233
|
return f"{type_annotation} or ~{enum_type_annotation}"
|
|
@@ -261,7 +261,7 @@ class EnumType(BaseType):
|
|
|
261
261
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
262
262
|
file_import = FileImport(self.code_model)
|
|
263
263
|
file_import.merge(self.value_type.imports(**kwargs))
|
|
264
|
-
if self.code_model.options["models-mode"]:
|
|
264
|
+
if self.code_model.options["models-mode"] or self.code_model.generate_typeddict_only:
|
|
265
265
|
if self.is_typeddict_mode:
|
|
266
266
|
# In typeddict mode, enums are Literal aliases in types.py — no Union needed
|
|
267
267
|
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
@@ -38,6 +38,7 @@ class ListType(BaseType):
|
|
|
38
38
|
self.code_model.options["version-tolerant"]
|
|
39
39
|
and self.element_type.is_xml
|
|
40
40
|
and not self.code_model.options["models-mode"]
|
|
41
|
+
and not self.code_model.generate_typeddict_only
|
|
41
42
|
):
|
|
42
43
|
# this means we're version tolerant XML, we just return the XML element
|
|
43
44
|
return self.element_type.type_annotation(**kwargs)
|
|
@@ -77,9 +77,7 @@ class ModelType(BaseType): # pylint: disable=too-many-instance-attributes, too-
|
|
|
77
77
|
self.cross_language_definition_id: Optional[str] = self.yaml_data.get("crossLanguageDefinitionId")
|
|
78
78
|
self.usage: int = self.yaml_data.get("usage", UsageFlags.Input.value | UsageFlags.Output.value)
|
|
79
79
|
self.client_namespace: str = self.yaml_data.get("clientNamespace", code_model.namespace)
|
|
80
|
-
self.is_typed_dict_only: bool = (
|
|
81
|
-
self.yaml_data.get("typedDictOnly", False) or code_model.options["models-mode"] == "typeddict"
|
|
82
|
-
)
|
|
80
|
+
self.is_typed_dict_only: bool = self.yaml_data.get("typedDictOnly", False) or code_model.generate_typeddict_only
|
|
83
81
|
|
|
84
82
|
@property
|
|
85
83
|
def is_usage_output(self) -> bool:
|
|
@@ -334,7 +334,7 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
334
334
|
file_import.merge(
|
|
335
335
|
response.imports(async_mode=async_mode, need_import_iobase=self.need_import_iobase, **kwargs)
|
|
336
336
|
)
|
|
337
|
-
if self.code_model.options["models-mode"]:
|
|
337
|
+
if self.code_model.options["models-mode"] or self.code_model.generate_typeddict_only:
|
|
338
338
|
for exception in self.exceptions:
|
|
339
339
|
file_import.merge(exception.imports(async_mode=async_mode, **kwargs))
|
|
340
340
|
|
|
@@ -552,7 +552,12 @@ class Operation(OperationBase[Response]):
|
|
|
552
552
|
"distributed_trace_async",
|
|
553
553
|
ImportType.SDKCORE,
|
|
554
554
|
)
|
|
555
|
-
if
|
|
555
|
+
if (
|
|
556
|
+
self.has_response_body
|
|
557
|
+
and not self.has_optional_return_type
|
|
558
|
+
and not self.code_model.options["models-mode"]
|
|
559
|
+
and not self.code_model.generate_typeddict_only
|
|
560
|
+
):
|
|
556
561
|
file_import.add_submodule_import("typing", "cast", ImportType.STDLIB)
|
|
557
562
|
|
|
558
563
|
return file_import
|
|
@@ -338,7 +338,9 @@ class Parameter(_ParameterBase):
|
|
|
338
338
|
) -> ParameterMethodLocation:
|
|
339
339
|
if not self.in_method_signature:
|
|
340
340
|
raise ValueError(f"Parameter '{self.client_name}' is not in the method.")
|
|
341
|
-
if
|
|
341
|
+
if (
|
|
342
|
+
self.code_model.options["models-mode"] == "dpg" or self.code_model.generate_typeddict_only
|
|
343
|
+
) and self.in_flattened_body:
|
|
342
344
|
return ParameterMethodLocation.KEYWORD_ONLY
|
|
343
345
|
if self.grouper:
|
|
344
346
|
return ParameterMethodLocation.POSITIONAL
|
|
@@ -34,6 +34,20 @@ def add_to_pylint_disable(curr_str: str, entry: str) -> str:
|
|
|
34
34
|
return f" # pylint: disable={entry}"
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
def escape_sphinx_field_name(name: str) -> str:
|
|
38
|
+
"""Escape a name used as the target of a Sphinx info field (``:ivar``, ``:vartype``,
|
|
39
|
+
``:keyword``, ``:paramtype``, ``:param``, ``:type``).
|
|
40
|
+
|
|
41
|
+
A name containing ``@`` (e.g. a wire name such as ``@search.facets``) is wrapped in
|
|
42
|
+
double backticks so it is treated as an inline literal. This renders cleanly in Sphinx
|
|
43
|
+
without a leading ``@`` being misinterpreted, and — unlike a ``\\@`` escape — introduces
|
|
44
|
+
no invalid escape sequence into the generated Python docstring.
|
|
45
|
+
"""
|
|
46
|
+
if "@" in name:
|
|
47
|
+
return f"``{name}``"
|
|
48
|
+
return name
|
|
49
|
+
|
|
50
|
+
|
|
37
51
|
class NamespaceType(str, Enum):
|
|
38
52
|
"""Special signal for impports"""
|
|
39
53
|
|
|
@@ -207,10 +207,10 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
207
207
|
self._serialize_and_write_top_level_folder(env=env, namespace=client_namespace)
|
|
208
208
|
|
|
209
209
|
# add models folder if there are models in this namespace
|
|
210
|
-
is_typeddict_mode = self.code_model.
|
|
211
|
-
if (
|
|
212
|
-
self.code_model.
|
|
213
|
-
)
|
|
210
|
+
is_typeddict_mode = self.code_model.generate_typeddict_only
|
|
211
|
+
if (self.code_model.has_non_json_models(client_namespace_type.models) or client_namespace_type.enums) and (
|
|
212
|
+
self.code_model.options["models-mode"] or self.code_model.generate_typeddict_only
|
|
213
|
+
):
|
|
214
214
|
self._serialize_and_write_models_folder(
|
|
215
215
|
env=env,
|
|
216
216
|
namespace=client_namespace,
|
|
@@ -238,7 +238,7 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
238
238
|
).serialize(),
|
|
239
239
|
)
|
|
240
240
|
|
|
241
|
-
if not self.code_model.options["models-mode"]:
|
|
241
|
+
if not self.code_model.options["models-mode"] and not self.code_model.generate_typeddict_only:
|
|
242
242
|
# keep models file if users ended up just writing a models file
|
|
243
243
|
model_path = generation_path / Path("models.py")
|
|
244
244
|
if self.read_file(model_path):
|
|
@@ -321,7 +321,7 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
321
321
|
# Write the models folder
|
|
322
322
|
models_path = self.code_model.get_generation_dir(namespace) / "models"
|
|
323
323
|
models_mode = self.code_model.options["models-mode"]
|
|
324
|
-
if models_mode
|
|
324
|
+
if models_mode == "dpg" or self.code_model.generate_typeddict_only:
|
|
325
325
|
serializer = DpgModelSerializer
|
|
326
326
|
else:
|
|
327
327
|
serializer = MsrestModelSerializer
|