@typespec/http-client-python 0.5.0 → 0.6.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 (163) hide show
  1. package/dist/emitter/code-model.d.ts.map +1 -1
  2. package/dist/emitter/code-model.js +10 -7
  3. package/dist/emitter/code-model.js.map +1 -1
  4. package/dist/emitter/emitter.d.ts.map +1 -1
  5. package/dist/emitter/emitter.js +35 -7
  6. package/dist/emitter/emitter.js.map +1 -1
  7. package/dist/emitter/http.js +1 -1
  8. package/dist/emitter/http.js.map +1 -1
  9. package/dist/emitter/lib.d.ts +1 -0
  10. package/dist/emitter/lib.d.ts.map +1 -1
  11. package/dist/emitter/lib.js +1 -0
  12. package/dist/emitter/lib.js.map +1 -1
  13. package/dist/emitter/types.d.ts.map +1 -1
  14. package/dist/emitter/types.js +7 -4
  15. package/dist/emitter/types.js.map +1 -1
  16. package/dist/emitter/utils.d.ts +1 -0
  17. package/dist/emitter/utils.d.ts.map +1 -1
  18. package/dist/emitter/utils.js +19 -0
  19. package/dist/emitter/utils.js.map +1 -1
  20. package/emitter/src/code-model.ts +16 -6
  21. package/emitter/src/emitter.ts +37 -11
  22. package/emitter/src/http.ts +1 -1
  23. package/emitter/src/lib.ts +4 -0
  24. package/emitter/src/types.ts +16 -4
  25. package/emitter/src/utils.ts +27 -0
  26. package/emitter/temp/tsconfig.tsbuildinfo +1 -1
  27. package/eng/scripts/Test-Packages.ps1 +1 -1
  28. package/eng/scripts/ci/regenerate.ts +21 -14
  29. package/eng/scripts/setup/__pycache__/venvtools.cpython-38.pyc +0 -0
  30. package/eng/scripts/setup/install.py +0 -5
  31. package/eng/scripts/setup/install.ts +0 -20
  32. package/generator/build/lib/pygen/black.py +2 -2
  33. package/generator/build/lib/pygen/codegen/__init__.py +6 -4
  34. package/generator/build/lib/pygen/codegen/_utils.py +4 -0
  35. package/generator/build/lib/pygen/codegen/models/base.py +2 -3
  36. package/generator/build/lib/pygen/codegen/models/base_builder.py +5 -3
  37. package/generator/build/lib/pygen/codegen/models/client.py +28 -19
  38. package/generator/build/lib/pygen/codegen/models/code_model.py +200 -33
  39. package/generator/build/lib/pygen/codegen/models/combined_type.py +8 -5
  40. package/generator/build/lib/pygen/codegen/models/constant_type.py +2 -3
  41. package/generator/build/lib/pygen/codegen/models/credential_types.py +1 -2
  42. package/generator/build/lib/pygen/codegen/models/dictionary_type.py +2 -3
  43. package/generator/build/lib/pygen/codegen/models/enum_type.py +47 -24
  44. package/generator/build/lib/pygen/codegen/models/imports.py +14 -12
  45. package/generator/build/lib/pygen/codegen/models/list_type.py +2 -3
  46. package/generator/build/lib/pygen/codegen/models/lro_operation.py +8 -4
  47. package/generator/build/lib/pygen/codegen/models/lro_paging_operation.py +2 -2
  48. package/generator/build/lib/pygen/codegen/models/model_type.py +34 -19
  49. package/generator/build/lib/pygen/codegen/models/operation.py +66 -29
  50. package/generator/build/lib/pygen/codegen/models/operation_group.py +56 -11
  51. package/generator/build/lib/pygen/codegen/models/paging_operation.py +9 -6
  52. package/generator/build/lib/pygen/codegen/models/parameter.py +10 -10
  53. package/generator/build/lib/pygen/codegen/models/parameter_list.py +7 -7
  54. package/generator/build/lib/pygen/codegen/models/primitive_types.py +23 -43
  55. package/generator/build/lib/pygen/codegen/models/property.py +7 -7
  56. package/generator/build/lib/pygen/codegen/models/request_builder.py +9 -15
  57. package/generator/build/lib/pygen/codegen/models/response.py +6 -8
  58. package/generator/build/lib/pygen/codegen/models/utils.py +11 -0
  59. package/generator/build/lib/pygen/codegen/serializers/__init__.py +201 -242
  60. package/generator/build/lib/pygen/codegen/serializers/base_serializer.py +19 -1
  61. package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +53 -35
  62. package/generator/build/lib/pygen/codegen/serializers/client_serializer.py +9 -5
  63. package/generator/build/lib/pygen/codegen/serializers/enum_serializer.py +17 -3
  64. package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +26 -14
  65. package/generator/build/lib/pygen/codegen/serializers/metadata_serializer.py +26 -8
  66. package/generator/build/lib/pygen/codegen/serializers/model_init_serializer.py +9 -4
  67. package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +62 -22
  68. package/generator/build/lib/pygen/codegen/serializers/operation_groups_serializer.py +19 -16
  69. package/generator/build/lib/pygen/codegen/serializers/operations_init_serializer.py +5 -10
  70. package/generator/build/lib/pygen/codegen/serializers/parameter_serializer.py +10 -7
  71. package/generator/build/lib/pygen/codegen/serializers/request_builders_serializer.py +10 -1
  72. package/generator/build/lib/pygen/codegen/serializers/sample_serializer.py +7 -10
  73. package/generator/build/lib/pygen/codegen/serializers/test_serializer.py +24 -28
  74. package/generator/build/lib/pygen/codegen/serializers/types_serializer.py +6 -1
  75. package/generator/build/lib/pygen/codegen/serializers/utils.py +1 -15
  76. package/generator/build/lib/pygen/codegen/templates/client_container.py.jinja2 +1 -1
  77. package/generator/build/lib/pygen/codegen/templates/config_container.py.jinja2 +1 -1
  78. package/generator/build/lib/pygen/codegen/templates/enum_container.py.jinja2 +1 -1
  79. package/generator/build/lib/pygen/codegen/templates/init.py.jinja2 +1 -1
  80. package/generator/build/lib/pygen/codegen/templates/model_container.py.jinja2 +1 -1
  81. package/generator/build/lib/pygen/codegen/templates/operations_folder_init.py.jinja2 +2 -4
  82. package/generator/build/lib/pygen/codegen/templates/test.py.jinja2 +3 -3
  83. package/generator/build/lib/pygen/codegen/templates/testpreparer.py.jinja2 +2 -2
  84. package/generator/build/lib/pygen/codegen/templates/vendor.py.jinja2 +4 -4
  85. package/generator/build/lib/pygen/preprocess/__init__.py +0 -4
  86. package/generator/dev_requirements.txt +2 -2
  87. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  88. package/generator/pygen/black.py +2 -2
  89. package/generator/pygen/codegen/__init__.py +6 -4
  90. package/generator/pygen/codegen/_utils.py +4 -0
  91. package/generator/pygen/codegen/models/base.py +2 -3
  92. package/generator/pygen/codegen/models/base_builder.py +5 -3
  93. package/generator/pygen/codegen/models/client.py +28 -19
  94. package/generator/pygen/codegen/models/code_model.py +200 -33
  95. package/generator/pygen/codegen/models/combined_type.py +8 -5
  96. package/generator/pygen/codegen/models/constant_type.py +2 -3
  97. package/generator/pygen/codegen/models/credential_types.py +1 -2
  98. package/generator/pygen/codegen/models/dictionary_type.py +2 -3
  99. package/generator/pygen/codegen/models/enum_type.py +47 -24
  100. package/generator/pygen/codegen/models/imports.py +14 -12
  101. package/generator/pygen/codegen/models/list_type.py +2 -3
  102. package/generator/pygen/codegen/models/lro_operation.py +8 -4
  103. package/generator/pygen/codegen/models/lro_paging_operation.py +2 -2
  104. package/generator/pygen/codegen/models/model_type.py +34 -19
  105. package/generator/pygen/codegen/models/operation.py +66 -29
  106. package/generator/pygen/codegen/models/operation_group.py +56 -11
  107. package/generator/pygen/codegen/models/paging_operation.py +9 -6
  108. package/generator/pygen/codegen/models/parameter.py +10 -10
  109. package/generator/pygen/codegen/models/parameter_list.py +7 -7
  110. package/generator/pygen/codegen/models/primitive_types.py +23 -43
  111. package/generator/pygen/codegen/models/property.py +7 -7
  112. package/generator/pygen/codegen/models/request_builder.py +9 -15
  113. package/generator/pygen/codegen/models/response.py +6 -8
  114. package/generator/pygen/codegen/models/utils.py +11 -0
  115. package/generator/pygen/codegen/serializers/__init__.py +201 -242
  116. package/generator/pygen/codegen/serializers/base_serializer.py +19 -1
  117. package/generator/pygen/codegen/serializers/builder_serializer.py +53 -35
  118. package/generator/pygen/codegen/serializers/client_serializer.py +9 -5
  119. package/generator/pygen/codegen/serializers/enum_serializer.py +17 -3
  120. package/generator/pygen/codegen/serializers/general_serializer.py +26 -14
  121. package/generator/pygen/codegen/serializers/metadata_serializer.py +26 -8
  122. package/generator/pygen/codegen/serializers/model_init_serializer.py +9 -4
  123. package/generator/pygen/codegen/serializers/model_serializer.py +62 -22
  124. package/generator/pygen/codegen/serializers/operation_groups_serializer.py +19 -16
  125. package/generator/pygen/codegen/serializers/operations_init_serializer.py +5 -10
  126. package/generator/pygen/codegen/serializers/parameter_serializer.py +10 -7
  127. package/generator/pygen/codegen/serializers/request_builders_serializer.py +10 -1
  128. package/generator/pygen/codegen/serializers/sample_serializer.py +7 -10
  129. package/generator/pygen/codegen/serializers/test_serializer.py +24 -28
  130. package/generator/pygen/codegen/serializers/types_serializer.py +6 -1
  131. package/generator/pygen/codegen/serializers/utils.py +1 -15
  132. package/generator/pygen/codegen/templates/client_container.py.jinja2 +1 -1
  133. package/generator/pygen/codegen/templates/config_container.py.jinja2 +1 -1
  134. package/generator/pygen/codegen/templates/enum_container.py.jinja2 +1 -1
  135. package/generator/pygen/codegen/templates/init.py.jinja2 +1 -1
  136. package/generator/pygen/codegen/templates/model_container.py.jinja2 +1 -1
  137. package/generator/pygen/codegen/templates/operations_folder_init.py.jinja2 +2 -4
  138. package/generator/pygen/codegen/templates/test.py.jinja2 +3 -3
  139. package/generator/pygen/codegen/templates/testpreparer.py.jinja2 +2 -2
  140. package/generator/pygen/codegen/templates/vendor.py.jinja2 +4 -4
  141. package/generator/pygen/preprocess/__init__.py +0 -4
  142. package/generator/test/azure/mock_api_tests/asynctests/test_azure_client_generator_core_flatten_async.py +1 -1
  143. package/generator/test/{generic_mock_api_tests/asynctests/test_payload_pageable_async.py → azure/mock_api_tests/asynctests/test_azure_payload_pageable_async.py} +1 -1
  144. package/generator/test/azure/mock_api_tests/asynctests/test_client_namespace_async.py +31 -0
  145. package/generator/test/azure/mock_api_tests/conftest.py +5 -4
  146. package/generator/test/azure/mock_api_tests/test_azure_client_generator_core_flatten.py +1 -1
  147. package/generator/test/{generic_mock_api_tests/test_payload_pageable.py → azure/mock_api_tests/test_azure_payload_pageable.py} +1 -1
  148. package/generator/test/azure/mock_api_tests/test_client_namespace.py +29 -0
  149. package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_resiliency_srv_driven.py +4 -2
  150. package/generator/test/{generic_mock_api_tests/asynctests → azure/mock_api_tests}/test_resiliency_srv_driven_async.py +4 -2
  151. package/generator/test/azure/requirements.txt +11 -9
  152. package/generator/test/azure/tox.ini +2 -2
  153. package/generator/test/generic_mock_api_tests/conftest.py +9 -4
  154. package/generator/test/unbranded/mock_api_tests/conftest.py +4 -4
  155. package/generator/test/unbranded/mock_api_tests/test_unbranded.py +1 -1
  156. package/generator/test/unbranded/requirements.txt +2 -9
  157. package/generator/test/unbranded/tox.ini +2 -2
  158. package/package.json +5 -6
  159. package/generator/requirements.txt +0 -7
  160. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_client_naming_async.py +0 -0
  161. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_client_structure_async.py +0 -0
  162. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_client_naming.py +0 -0
  163. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_client_structure.py +0 -0
@@ -47,11 +47,6 @@ def main():
47
47
  venv_context = env_builder.context
48
48
 
49
49
  python_run(venv_context, "pip", ["install", "-U", "pip"])
50
- python_run(
51
- venv_context,
52
- "pip",
53
- ["install", "-r", f"{_ROOT_DIR}/generator/requirements.txt"],
54
- )
55
50
  python_run(venv_context, "pip", ["install", "-e", f"{_ROOT_DIR}/generator"])
56
51
 
57
52
 
@@ -1,7 +1,3 @@
1
- import fs from "fs";
2
- import path, { dirname } from "path";
3
- import { loadPyodide } from "pyodide";
4
- import { fileURLToPath } from "url";
5
1
  import { runPython3 } from "./run-python3.js";
6
2
 
7
3
  async function main() {
@@ -10,23 +6,7 @@ async function main() {
10
6
  console.log("Found Python on your local environment and created a venv with all requirements."); // eslint-disable-line no-console
11
7
  } catch (error) {
12
8
  console.log("No Python found on your local environment. We will use Pyodide instead."); // eslint-disable-line no-console
13
- } finally {
14
- await installPyodideDeps();
15
- console.log("Successfully installed all required Python packages in Pyodide"); // eslint-disable-line no-console
16
9
  }
17
10
  }
18
11
 
19
- async function installPyodideDeps() {
20
- const root = path.join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
21
- const pyodide = await loadPyodide({
22
- indexURL: path.dirname(fileURLToPath(import.meta.resolve("pyodide"))),
23
- });
24
- await pyodide.loadPackage("micropip");
25
- const micropip = pyodide.pyimport("micropip");
26
- const requirementsPath = path.join(root, "generator", "requirements.txt");
27
- const requirementsText = fs.readFileSync(requirementsPath, "utf-8");
28
- const requirementsArray = requirementsText.split("\n").filter((line) => line.trim() !== "");
29
- await micropip.install(requirementsArray);
30
- }
31
-
32
12
  main();
@@ -43,8 +43,8 @@ class BlackScriptPlugin(Plugin):
43
43
  "venv",
44
44
  "env",
45
45
  )
46
- and not Path(f).parts[0].startswith(".")
47
- and Path(f).suffix == ".py"
46
+ # we shall also format generated files like "../../../generated_tests/test_xxx.py"
47
+ and (not Path(f).parts[0].startswith(".") or Path(f).parts[0] == "..") and Path(f).suffix == ".py"
48
48
  ],
49
49
  )
50
50
  )
@@ -40,6 +40,7 @@ class OptionsRetriever:
40
40
  "generate-test": False,
41
41
  "from-typespec": False,
42
42
  "emit-cross-language-definition-file": False,
43
+ "enable-typespec-namespace": False,
43
44
  }
44
45
 
45
46
  @property
@@ -244,8 +245,8 @@ class CodeGenerator(Plugin):
244
245
  @staticmethod
245
246
  def sort_exceptions(yaml_data: Dict[str, Any]) -> None:
246
247
  for client in yaml_data["clients"]:
247
- for group in client["operationGroups"]:
248
- for operation in group["operations"]:
248
+ for group in client.get("operationGroups", []):
249
+ for operation in group.get("operations", []):
249
250
  if not operation.get("exceptions"):
250
251
  continue
251
252
  # sort exceptions by status code, first single status code, then range, then default
@@ -261,8 +262,8 @@ class CodeGenerator(Plugin):
261
262
  @staticmethod
262
263
  def remove_cloud_errors(yaml_data: Dict[str, Any]) -> None:
263
264
  for client in yaml_data["clients"]:
264
- for group in client["operationGroups"]:
265
- for operation in group["operations"]:
265
+ for group in client.get("operationGroups", []):
266
+ for operation in group.get("operations", []):
266
267
  if not operation.get("exceptions"):
267
268
  continue
268
269
  i = 0
@@ -316,6 +317,7 @@ class CodeGenerator(Plugin):
316
317
  "flavor",
317
318
  "company_name",
318
319
  "emit_cross_language_definition_file",
320
+ "enable_typespec_namespace",
319
321
  ]
320
322
  return {f: getattr(self.options_retriever, f) for f in flags}
321
323
 
@@ -15,3 +15,7 @@ SWAGGER_PACKAGE_MODE = ["mgmtplane", "dataplane"] # for backward compatibility
15
15
  TYPESPEC_PACKAGE_MODE = ["azure-mgmt", "azure-dataplane", "generic"]
16
16
  VALID_PACKAGE_MODE = SWAGGER_PACKAGE_MODE + TYPESPEC_PACKAGE_MODE
17
17
  NAME_LENGTH_LIMIT = 40
18
+
19
+
20
+ def get_parent_namespace(namespace: str) -> str:
21
+ return namespace.rsplit(".", 1)[0]
@@ -87,8 +87,7 @@ class BaseType(BaseModel, ABC): # pylint: disable=too-many-public-methods
87
87
  attrs_list.append("'text': True")
88
88
  return ", ".join(attrs_list)
89
89
 
90
- @property
91
- def serialization_type(self) -> str:
90
+ def serialization_type(self, **kwargs: Any) -> str:
92
91
  """The tag recognized by 'msrest' as a serialization/deserialization.
93
92
 
94
93
  'str', 'int', 'float', 'bool' or
@@ -103,7 +102,7 @@ class BaseType(BaseModel, ABC): # pylint: disable=too-many-public-methods
103
102
 
104
103
  @property
105
104
  def msrest_deserialization_key(self) -> str:
106
- return self.serialization_type
105
+ return self.serialization_type()
107
106
 
108
107
  @property
109
108
  def client_default_value(self) -> Any:
@@ -14,6 +14,7 @@ from typing import (
14
14
  Union,
15
15
  TYPE_CHECKING,
16
16
  cast,
17
+ Sequence,
17
18
  )
18
19
  from abc import abstractmethod
19
20
 
@@ -39,7 +40,7 @@ if TYPE_CHECKING:
39
40
  from .request_builder import RequestBuilder
40
41
 
41
42
 
42
- OverloadListType = TypeVar("OverloadListType", bound=Union[List["Operation"], List["RequestBuilder"]])
43
+ OverloadListType = TypeVar("OverloadListType", bound=Union[Sequence["Operation"], Sequence["RequestBuilder"]])
43
44
 
44
45
  _LOGGER = logging.getLogger(__name__)
45
46
 
@@ -72,6 +73,7 @@ class BaseBuilder(
72
73
  self.api_versions: List[str] = yaml_data["apiVersions"]
73
74
  self.added_on: Optional[str] = yaml_data.get("addedOn")
74
75
  self.external_docs: Optional[Dict[str, Any]] = yaml_data.get("externalDocs")
76
+ self.client_namespace: str = yaml_data.get("clientNamespace", code_model.namespace)
75
77
 
76
78
  if code_model.options["version_tolerant"] and yaml_data.get("abstract"):
77
79
  _LOGGER.warning(
@@ -112,7 +114,7 @@ class BaseBuilder(
112
114
  )
113
115
  return self._description or self.name
114
116
 
115
- def method_signature(self, async_mode: bool) -> List[str]:
117
+ def method_signature(self, async_mode: bool, **kwargs: Any) -> List[str]:
116
118
  if self.abstract:
117
119
  return ["*args,", "**kwargs"]
118
- return self.parameters.method_signature(async_mode)
120
+ return self.parameters.method_signature(async_mode, **kwargs)
@@ -43,6 +43,7 @@ class _ClientConfigBase(Generic[ParameterListType], BaseModel):
43
43
  self.parameters = parameters
44
44
  self.url: str = self.yaml_data["url"] # the base endpoint of the client. Can be parameterized or not
45
45
  self.legacy_filename: str = self.yaml_data.get("legacyFilename", "client")
46
+ self.client_namespace: str = self.yaml_data.get("clientNamespace", code_model.namespace)
46
47
 
47
48
  @property
48
49
  def description(self) -> str:
@@ -188,7 +189,7 @@ class Client(_ClientConfigBase[ClientGlobalParameterList]):
188
189
  except StopIteration as exc:
189
190
  raise KeyError(f"No operation with id {operation_id} found.") from exc
190
191
 
191
- def _imports_shared(self, async_mode: bool) -> FileImport:
192
+ def _imports_shared(self, async_mode: bool, **kwargs) -> FileImport:
192
193
  file_import = FileImport(self.code_model)
193
194
  file_import.add_submodule_import("typing", "Any", ImportType.STDLIB, TypingSection.CONDITIONAL)
194
195
  if self.code_model.options["azure_arm"]:
@@ -206,8 +207,8 @@ class Client(_ClientConfigBase[ClientGlobalParameterList]):
206
207
  file_import.merge(
207
208
  gp.imports(
208
209
  async_mode,
209
- relative_path=".." if async_mode else ".",
210
- operation=True,
210
+ is_operation_file=True,
211
+ **kwargs,
211
212
  )
212
213
  )
213
214
  file_import.add_submodule_import(
@@ -215,8 +216,9 @@ class Client(_ClientConfigBase[ClientGlobalParameterList]):
215
216
  f"{self.name}Configuration",
216
217
  ImportType.LOCAL,
217
218
  )
219
+ serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
218
220
  file_import.add_msrest_import(
219
- relative_path=".." if async_mode else ".",
221
+ serialize_namespace=serialize_namespace,
220
222
  msrest_import_type=MsrestImportType.SerializerDeserializer,
221
223
  typing_section=TypingSection.REGULAR,
222
224
  )
@@ -277,8 +279,8 @@ class Client(_ClientConfigBase[ClientGlobalParameterList]):
277
279
  """Whether there is non-abstract operation in any operation group."""
278
280
  return any(og.has_non_abstract_operations for og in self.operation_groups)
279
281
 
280
- def imports(self, async_mode: bool) -> FileImport:
281
- file_import = self._imports_shared(async_mode)
282
+ def imports(self, async_mode: bool, **kwargs) -> FileImport:
283
+ file_import = self._imports_shared(async_mode, **kwargs)
282
284
  if async_mode:
283
285
  file_import.add_submodule_import("typing", "Awaitable", ImportType.STDLIB)
284
286
  file_import.add_submodule_import(
@@ -300,9 +302,13 @@ class Client(_ClientConfigBase[ClientGlobalParameterList]):
300
302
  ImportType.SDKCORE,
301
303
  TypingSection.CONDITIONAL,
302
304
  )
305
+ serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
303
306
  for og in self.operation_groups:
304
307
  file_import.add_submodule_import(
305
- f".{self.code_model.operations_folder_name}",
308
+ self.code_model.get_relative_import_path(
309
+ serialize_namespace,
310
+ self.code_model.get_imported_namespace_for_operation(og.client_namespace, async_mode),
311
+ ),
306
312
  og.class_name,
307
313
  ImportType.LOCAL,
308
314
  )
@@ -317,8 +323,8 @@ class Client(_ClientConfigBase[ClientGlobalParameterList]):
317
323
  file_import.add_submodule_import("copy", "deepcopy", ImportType.STDLIB)
318
324
  return file_import
319
325
 
320
- def imports_for_multiapi(self, async_mode: bool) -> FileImport:
321
- file_import = self._imports_shared(async_mode)
326
+ def imports_for_multiapi(self, async_mode: bool, **kwargs) -> FileImport:
327
+ file_import = self._imports_shared(async_mode, **kwargs)
322
328
  file_import.add_submodule_import("typing", "Optional", ImportType.STDLIB, TypingSection.CONDITIONAL)
323
329
  try:
324
330
  mixin_operation = next(og for og in self.operation_groups if og.is_mixin)
@@ -377,7 +383,7 @@ class Config(_ClientConfigBase[ConfigGlobalParameterList]):
377
383
  def name(self) -> str:
378
384
  return f"{super().name}Configuration"
379
385
 
380
- def _imports_shared(self, async_mode: bool) -> FileImport:
386
+ def _imports_shared(self, async_mode: bool, **kwargs: Any) -> FileImport:
381
387
  file_import = FileImport(self.code_model)
382
388
  file_import.add_submodule_import(
383
389
  "pipeline" if self.code_model.is_azure_flavor else "runtime",
@@ -386,7 +392,12 @@ class Config(_ClientConfigBase[ConfigGlobalParameterList]):
386
392
  )
387
393
  file_import.add_submodule_import("typing", "Any", ImportType.STDLIB, TypingSection.CONDITIONAL)
388
394
  if self.code_model.options["package_version"]:
389
- file_import.add_submodule_import(".._version" if async_mode else "._version", "VERSION", ImportType.LOCAL)
395
+ serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
396
+ file_import.add_submodule_import(
397
+ self.code_model.get_relative_import_path(serialize_namespace, module_name="_version"),
398
+ "VERSION",
399
+ ImportType.LOCAL,
400
+ )
390
401
  if self.code_model.options["azure_arm"]:
391
402
  policy = "AsyncARMChallengeAuthenticationPolicy" if async_mode else "ARMChallengeAuthenticationPolicy"
392
403
  file_import.add_submodule_import("azure.mgmt.core.policies", "ARMHttpLoggingPolicy", ImportType.SDKCORE)
@@ -394,22 +405,21 @@ class Config(_ClientConfigBase[ConfigGlobalParameterList]):
394
405
 
395
406
  return file_import
396
407
 
397
- def imports(self, async_mode: bool) -> FileImport:
398
- file_import = self._imports_shared(async_mode)
408
+ def imports(self, async_mode: bool, **kwargs) -> FileImport:
409
+ file_import = self._imports_shared(async_mode, **kwargs)
399
410
  for gp in self.parameters:
400
411
  if gp.method_location == ParameterMethodLocation.KWARG and gp not in self.parameters.kwargs_to_pop:
401
412
  continue
402
413
  file_import.merge(
403
414
  gp.imports(
404
415
  async_mode=async_mode,
405
- relative_path=".." if async_mode else ".",
406
- operation=True,
416
+ **kwargs,
407
417
  )
408
418
  )
409
419
  return file_import
410
420
 
411
- def imports_for_multiapi(self, async_mode: bool) -> FileImport:
412
- file_import = self._imports_shared(async_mode)
421
+ def imports_for_multiapi(self, async_mode: bool, **kwargs: Any) -> FileImport:
422
+ file_import = self._imports_shared(async_mode, **kwargs)
413
423
  for gp in self.parameters:
414
424
  if (
415
425
  gp.method_location == ParameterMethodLocation.KWARG
@@ -420,8 +430,7 @@ class Config(_ClientConfigBase[ConfigGlobalParameterList]):
420
430
  file_import.merge(
421
431
  gp.imports_for_multiapi(
422
432
  async_mode=async_mode,
423
- relative_path=".." if async_mode else ".",
424
- operation=True,
433
+ **kwargs,
425
434
  )
426
435
  )
427
436
  return file_import
@@ -3,7 +3,7 @@
3
3
  # Licensed under the MIT License. See License.txt in the project root for
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
- from typing import List, Dict, Any, Set, Union, Literal
6
+ from typing import List, Dict, Any, Set, Union, Literal, Optional, cast
7
7
 
8
8
  from .base import BaseType
9
9
  from .enum_type import EnumType
@@ -11,12 +11,40 @@ from .model_type import ModelType, UsageFlags
11
11
  from .combined_type import CombinedType
12
12
  from .client import Client
13
13
  from .request_builder import RequestBuilder, OverloadedRequestBuilder
14
+ from .operation_group import OperationGroup
15
+ from .utils import NamespaceType
14
16
 
15
17
 
16
18
  def _is_legacy(options) -> bool:
17
19
  return not (options.get("version_tolerant") or options.get("low_level_client"))
18
20
 
19
21
 
22
+ def get_all_operation_groups_recursively(clients: List[Client]) -> List[OperationGroup]:
23
+ operation_groups = []
24
+ queue = []
25
+ for client in clients:
26
+ queue.extend(client.operation_groups)
27
+ while queue:
28
+ operation_groups.append(queue.pop(0))
29
+ if operation_groups[-1].operation_groups:
30
+ queue.extend(operation_groups[-1].operation_groups)
31
+ return operation_groups
32
+
33
+
34
+ class ClientNamespaceType:
35
+ def __init__(
36
+ self,
37
+ clients: Optional[List[Client]] = None,
38
+ models: Optional[List[ModelType]] = None,
39
+ enums: Optional[List[EnumType]] = None,
40
+ operation_groups: Optional[List[OperationGroup]] = None,
41
+ ):
42
+ self.clients = clients or []
43
+ self.models = models or []
44
+ self.enums = enums or []
45
+ self.operation_groups = operation_groups or []
46
+
47
+
20
48
  class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-instance-attributes
21
49
  """Top level code model
22
50
 
@@ -44,8 +72,6 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
44
72
  self,
45
73
  yaml_data: Dict[str, Any],
46
74
  options: Dict[str, Any],
47
- *,
48
- is_subnamespace: bool = False,
49
75
  ) -> None:
50
76
  self.yaml_data = yaml_data
51
77
  self.options = options
@@ -59,18 +85,113 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
59
85
  self.clients: List[Client] = [
60
86
  Client.from_yaml(client_yaml_data, self) for client_yaml_data in yaml_data["clients"]
61
87
  ]
62
- self.subnamespace_to_clients: Dict[str, List[Client]] = {
63
- subnamespace: [Client.from_yaml(client_yaml, self, is_subclient=True) for client_yaml in client_yamls]
64
- for subnamespace, client_yamls in yaml_data.get("subnamespaceToClients", {}).items()
65
- }
66
88
  if self.options["models_mode"] and self.model_types:
67
89
  self.sort_model_types()
68
- self.is_subnamespace = is_subnamespace
69
90
  self.named_unions: List[CombinedType] = [
70
91
  t for t in self.types_map.values() if isinstance(t, CombinedType) and t.name
71
92
  ]
72
93
  self.cross_language_package_id = self.yaml_data.get("crossLanguagePackageId")
73
94
  self.for_test: bool = False
95
+ # key is typespec namespace, value is models/clients/opeartion_groups/enums cache in the namespace
96
+ self._client_namespace_types: Dict[str, ClientNamespaceType] = {}
97
+ self.has_subnamespace = False
98
+ self._operations_folder_name: Dict[str, str] = {}
99
+ self._relative_import_path: Dict[str, str] = {}
100
+
101
+ @staticmethod
102
+ def get_imported_namespace_for_client(imported_namespace: str, async_mode: bool = False) -> str:
103
+ return imported_namespace + (".aio" if async_mode else "")
104
+
105
+ @staticmethod
106
+ def get_imported_namespace_for_model(imported_namespace: str) -> str:
107
+ return imported_namespace + ".models"
108
+
109
+ def get_imported_namespace_for_operation(self, imported_namespace: str, async_mode: bool = False) -> str:
110
+ module_namespace = f".{self.operations_folder_name(imported_namespace)}"
111
+ return self.get_imported_namespace_for_client(imported_namespace, async_mode) + module_namespace
112
+
113
+ # | serialize_namespace | imported_namespace | relative_import_path |
114
+ # |----------------------|----------------------|----------------------|
115
+ # |azure.test.operations | azure.test.operations| . |
116
+ # |azure.test.operations | azure.test | .. |
117
+ # |azure.test.operations | azure.test.subtest | ..subtest |
118
+ # |azure.test.operations | azure | ... |
119
+ # |azure.test.aio.operations | azure.test | ... |
120
+ # |azure.test.subtest.aio.operations|azure.test | .... |
121
+ # |azure.test |azure.test.subtest | .subtest |
122
+ def get_relative_import_path(
123
+ self,
124
+ serialize_namespace: str,
125
+ imported_namespace: Optional[str] = None,
126
+ module_name: Optional[str] = None,
127
+ ) -> str:
128
+ if imported_namespace is None:
129
+ imported_namespace = self.namespace
130
+
131
+ key = f"{serialize_namespace}-{imported_namespace}"
132
+ if key not in self._relative_import_path:
133
+ idx = 0
134
+ serialize_namespace_split = serialize_namespace.split(".")
135
+ imported_namespace_split = cast(str, imported_namespace).split(".")
136
+ while idx < min(len(serialize_namespace_split), len(imported_namespace_split)):
137
+ if serialize_namespace_split[idx] != imported_namespace_split[idx]:
138
+ break
139
+ idx += 1
140
+ self._relative_import_path[key] = "." * (len(serialize_namespace_split[idx:]) + 1) + ".".join(
141
+ imported_namespace_split[idx:]
142
+ )
143
+ result = self._relative_import_path[key]
144
+ if module_name is None:
145
+ return result
146
+ return f"{result}{module_name}" if result.endswith(".") else f"{result}.{module_name}"
147
+
148
+ @property
149
+ def need_unique_model_alias(self) -> bool:
150
+ return self.has_subnamespace and self.options["enable_typespec_namespace"]
151
+
152
+ def get_unique_models_alias(self, serialize_namespace: str, imported_namespace: str) -> str:
153
+ if not self.need_unique_model_alias:
154
+ return "_models"
155
+ relative_path = self.get_relative_import_path(
156
+ serialize_namespace, self.get_imported_namespace_for_model(imported_namespace)
157
+ )
158
+ dot_num = max(relative_path.count(".") - 1, 0)
159
+ parts = [""] + ([p for p in relative_path.split(".") if p] or ["models"])
160
+ return "_".join(parts) + (str(dot_num) if dot_num > 0 else "")
161
+
162
+ @property
163
+ def client_namespace_types(self) -> Dict[str, ClientNamespaceType]:
164
+ if not self._client_namespace_types:
165
+ # calculate client namespace types for each kind of client namespace
166
+ for client in self.clients:
167
+ if client.client_namespace not in self._client_namespace_types:
168
+ self._client_namespace_types[client.client_namespace] = ClientNamespaceType()
169
+ self._client_namespace_types[client.client_namespace].clients.append(client)
170
+ for model in self.model_types:
171
+ if model.client_namespace not in self._client_namespace_types:
172
+ self._client_namespace_types[model.client_namespace] = ClientNamespaceType()
173
+ self._client_namespace_types[model.client_namespace].models.append(model)
174
+ for enum in self.enums:
175
+ if enum.client_namespace not in self._client_namespace_types:
176
+ self._client_namespace_types[enum.client_namespace] = ClientNamespaceType()
177
+ self._client_namespace_types[enum.client_namespace].enums.append(enum)
178
+ for operation_group in get_all_operation_groups_recursively(self.clients):
179
+ if operation_group.client_namespace not in self._client_namespace_types:
180
+ self._client_namespace_types[operation_group.client_namespace] = ClientNamespaceType()
181
+ self._client_namespace_types[operation_group.client_namespace].operation_groups.append(operation_group)
182
+
183
+ # here we can check and record whether there are multi kinds of client namespace
184
+ if len(self._client_namespace_types.keys()) > 1:
185
+ self.has_subnamespace = True
186
+
187
+ # insert namespace to make sure it is continuous(e.g. ("", "azure", "azure.mgmt", "azure.mgmt.service"))
188
+ longest_namespace = sorted(self._client_namespace_types.keys())[-1]
189
+ namespace_parts = longest_namespace.split(".")
190
+ for idx in range(len(namespace_parts) + 1):
191
+ namespace = ".".join(namespace_parts[:idx])
192
+ if namespace not in self._client_namespace_types:
193
+ self._client_namespace_types[namespace] = ClientNamespaceType()
194
+ return self._client_namespace_types
74
195
 
75
196
  @property
76
197
  def has_form_data(self) -> bool:
@@ -80,17 +201,17 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
80
201
  def has_etag(self) -> bool:
81
202
  return any(client.has_etag for client in self.clients)
82
203
 
204
+ @staticmethod
205
+ def clients_has_operations(clients: List[Client]) -> bool:
206
+ return any(c for c in clients if c.has_operations)
207
+
83
208
  @property
84
209
  def has_operations(self) -> bool:
85
- if any(c for c in self.clients if c.has_operations):
86
- return True
87
- return any(c for clients in self.subnamespace_to_clients.values() for c in clients if c.has_operations)
210
+ return self.clients_has_operations(self.clients)
88
211
 
89
212
  @property
90
213
  def has_non_abstract_operations(self) -> bool:
91
- return any(c for c in self.clients if c.has_non_abstract_operations) or any(
92
- c for cs in self.subnamespace_to_clients.values() for c in cs if c.has_non_abstract_operations
93
- )
214
+ return any(c for c in self.clients if c.has_non_abstract_operations)
94
215
 
95
216
  def lookup_request_builder(self, request_builder_id: int) -> Union[RequestBuilder, OverloadedRequestBuilder]:
96
217
  """Find the request builder based off of id"""
@@ -114,31 +235,73 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
114
235
  def client_filename(self) -> str:
115
236
  return self.clients[0].filename
116
237
 
117
- def need_vendored_code(self, async_mode: bool) -> bool:
118
- """Whether we need to vendor code in the _vendor.py file for this SDK"""
119
- if self.has_abstract_operations:
120
- return True
121
- if async_mode:
122
- return self.need_mixin_abc
123
- return self.need_mixin_abc or self.has_etag or self.has_form_data
238
+ def get_clients(self, client_namespace: str) -> List[Client]:
239
+ """Get all clients in specific namespace"""
240
+ return self.client_namespace_types.get(client_namespace, ClientNamespaceType()).clients
124
241
 
125
- @property
126
- def need_mixin_abc(self) -> bool:
127
- return any(c for c in self.clients if c.has_mixin)
242
+ def is_top_namespace(self, client_namespace: str) -> bool:
243
+ """Whether the namespace is the top namespace. For example, a package named 'azure-mgmt-service',
244
+ 'azure.mgmt.service' is the top namespace.
245
+ """
246
+ return client_namespace == self.namespace
247
+
248
+ def need_vendored_code(self, async_mode: bool, client_namespace: str) -> bool:
249
+ """Whether we need to vendor code in the _vendor.py in specific namespace"""
250
+ return (
251
+ self.need_vendored_form_data(async_mode, client_namespace)
252
+ or self.need_vendored_etag(client_namespace)
253
+ or self.need_vendored_abstract(client_namespace)
254
+ or self.need_vendored_mixin(client_namespace)
255
+ )
256
+
257
+ def need_vendored_form_data(self, async_mode: bool, client_namespace: str) -> bool:
258
+ return (
259
+ (not async_mode)
260
+ and self.is_top_namespace(client_namespace)
261
+ and self.has_form_data
262
+ and self.options["models_mode"] == "dpg"
263
+ )
264
+
265
+ def need_vendored_etag(self, client_namespace: str) -> bool:
266
+ return self.is_top_namespace(client_namespace) and self.has_etag
267
+
268
+ def need_vendored_abstract(self, client_namespace: str) -> bool:
269
+ return self.is_top_namespace(client_namespace) and self.has_abstract_operations
270
+
271
+ def need_vendored_mixin(self, client_namespace: str) -> bool:
272
+ return self.has_mixin(client_namespace)
273
+
274
+ def has_mixin(self, client_namespace: str) -> bool:
275
+ return any(c for c in self.get_clients(client_namespace) if c.has_mixin)
128
276
 
129
277
  @property
130
278
  def has_abstract_operations(self) -> bool:
131
279
  return any(c for c in self.clients if c.has_abstract_operations)
132
280
 
133
- @property
134
- def operations_folder_name(self) -> str:
281
+ def operations_folder_name(self, client_namespace: str) -> str:
135
282
  """Get the name of the operations folder that holds operations."""
136
- name = "operations"
137
- if self.options["version_tolerant"] and not any(
138
- og for client in self.clients for og in client.operation_groups if not og.is_mixin
139
- ):
140
- name = f"_{name}"
141
- return name
283
+ if client_namespace not in self._operations_folder_name:
284
+ name = "operations"
285
+ operation_groups = self.client_namespace_types.get(client_namespace, ClientNamespaceType()).operation_groups
286
+ if self.options["version_tolerant"] and all(og.is_mixin for og in operation_groups):
287
+ name = f"_{name}"
288
+ self._operations_folder_name[client_namespace] = name
289
+ return self._operations_folder_name[client_namespace]
290
+
291
+ def get_serialize_namespace(
292
+ self,
293
+ client_namespace: str,
294
+ async_mode: bool = False,
295
+ client_namespace_type: NamespaceType = NamespaceType.CLIENT,
296
+ ) -> str:
297
+ """calculate the namespace for serialization from client namespace"""
298
+ if client_namespace_type == NamespaceType.CLIENT:
299
+ return client_namespace + (".aio" if async_mode else "")
300
+ if client_namespace_type == NamespaceType.MODEL:
301
+ return client_namespace + ".models"
302
+
303
+ operations_folder_name = self.operations_folder_name(client_namespace)
304
+ return client_namespace + (".aio." if async_mode else ".") + operations_folder_name
142
305
 
143
306
  @property
144
307
  def description(self) -> str:
@@ -170,9 +333,13 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
170
333
  def model_types(self, val: List[ModelType]) -> None:
171
334
  self._model_types = val
172
335
 
336
+ @staticmethod
337
+ def get_public_model_types(models: List[ModelType]) -> List[ModelType]:
338
+ return [m for m in models if not m.internal and not m.base == "json"]
339
+
173
340
  @property
174
341
  def public_model_types(self) -> List[ModelType]:
175
- return [m for m in self.model_types if not m.internal and not m.base == "json"]
342
+ return self.get_public_model_types(self.model_types)
176
343
 
177
344
  @property
178
345
  def enums(self) -> List[EnumType]:
@@ -8,6 +8,7 @@ import re
8
8
  from .imports import FileImport, ImportType, TypingSection
9
9
  from .base import BaseType
10
10
  from .model_type import ModelType
11
+ from .utils import NamespaceType
11
12
 
12
13
  if TYPE_CHECKING:
13
14
  from .code_model import CodeModel
@@ -30,9 +31,9 @@ class CombinedType(BaseType):
30
31
  self.types = types # the types that this type is combining
31
32
  self.name = yaml_data.get("name")
32
33
  self._is_union_of_literals = all(i.type == "constant" for i in self.types)
34
+ self.client_namespace: str = self.yaml_data.get("clientNamespace", code_model.namespace)
33
35
 
34
- @property
35
- def serialization_type(self) -> str:
36
+ def serialization_type(self, **kwargs: Any) -> str:
36
37
  """The tag recognized by 'msrest' as a serialization/deserialization.
37
38
 
38
39
  'str', 'int', 'float', 'bool' or
@@ -45,7 +46,7 @@ class CombinedType(BaseType):
45
46
  """
46
47
  if not all(t for t in self.types if t.type == "constant"):
47
48
  raise ValueError("Shouldn't get serialization type of a combinedtype")
48
- return self.types[0].serialization_type
49
+ return self.types[0].serialization_type(**kwargs)
49
50
 
50
51
  @property
51
52
  def client_default_value(self) -> Any:
@@ -112,9 +113,11 @@ class CombinedType(BaseType):
112
113
 
113
114
  def imports(self, **kwargs: Any) -> FileImport:
114
115
  file_import = FileImport(self.code_model)
115
- if self.name and not kwargs.get("is_types_file"):
116
+ serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
117
+ serialize_namespace_type = kwargs.get("serialize_namespace_type")
118
+ if self.name and serialize_namespace_type != NamespaceType.TYPES_FILE:
116
119
  file_import.add_submodule_import(
117
- kwargs.pop("relative_path"),
120
+ self.code_model.get_relative_import_path(serialize_namespace),
118
121
  "_types",
119
122
  ImportType.LOCAL,
120
123
  TypingSection.TYPING,
@@ -56,14 +56,13 @@ class ConstantType(BaseType):
56
56
  f"Default value is {self.get_declaration()}.",
57
57
  )
58
58
 
59
- @property
60
- def serialization_type(self) -> str:
59
+ def serialization_type(self, **kwargs: Any) -> str:
61
60
  """Returns the serialization value for msrest.
62
61
 
63
62
  :return: The serialization value for msrest
64
63
  :rtype: str
65
64
  """
66
- return self.value_type.serialization_type
65
+ return self.value_type.serialization_type(**kwargs)
67
66
 
68
67
  def docstring_text(self, **kwargs: Any) -> str:
69
68
  return "constant"