@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.
Files changed (73) hide show
  1. package/dist/emitter/emitter.js +10 -2
  2. package/dist/emitter/emitter.js.map +1 -1
  3. package/dist/emitter/lib.d.ts +6 -0
  4. package/dist/emitter/lib.d.ts.map +1 -1
  5. package/dist/emitter/lib.js +29 -0
  6. package/dist/emitter/lib.js.map +1 -1
  7. package/dist/emitter/types.d.ts.map +1 -1
  8. package/dist/emitter/types.js +2 -0
  9. package/dist/emitter/types.js.map +1 -1
  10. package/emitter/src/emitter.ts +9 -1
  11. package/emitter/src/lib.ts +36 -0
  12. package/emitter/src/types.ts +2 -0
  13. package/emitter/temp/tsconfig.tsbuildinfo +1 -1
  14. package/eng/scripts/ci/regenerate-common.ts +40 -15
  15. package/eng/scripts/setup/__pycache__/package_manager.cpython-311.pyc +0 -0
  16. package/eng/scripts/setup/__pycache__/venvtools.cpython-311.pyc +0 -0
  17. package/generator/build/lib/pygen/__init__.py +4 -3
  18. package/generator/build/lib/pygen/codegen/models/__init__.py +4 -2
  19. package/generator/build/lib/pygen/codegen/models/code_model.py +27 -5
  20. package/generator/build/lib/pygen/codegen/models/combined_type.py +3 -3
  21. package/generator/build/lib/pygen/codegen/models/enum_type.py +75 -25
  22. package/generator/build/lib/pygen/codegen/models/list_type.py +10 -4
  23. package/generator/build/lib/pygen/codegen/models/model_type.py +145 -4
  24. package/generator/build/lib/pygen/codegen/models/operation.py +1 -0
  25. package/generator/build/lib/pygen/codegen/models/paging_operation.py +5 -1
  26. package/generator/build/lib/pygen/codegen/models/parameter.py +2 -2
  27. package/generator/build/lib/pygen/codegen/models/primitive_types.py +30 -6
  28. package/generator/build/lib/pygen/codegen/models/property.py +21 -8
  29. package/generator/build/lib/pygen/codegen/models/response.py +16 -3
  30. package/generator/build/lib/pygen/codegen/models/utils.py +12 -10
  31. package/generator/build/lib/pygen/codegen/serializers/__init__.py +46 -11
  32. package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +48 -15
  33. package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +32 -2
  34. package/generator/build/lib/pygen/codegen/serializers/model_init_serializer.py +1 -1
  35. package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +127 -3
  36. package/generator/build/lib/pygen/codegen/serializers/types_serializer.py +263 -12
  37. package/generator/build/lib/pygen/codegen/serializers/unions_serializer.py +44 -0
  38. package/generator/build/lib/pygen/codegen/templates/model_container.py.jinja2 +1 -1
  39. package/generator/build/lib/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +30 -2
  40. package/generator/build/lib/pygen/codegen/templates/types.py.jinja2 +51 -2
  41. package/generator/build/lib/pygen/codegen/templates/unions.py.jinja2 +9 -0
  42. package/generator/build/lib/pygen/preprocess/__init__.py +104 -13
  43. package/generator/build/lib/pygen/utils.py +16 -0
  44. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  45. package/generator/pygen/__init__.py +4 -3
  46. package/generator/pygen/codegen/models/__init__.py +4 -2
  47. package/generator/pygen/codegen/models/code_model.py +27 -5
  48. package/generator/pygen/codegen/models/combined_type.py +3 -3
  49. package/generator/pygen/codegen/models/enum_type.py +75 -25
  50. package/generator/pygen/codegen/models/list_type.py +10 -4
  51. package/generator/pygen/codegen/models/model_type.py +145 -4
  52. package/generator/pygen/codegen/models/operation.py +1 -0
  53. package/generator/pygen/codegen/models/paging_operation.py +5 -1
  54. package/generator/pygen/codegen/models/parameter.py +2 -2
  55. package/generator/pygen/codegen/models/primitive_types.py +30 -6
  56. package/generator/pygen/codegen/models/property.py +21 -8
  57. package/generator/pygen/codegen/models/response.py +16 -3
  58. package/generator/pygen/codegen/models/utils.py +12 -10
  59. package/generator/pygen/codegen/serializers/__init__.py +46 -11
  60. package/generator/pygen/codegen/serializers/builder_serializer.py +48 -15
  61. package/generator/pygen/codegen/serializers/general_serializer.py +32 -2
  62. package/generator/pygen/codegen/serializers/model_init_serializer.py +1 -1
  63. package/generator/pygen/codegen/serializers/model_serializer.py +127 -3
  64. package/generator/pygen/codegen/serializers/types_serializer.py +263 -12
  65. package/generator/pygen/codegen/serializers/unions_serializer.py +44 -0
  66. package/generator/pygen/codegen/templates/model_container.py.jinja2 +1 -1
  67. package/generator/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +30 -2
  68. package/generator/pygen/codegen/templates/types.py.jinja2 +51 -2
  69. package/generator/pygen/codegen/templates/unions.py.jinja2 +9 -0
  70. package/generator/pygen/preprocess/__init__.py +104 -13
  71. package/generator/pygen/utils.py +16 -0
  72. package/generator/pygen.egg-info/SOURCES.txt +2 -0
  73. 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
- if description and description[-1] != ".":
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
- if origin_type == "model":
332
- body_parameter["type"]["types"].insert(1, KNOWN_TYPES["any-object"])
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
- # dict or list
335
- # copy the original dict / list type
336
- any_obj_list_or_dict = copy.deepcopy(body_parameter["type"]["types"][0])
337
- any_obj_list_or_dict["elementType"] = KNOWN_TYPES["any-object"]
338
- body_parameter["type"]["types"].insert(1, any_obj_list_or_dict)
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(extract_original_name(yaml_data["name"]), PadType.METHOD, yaml_data)
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"], PadType.OPERATION_GROUP, yaml_data["nextOperation"]
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"], PadType.OPERATION_GROUP, operation_group
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 {
@@ -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"] == "json" or not code_model.options["models-mode"]:
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 get_unique_models_alias(self, serialize_namespace: str, imported_namespace: str) -> str:
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 "_models"
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
- parts = [""] + ([p for p in relative_path.split(".") if p] or ["models"])
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 for t in self.types_map.values() if isinstance(t, ModelType) and t.usage != UsageFlags.Default.value
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'"_types.{self.name}"'
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.TYPES_FILE:
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
- "_types",
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
- if kwargs.get("need_model_alias", True):
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
- file_import.add_submodule_import("typing", "Union", ImportType.STDLIB, TypingSection.REGULAR)
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
- serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
246
- relative_path = self.code_model.get_relative_import_path(serialize_namespace, self.client_namespace)
247
- alias = self.code_model.get_unique_models_alias(serialize_namespace, self.client_namespace)
248
- serialize_namespace_type = kwargs.get("serialize_namespace_type")
249
- called_by_property = kwargs.get("called_by_property", False)
250
- if serialize_namespace_type in [NamespaceType.OPERATION, NamespaceType.CLIENT]:
251
- file_import.add_submodule_import(
252
- relative_path,
253
- "models",
254
- ImportType.LOCAL,
255
- alias=alias,
256
- typing_section=TypingSection.REGULAR,
257
- )
258
- elif serialize_namespace_type == NamespaceType.TYPES_FILE or (
259
- serialize_namespace_type == NamespaceType.MODEL and called_by_property
260
- ):
261
- file_import.add_submodule_import(
262
- relative_path,
263
- "models",
264
- ImportType.LOCAL,
265
- alias=alias,
266
- typing_section=TypingSection.TYPING,
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/property named `list` we have to make sure there's no conflict with the built-in `list`
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
- use_list_import = (self.code_model.has_operation_named_list and is_operation_file) or (
47
- self.code_model.has_property_named_list and not is_operation_file
49
+ in_operation_context = (
50
+ serialize_namespace_type in (NamespaceType.OPERATION, NamespaceType.CLIENT) or is_operation_file
48
51
  )
49
- list_type = "List" if use_list_import else "list"
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: