@typespec/http-client-python 0.32.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/emitter.js +10 -2
- package/dist/emitter/emitter.js.map +1 -1
- package/dist/emitter/lib.d.ts +6 -0
- package/dist/emitter/lib.d.ts.map +1 -1
- package/dist/emitter/lib.js +29 -0
- package/dist/emitter/lib.js.map +1 -1
- 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/emitter.ts +9 -1
- package/emitter/src/lib.ts +36 -0
- 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 +4 -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 +12 -10
- 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/general_serializer.py +32 -2
- 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/packaging_templates/pyproject.toml.jinja2 +30 -2
- 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 +104 -13
- package/generator/build/lib/pygen/utils.py +16 -0
- package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
- package/generator/pygen/__init__.py +4 -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 +12 -10
- package/generator/pygen/codegen/serializers/__init__.py +46 -11
- package/generator/pygen/codegen/serializers/builder_serializer.py +48 -15
- package/generator/pygen/codegen/serializers/general_serializer.py +32 -2
- 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/packaging_templates/pyproject.toml.jinja2 +30 -2
- 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 +104 -13
- package/generator/pygen/utils.py +16 -0
- package/generator/pygen.egg-info/SOURCES.txt +2 -0
- package/package.json +5 -5
|
@@ -22,6 +22,7 @@ from ..utils import (
|
|
|
22
22
|
get_body_type_for_description,
|
|
23
23
|
JSON_REGEXP,
|
|
24
24
|
KNOWN_TYPES,
|
|
25
|
+
description_ends_with_code_block,
|
|
25
26
|
)
|
|
26
27
|
|
|
27
28
|
|
|
@@ -93,6 +94,8 @@ def add_overloads_for_body_param(yaml_data: dict[str, Any]) -> None:
|
|
|
93
94
|
continue
|
|
94
95
|
if body_type.get("type") == "model" and body_type.get("base") == "json":
|
|
95
96
|
yaml_data["overloads"].append(add_overload(yaml_data, body_type, for_flatten_params=True))
|
|
97
|
+
# Skip single-body JSON overload; the TypedDict overload replaces it
|
|
98
|
+
continue
|
|
96
99
|
yaml_data["overloads"].append(add_overload(yaml_data, body_type))
|
|
97
100
|
content_type_param = next(p for p in yaml_data["parameters"] if p["wireName"].lower() == "content-type")
|
|
98
101
|
content_type_param["inOverload"] = False
|
|
@@ -108,7 +111,9 @@ def update_description(description: Optional[str], default_description: str = ""
|
|
|
108
111
|
if not description:
|
|
109
112
|
description = default_description
|
|
110
113
|
description.rstrip(" ")
|
|
111
|
-
|
|
114
|
+
# Don't append a trailing period when the description ends with a code block: the
|
|
115
|
+
# period would land inside the rendered literal block (e.g. "]." ) and break Sphinx.
|
|
116
|
+
if description and description[-1] != "." and not description_ends_with_code_block(description):
|
|
112
117
|
description += "."
|
|
113
118
|
return description
|
|
114
119
|
|
|
@@ -301,6 +306,44 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
301
306
|
def is_tsp(self) -> bool:
|
|
302
307
|
return self.options.get("tsp_file", False)
|
|
303
308
|
|
|
309
|
+
@staticmethod
|
|
310
|
+
def _find_existing_typeddict(code_model: dict[str, Any], cross_lang_id: Optional[str]) -> Optional[dict[str, Any]]:
|
|
311
|
+
"""Find an existing typeddict copy with the given crossLanguageDefinitionId."""
|
|
312
|
+
if not cross_lang_id:
|
|
313
|
+
return None
|
|
314
|
+
return next(
|
|
315
|
+
(
|
|
316
|
+
t
|
|
317
|
+
for t in code_model["types"]
|
|
318
|
+
if t.get("type") == "model"
|
|
319
|
+
and t.get("base") == "typeddict"
|
|
320
|
+
and t.get("crossLanguageDefinitionId") == cross_lang_id
|
|
321
|
+
),
|
|
322
|
+
None,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
@staticmethod
|
|
326
|
+
def _insert_typeddict_overload(
|
|
327
|
+
code_model: dict[str, Any],
|
|
328
|
+
body_parameter: dict[str, Any],
|
|
329
|
+
source: dict[str, Any],
|
|
330
|
+
origin_type: str,
|
|
331
|
+
existing_td: Optional[dict[str, Any]],
|
|
332
|
+
) -> None:
|
|
333
|
+
"""Insert a typeddict type into the body parameter's combined types."""
|
|
334
|
+
if origin_type == "model":
|
|
335
|
+
td_type = existing_td or {**source, "base": "typeddict"}
|
|
336
|
+
body_parameter["type"]["types"].insert(1, td_type)
|
|
337
|
+
if not existing_td:
|
|
338
|
+
code_model["types"].append(td_type)
|
|
339
|
+
else:
|
|
340
|
+
td_list_or_dict = copy.deepcopy(body_parameter["type"]["types"][0])
|
|
341
|
+
td_elem = existing_td or {**source, "base": "typeddict"}
|
|
342
|
+
td_list_or_dict["elementType"] = td_elem
|
|
343
|
+
body_parameter["type"]["types"].insert(1, td_list_or_dict)
|
|
344
|
+
if not existing_td:
|
|
345
|
+
code_model["types"].append(td_elem)
|
|
346
|
+
|
|
304
347
|
def add_body_param_type(
|
|
305
348
|
self,
|
|
306
349
|
code_model: dict[str, Any],
|
|
@@ -319,25 +362,65 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
319
362
|
body_parameter["type"] if origin_type == "model" else body_parameter["type"].get("elementType", {})
|
|
320
363
|
)
|
|
321
364
|
is_dpg_model = model_type.get("base") == "dpg"
|
|
365
|
+
is_json_model = model_type.get("base") == "json"
|
|
366
|
+
is_typeddict_only = self.options["models-mode"] == "typeddict"
|
|
367
|
+
|
|
322
368
|
body_parameter["type"] = {
|
|
323
369
|
"type": "combined",
|
|
324
370
|
"types": [body_parameter["type"]],
|
|
325
371
|
}
|
|
326
|
-
# don't add binary overload for multipart content type
|
|
327
|
-
if not (self.is_tsp and has_multi_part_content_type(body_parameter)):
|
|
372
|
+
# don't add binary overload for multipart content type or typeddict-only mode
|
|
373
|
+
if not (self.is_tsp and has_multi_part_content_type(body_parameter)) and not is_typeddict_only:
|
|
328
374
|
body_parameter["type"]["types"].append(KNOWN_TYPES["binary"])
|
|
329
375
|
|
|
376
|
+
# Add typeddict overload for non-spread dpg models
|
|
330
377
|
if self.options["models-mode"] == "dpg" and is_dpg_model:
|
|
331
|
-
|
|
332
|
-
|
|
378
|
+
cross_lang_id = model_type.get("crossLanguageDefinitionId")
|
|
379
|
+
existing_td = self._find_existing_typeddict(code_model, cross_lang_id)
|
|
380
|
+
self._insert_typeddict_overload(code_model, body_parameter, model_type, origin_type, existing_td)
|
|
381
|
+
|
|
382
|
+
# For spread bodies (json base), add a typeddict overload that references
|
|
383
|
+
# the original model. This replaces the JSON single-body overload.
|
|
384
|
+
if is_json_model:
|
|
385
|
+
cross_lang_id = model_type.get("crossLanguageDefinitionId")
|
|
386
|
+
original = None
|
|
387
|
+
if cross_lang_id:
|
|
388
|
+
original = next(
|
|
389
|
+
(
|
|
390
|
+
t
|
|
391
|
+
for t in code_model["types"]
|
|
392
|
+
if t.get("type") == "model"
|
|
393
|
+
and t.get("crossLanguageDefinitionId") == cross_lang_id
|
|
394
|
+
and t is not model_type
|
|
395
|
+
),
|
|
396
|
+
None,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
if is_typeddict_only and original:
|
|
400
|
+
# In typeddict-only mode, the original dpg model already renders
|
|
401
|
+
# as a TypedDict — reference it directly, no copy needed.
|
|
402
|
+
if origin_type == "model":
|
|
403
|
+
body_parameter["type"]["types"].insert(1, original)
|
|
404
|
+
else:
|
|
405
|
+
td_list_or_dict = copy.deepcopy(body_parameter["type"]["types"][0])
|
|
406
|
+
td_list_or_dict["elementType"] = original
|
|
407
|
+
body_parameter["type"]["types"].insert(1, td_list_or_dict)
|
|
333
408
|
else:
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
409
|
+
source = original or model_type
|
|
410
|
+
existing_td = self._find_existing_typeddict(code_model, cross_lang_id)
|
|
411
|
+
self._insert_typeddict_overload(code_model, body_parameter, source, origin_type, existing_td)
|
|
412
|
+
|
|
413
|
+
if len(body_parameter["type"]["types"]) == 1:
|
|
414
|
+
# Only one body variant remains (e.g. typeddict-only mode where the
|
|
415
|
+
# binary and JSON overloads are omitted). Collapse the combined
|
|
416
|
+
# wrapper back to the single type so we don't emit a lone
|
|
417
|
+
# ``@overload`` (mypy rejects a single overload definition).
|
|
418
|
+
body_parameter["type"] = body_parameter["type"]["types"][0]
|
|
419
|
+
return
|
|
420
|
+
|
|
339
421
|
code_model["types"].append(body_parameter["type"])
|
|
340
422
|
|
|
423
|
+
|
|
341
424
|
def pad_reserved_words(self, name: str, pad_type: PadType, yaml_type: dict[str, Any]) -> str:
|
|
342
425
|
# we want to pad hidden variables as well
|
|
343
426
|
if not name:
|
|
@@ -485,7 +568,11 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
485
568
|
if yaml_data.get("isLroInitialOperation") is True:
|
|
486
569
|
yaml_data["name"] = (
|
|
487
570
|
"_"
|
|
488
|
-
+ self.pad_reserved_words(
|
|
571
|
+
+ self.pad_reserved_words(
|
|
572
|
+
extract_original_name(yaml_data["name"]),
|
|
573
|
+
PadType.METHOD,
|
|
574
|
+
yaml_data,
|
|
575
|
+
)
|
|
489
576
|
+ "_initial"
|
|
490
577
|
)
|
|
491
578
|
else:
|
|
@@ -576,7 +663,9 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
576
663
|
item_type = item_type or yaml_data["itemType"]["elementType"]
|
|
577
664
|
if yaml_data.get("nextOperation"):
|
|
578
665
|
yaml_data["nextOperation"]["groupName"] = self.pad_reserved_words(
|
|
579
|
-
yaml_data["nextOperation"]["groupName"],
|
|
666
|
+
yaml_data["nextOperation"]["groupName"],
|
|
667
|
+
PadType.OPERATION_GROUP,
|
|
668
|
+
yaml_data["nextOperation"],
|
|
580
669
|
)
|
|
581
670
|
yaml_data["nextOperation"]["groupName"] = to_snake_case(yaml_data["nextOperation"]["groupName"])
|
|
582
671
|
for response in yaml_data["nextOperation"].get("responses", []):
|
|
@@ -598,7 +687,9 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
598
687
|
)
|
|
599
688
|
operation_group["identifyName"] = to_snake_case(operation_group["identifyName"])
|
|
600
689
|
operation_group["propertyName"] = self.pad_reserved_words(
|
|
601
|
-
operation_group["propertyName"],
|
|
690
|
+
operation_group["propertyName"],
|
|
691
|
+
PadType.OPERATION_GROUP,
|
|
692
|
+
operation_group,
|
|
602
693
|
)
|
|
603
694
|
operation_group["propertyName"] = to_snake_case(operation_group["propertyName"])
|
|
604
695
|
operation_group["className"] = update_operation_group_class_name(
|
|
@@ -11,6 +11,22 @@ SWAGGER_PACKAGE_MODE = ["mgmtplane", "dataplane"] # for backward compatibility
|
|
|
11
11
|
TYPESPEC_PACKAGE_MODE = ["azure-mgmt", "azure-dataplane", "generic"]
|
|
12
12
|
VALID_PACKAGE_MODE = SWAGGER_PACKAGE_MODE + TYPESPEC_PACKAGE_MODE
|
|
13
13
|
|
|
14
|
+
CODE_BLOCK_MARKER = ".. code-block::"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def description_ends_with_code_block(description: str) -> bool:
|
|
18
|
+
"""Return True when the description's trailing content is an RST ``.. code-block::``.
|
|
19
|
+
|
|
20
|
+
True when the last ``.. code-block::`` directive (starting its own line, so inline
|
|
21
|
+
mentions are ignored) is followed only by blank or indented lines, i.e. the literal
|
|
22
|
+
block runs to the end of the description.
|
|
23
|
+
"""
|
|
24
|
+
lines = description.rstrip().splitlines()
|
|
25
|
+
directives = [i for i, line in enumerate(lines) if line.lstrip().startswith(CODE_BLOCK_MARKER)]
|
|
26
|
+
if not directives:
|
|
27
|
+
return False
|
|
28
|
+
return all(not line.strip() or line.startswith((" ", "\t")) for line in lines[directives[-1] + 1 :])
|
|
29
|
+
|
|
14
30
|
|
|
15
31
|
def update_enum_value(name: str, value: Any, description: str, enum_type: dict[str, Any]) -> dict[str, Any]:
|
|
16
32
|
return {
|
|
Binary file
|
|
@@ -29,6 +29,7 @@ class OptionsDict(MutableMapping):
|
|
|
29
29
|
"from-typespec": False,
|
|
30
30
|
"generate-sample": False,
|
|
31
31
|
"keep-setup-py": False,
|
|
32
|
+
"keep-pyproject-fields": "",
|
|
32
33
|
"generate-test": False,
|
|
33
34
|
"head-as-boolean": True,
|
|
34
35
|
"keep-version-file": False,
|
|
@@ -167,10 +168,10 @@ class OptionsDict(MutableMapping):
|
|
|
167
168
|
if key == "models-mode" and value == "none":
|
|
168
169
|
value = False # switch to falsy value for easier code writing
|
|
169
170
|
|
|
170
|
-
if key == "models-mode" and value not in ["msrest", "dpg", False]:
|
|
171
|
+
if key == "models-mode" and value not in ["msrest", "dpg", "typeddict", False]:
|
|
171
172
|
raise ValueError(
|
|
172
|
-
"--models-mode can only be 'msrest', 'dpg' or 'none'. "
|
|
173
|
-
"Pass in 'msrest' if you want msrest models, or "
|
|
173
|
+
"--models-mode can only be 'msrest', 'dpg', 'typeddict', or 'none'. "
|
|
174
|
+
"Pass in 'msrest' if you want msrest models, 'typeddict' for TypedDict models, or "
|
|
174
175
|
"'none' if you don't want any."
|
|
175
176
|
)
|
|
176
177
|
if key == "package-mode":
|
|
@@ -9,7 +9,7 @@ from .base import BaseModel
|
|
|
9
9
|
from .base_builder import BaseBuilder, ParameterListType
|
|
10
10
|
from .code_model import CodeModel
|
|
11
11
|
from .client import Client
|
|
12
|
-
from .model_type import ModelType, JSONModelType, DPGModelType, MsrestModelType
|
|
12
|
+
from .model_type import ModelType, JSONModelType, DPGModelType, MsrestModelType, TypedDictModelType
|
|
13
13
|
from .dictionary_type import DictionaryType
|
|
14
14
|
from .list_type import ListType
|
|
15
15
|
from .combined_type import CombinedType
|
|
@@ -167,7 +167,9 @@ def build_type(yaml_data: dict[str, Any], code_model: CodeModel) -> BaseType:
|
|
|
167
167
|
response: Optional[BaseType] = None
|
|
168
168
|
if yaml_data["type"] == "model":
|
|
169
169
|
# need to special case model to avoid recursion
|
|
170
|
-
if yaml_data["base"] == "
|
|
170
|
+
if yaml_data["base"] == "typeddict":
|
|
171
|
+
model_type = TypedDictModelType # type: ignore
|
|
172
|
+
elif yaml_data["base"] == "json" or not code_model.options["models-mode"]:
|
|
171
173
|
model_type = JSONModelType
|
|
172
174
|
elif yaml_data["base"] == "dpg":
|
|
173
175
|
model_type = DPGModelType # type: ignore
|
|
@@ -152,16 +152,28 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
|
|
|
152
152
|
return result
|
|
153
153
|
return f"{result}{module_name}" if result.endswith(".") else f"{result}.{module_name}"
|
|
154
154
|
|
|
155
|
-
def
|
|
155
|
+
def _get_unique_import_alias(self, serialize_namespace: str, imported_namespace: str, module_name: str) -> str:
|
|
156
156
|
if not self.has_subnamespace:
|
|
157
|
-
return "
|
|
157
|
+
return f"_{module_name}"
|
|
158
158
|
relative_path = self.get_relative_import_path(
|
|
159
159
|
serialize_namespace, self.get_imported_namespace_for_model(imported_namespace)
|
|
160
160
|
)
|
|
161
161
|
dot_num = max(relative_path.count(".") - 1, 0)
|
|
162
|
-
|
|
162
|
+
path_parts = [p for p in relative_path.split(".") if p]
|
|
163
|
+
# For "models", keep existing format: _<path_parts><dot_num> (e.g. _models1, _firstnamespace_models2)
|
|
164
|
+
# For other modules like "types", prefix with module name: _types_<path_parts><dot_num>
|
|
165
|
+
if module_name == "models":
|
|
166
|
+
parts = [""] + (path_parts or [module_name])
|
|
167
|
+
else:
|
|
168
|
+
parts = [f"_{module_name}"] + (path_parts or [])
|
|
163
169
|
return "_".join(parts) + (str(dot_num) if dot_num > 0 else "")
|
|
164
170
|
|
|
171
|
+
def get_unique_models_alias(self, serialize_namespace: str, imported_namespace: str) -> str:
|
|
172
|
+
return self._get_unique_import_alias(serialize_namespace, imported_namespace, "models")
|
|
173
|
+
|
|
174
|
+
def get_unique_types_alias(self, serialize_namespace: str, imported_namespace: str) -> str:
|
|
175
|
+
return self._get_unique_import_alias(serialize_namespace, imported_namespace, "types")
|
|
176
|
+
|
|
165
177
|
@property
|
|
166
178
|
def client_namespace_types(self) -> dict[str, ClientNamespaceType]:
|
|
167
179
|
if not self._client_namespace_types:
|
|
@@ -174,6 +186,14 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
|
|
|
174
186
|
if model.client_namespace not in self._client_namespace_types:
|
|
175
187
|
self._client_namespace_types[model.client_namespace] = ClientNamespaceType()
|
|
176
188
|
self._client_namespace_types[model.client_namespace].models.append(model)
|
|
189
|
+
# TypedDict copies (base="typeddict") are excluded from model_types to keep
|
|
190
|
+
# them out of _models.py, but they need to be in the namespace model list
|
|
191
|
+
# so the TypesSerializer can render them in types.py.
|
|
192
|
+
for t in self.types_map.values():
|
|
193
|
+
if isinstance(t, ModelType) and t.base == "typeddict" and t.usage != UsageFlags.Default.value:
|
|
194
|
+
if t.client_namespace not in self._client_namespace_types:
|
|
195
|
+
self._client_namespace_types[t.client_namespace] = ClientNamespaceType()
|
|
196
|
+
self._client_namespace_types[t.client_namespace].models.append(t)
|
|
177
197
|
for enum in self.enums:
|
|
178
198
|
if enum.client_namespace not in self._client_namespace_types:
|
|
179
199
|
self._client_namespace_types[enum.client_namespace] = ClientNamespaceType()
|
|
@@ -339,7 +359,9 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
|
|
|
339
359
|
"""All of the model types in this class"""
|
|
340
360
|
if not self._model_types:
|
|
341
361
|
self._model_types = [
|
|
342
|
-
t
|
|
362
|
+
t
|
|
363
|
+
for t in self.types_map.values()
|
|
364
|
+
if isinstance(t, ModelType) and t.usage != UsageFlags.Default.value and t.base != "typeddict"
|
|
343
365
|
]
|
|
344
366
|
return self._model_types
|
|
345
367
|
|
|
@@ -349,7 +371,7 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
|
|
|
349
371
|
|
|
350
372
|
@staticmethod
|
|
351
373
|
def get_public_model_types(models: list[ModelType]) -> list[ModelType]:
|
|
352
|
-
return [m for m in models if not m.internal and not m.base == "json"]
|
|
374
|
+
return [m for m in models if not m.internal and not m.base == "json" and not m.is_typed_dict_only]
|
|
353
375
|
|
|
354
376
|
@property
|
|
355
377
|
def public_model_types(self) -> list[ModelType]:
|
|
@@ -66,7 +66,7 @@ class CombinedType(BaseType):
|
|
|
66
66
|
|
|
67
67
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
68
68
|
if self.name:
|
|
69
|
-
return f'"
|
|
69
|
+
return f'"_unions.{self.name}"'
|
|
70
70
|
return self.type_definition(**kwargs)
|
|
71
71
|
|
|
72
72
|
def type_definition(self, **kwargs: Any) -> str:
|
|
@@ -116,10 +116,10 @@ class CombinedType(BaseType):
|
|
|
116
116
|
file_import = FileImport(self.code_model)
|
|
117
117
|
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
118
118
|
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
119
|
-
if self.name and serialize_namespace_type != NamespaceType.
|
|
119
|
+
if self.name and serialize_namespace_type != NamespaceType.UNIONS_FILE:
|
|
120
120
|
file_import.add_submodule_import(
|
|
121
121
|
self.code_model.get_relative_import_path(serialize_namespace),
|
|
122
|
-
"
|
|
122
|
+
"_unions",
|
|
123
123
|
ImportType.LOCAL,
|
|
124
124
|
TypingSection.TYPING,
|
|
125
125
|
)
|
|
@@ -167,6 +167,10 @@ class EnumType(BaseType):
|
|
|
167
167
|
enum_description = f"Known values are: {possible_values_str}."
|
|
168
168
|
return enum_description
|
|
169
169
|
|
|
170
|
+
@property
|
|
171
|
+
def is_typeddict_mode(self) -> bool:
|
|
172
|
+
return self.code_model.options["models-mode"] == "typeddict"
|
|
173
|
+
|
|
170
174
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
171
175
|
"""The python type used for type annotation
|
|
172
176
|
|
|
@@ -174,9 +178,20 @@ class EnumType(BaseType):
|
|
|
174
178
|
:rtype: str
|
|
175
179
|
"""
|
|
176
180
|
if self.code_model.options["models-mode"]:
|
|
181
|
+
if self.is_typeddict_mode:
|
|
182
|
+
# In typeddict mode, enums are Literal aliases defined in types.py
|
|
183
|
+
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
184
|
+
if serialize_namespace_type == NamespaceType.TYPES_FILE:
|
|
185
|
+
# Same file — just the name, no module prefix
|
|
186
|
+
return self.name
|
|
187
|
+
# From operation/client files, use types.EnumName (matching model pattern)
|
|
188
|
+
return f"types.{self.name}"
|
|
177
189
|
|
|
178
190
|
module_name = ""
|
|
179
|
-
|
|
191
|
+
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
192
|
+
if serialize_namespace_type == NamespaceType.TYPES_FILE:
|
|
193
|
+
pass # no module prefix for types.py
|
|
194
|
+
elif kwargs.get("need_model_alias", True):
|
|
180
195
|
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
181
196
|
model_alias = self.code_model.get_unique_models_alias(serialize_namespace, self.client_namespace)
|
|
182
197
|
module_name = f"{model_alias}."
|
|
@@ -240,31 +255,66 @@ class EnumType(BaseType):
|
|
|
240
255
|
file_import = FileImport(self.code_model)
|
|
241
256
|
file_import.merge(self.value_type.imports(**kwargs))
|
|
242
257
|
if self.code_model.options["models-mode"]:
|
|
243
|
-
|
|
258
|
+
if self.is_typeddict_mode:
|
|
259
|
+
# In typeddict mode, enums are Literal aliases in types.py — no Union needed
|
|
260
|
+
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
261
|
+
if serialize_namespace_type == NamespaceType.TYPES_FILE:
|
|
262
|
+
# Same file — no import needed for same-namespace enums
|
|
263
|
+
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
264
|
+
if self.client_namespace != serialize_namespace:
|
|
265
|
+
# Cross-namespace: import from sibling types module
|
|
266
|
+
relative_path = self.code_model.get_relative_import_path(
|
|
267
|
+
serialize_namespace, self.client_namespace
|
|
268
|
+
)
|
|
269
|
+
file_import.add_submodule_import(
|
|
270
|
+
f"{relative_path}types" if relative_path != "." else ".types",
|
|
271
|
+
self.name,
|
|
272
|
+
ImportType.LOCAL,
|
|
273
|
+
typing_section=TypingSection.REGULAR,
|
|
274
|
+
)
|
|
275
|
+
elif serialize_namespace_type in [NamespaceType.OPERATION, NamespaceType.CLIENT]:
|
|
276
|
+
# Import types module directly (matching model pattern)
|
|
277
|
+
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
278
|
+
relative_path = self.code_model.get_relative_import_path(serialize_namespace, self.client_namespace)
|
|
279
|
+
file_import.add_submodule_import(
|
|
280
|
+
relative_path,
|
|
281
|
+
"types",
|
|
282
|
+
ImportType.LOCAL,
|
|
283
|
+
)
|
|
284
|
+
else:
|
|
285
|
+
file_import.add_submodule_import("typing", "Union", ImportType.STDLIB, TypingSection.REGULAR)
|
|
244
286
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
287
|
+
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
288
|
+
relative_path = self.code_model.get_relative_import_path(serialize_namespace, self.client_namespace)
|
|
289
|
+
alias = self.code_model.get_unique_models_alias(serialize_namespace, self.client_namespace)
|
|
290
|
+
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
291
|
+
called_by_property = kwargs.get("called_by_property", False)
|
|
292
|
+
if serialize_namespace_type in [NamespaceType.OPERATION, NamespaceType.CLIENT]:
|
|
293
|
+
file_import.add_submodule_import(
|
|
294
|
+
relative_path,
|
|
295
|
+
"models",
|
|
296
|
+
ImportType.LOCAL,
|
|
297
|
+
alias=alias,
|
|
298
|
+
typing_section=TypingSection.REGULAR,
|
|
299
|
+
)
|
|
300
|
+
elif serialize_namespace_type == NamespaceType.TYPES_FILE:
|
|
301
|
+
# Import enum name directly to avoid dotted forward refs in TypedDict annotations
|
|
302
|
+
file_import.add_submodule_import(
|
|
303
|
+
f"{relative_path}models" if relative_path != "." else ".models",
|
|
304
|
+
self.name,
|
|
305
|
+
ImportType.LOCAL,
|
|
306
|
+
typing_section=TypingSection.TYPING,
|
|
307
|
+
)
|
|
308
|
+
elif serialize_namespace_type == NamespaceType.UNIONS_FILE or (
|
|
309
|
+
serialize_namespace_type == NamespaceType.MODEL and called_by_property
|
|
310
|
+
):
|
|
311
|
+
file_import.add_submodule_import(
|
|
312
|
+
relative_path,
|
|
313
|
+
"models",
|
|
314
|
+
ImportType.LOCAL,
|
|
315
|
+
alias=alias,
|
|
316
|
+
typing_section=TypingSection.TYPING,
|
|
317
|
+
)
|
|
268
318
|
|
|
269
319
|
file_import.merge(self.value_type.imports(**kwargs))
|
|
270
320
|
return file_import
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
from typing import Any, Optional, Union, TYPE_CHECKING
|
|
7
7
|
from .base import BaseType
|
|
8
8
|
from .imports import FileImport
|
|
9
|
+
from .utils import NamespaceType
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
11
12
|
from .code_model import CodeModel
|
|
@@ -41,12 +42,17 @@ class ListType(BaseType):
|
|
|
41
42
|
# this means we're version tolerant XML, we just return the XML element
|
|
42
43
|
return self.element_type.type_annotation(**kwargs)
|
|
43
44
|
|
|
44
|
-
# if there is a function
|
|
45
|
+
# if there is a function named `list` we have to make sure there's no conflict with the built-in `list`
|
|
46
|
+
# in operation files. The operation_groups_serializer defines `List = list` alias for this case.
|
|
47
|
+
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
45
48
|
is_operation_file = kwargs.get("is_operation_file", False)
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
in_operation_context = (
|
|
50
|
+
serialize_namespace_type in (NamespaceType.OPERATION, NamespaceType.CLIENT) or is_operation_file
|
|
48
51
|
)
|
|
49
|
-
|
|
52
|
+
if in_operation_context and self.code_model.has_operation_named_list:
|
|
53
|
+
list_type = "List"
|
|
54
|
+
else:
|
|
55
|
+
list_type = "list"
|
|
50
56
|
return f"{list_type}[{self.element_type.type_annotation(**kwargs)}]"
|
|
51
57
|
|
|
52
58
|
def description(self, *, is_operation_file: bool) -> str:
|