@typespec/http-client-python 0.14.1 → 0.14.3-dev.1

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 (41) hide show
  1. package/dist/emitter/emitter.js +1 -3
  2. package/dist/emitter/emitter.js.map +1 -1
  3. package/dist/emitter/lib.d.ts +1 -0
  4. package/dist/emitter/lib.d.ts.map +1 -1
  5. package/dist/emitter/lib.js +10 -0
  6. package/dist/emitter/lib.js.map +1 -1
  7. package/emitter/src/emitter.ts +1 -3
  8. package/emitter/src/lib.ts +13 -0
  9. package/emitter/temp/tsconfig.tsbuildinfo +1 -1
  10. package/eng/scripts/ci/regenerate.ts +11 -4
  11. package/eng/scripts/setup/__pycache__/package_manager.cpython-39.pyc +0 -0
  12. package/eng/scripts/setup/__pycache__/venvtools.cpython-39.pyc +0 -0
  13. package/generator/build/lib/pygen/__init__.py +19 -1
  14. package/generator/build/lib/pygen/codegen/models/code_model.py +32 -0
  15. package/generator/build/lib/pygen/codegen/models/operation.py +3 -4
  16. package/generator/build/lib/pygen/codegen/models/paging_operation.py +1 -1
  17. package/generator/build/lib/pygen/codegen/serializers/__init__.py +79 -71
  18. package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +4 -3
  19. package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +86 -13
  20. package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +1 -1
  21. package/generator/build/lib/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +109 -0
  22. package/generator/component-detection-pip-report.json +1 -1
  23. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  24. package/generator/pygen/__init__.py +19 -1
  25. package/generator/pygen/codegen/models/code_model.py +32 -0
  26. package/generator/pygen/codegen/models/operation.py +3 -4
  27. package/generator/pygen/codegen/models/paging_operation.py +1 -1
  28. package/generator/pygen/codegen/serializers/__init__.py +79 -71
  29. package/generator/pygen/codegen/serializers/builder_serializer.py +4 -3
  30. package/generator/pygen/codegen/serializers/general_serializer.py +86 -13
  31. package/generator/pygen/codegen/serializers/model_serializer.py +1 -1
  32. package/generator/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +109 -0
  33. package/generator/pygen.egg-info/SOURCES.txt +1 -0
  34. package/generator/test/azure/requirements.txt +1 -0
  35. package/generator/test/azure/tox.ini +2 -2
  36. package/generator/test/generic_mock_api_tests/asynctests/test_authentication_async.py +7 -4
  37. package/generator/test/generic_mock_api_tests/test_authentication.py +7 -4
  38. package/generator/test/unbranded/mock_api_tests/test_unbranded.py +3 -1
  39. package/generator/test/unbranded/requirements.txt +1 -0
  40. package/generator/test/unbranded/tox.ini +2 -2
  41. package/package.json +1 -1
@@ -25,10 +25,10 @@ class OptionsDict(MutableMapping):
25
25
  "azure-arm": False,
26
26
  "basic-setup-py": False,
27
27
  "client-side-validation": False,
28
- "emit-cross-language-definition-file": False,
29
28
  "flavor": "azure", # need to default to azure in shared code so we don't break swagger generation
30
29
  "from-typespec": False,
31
30
  "generate-sample": False,
31
+ "keep-setup-py": False,
32
32
  "generate-test": False,
33
33
  "head-as-boolean": True,
34
34
  "keep-version-file": False,
@@ -39,6 +39,7 @@ class OptionsDict(MutableMapping):
39
39
  "polymorphic-examples": 5,
40
40
  "validate-versioning": True,
41
41
  "version-tolerant": True,
42
+ "generation-subdir": None, # subdirectory to generate the code in
42
43
  }
43
44
 
44
45
  def __init__(self, options: Optional[Dict[str, Any]] = None) -> None:
@@ -106,6 +107,8 @@ class OptionsDict(MutableMapping):
106
107
  models_mode_default = "dpg"
107
108
  # switch to falsy value for easier code writing
108
109
  return models_mode_default
110
+ if key == "emit-cross-language-definition-file":
111
+ return self.get("flavor") == "azure"
109
112
  return self.DEFAULTS[key]
110
113
 
111
114
  def _validate_combinations(self) -> None:
@@ -134,6 +137,10 @@ class OptionsDict(MutableMapping):
134
137
  "We are working on creating a new multiapi SDK for version tolerant and it is not available yet."
135
138
  )
136
139
 
140
+ # If multiapi, do not generate default pyproject.toml
141
+ if self.get("multiapi"):
142
+ self["keep-setup-py"] = True
143
+
137
144
  if self.get("client-side-validation") and self.get("version-tolerant"):
138
145
  raise ValueError("Can not generate version tolerant with --client-side-validation. ")
139
146
 
@@ -210,6 +217,9 @@ class ReaderAndWriter:
210
217
  _LOGGER.warning("Loading python.json file. This behavior will be depreacted")
211
218
  self.options.update(python_json)
212
219
 
220
+ def get_output_folder(self) -> Path:
221
+ return self.output_folder
222
+
213
223
  def read_file(self, path: Union[str, Path]) -> str:
214
224
  """Directly reading from disk"""
215
225
  # make path relative to output folder
@@ -227,6 +237,14 @@ class ReaderAndWriter:
227
237
  with open(self.output_folder / Path(filename), "w", encoding="utf-8") as fd:
228
238
  fd.write(file_content)
229
239
 
240
+ def remove_file(self, filename: Union[str, Path]) -> None:
241
+ try:
242
+ file_path = self.output_folder / Path(filename)
243
+ if file_path.is_file():
244
+ file_path.unlink()
245
+ except FileNotFoundError:
246
+ pass
247
+
230
248
  def list_file(self) -> List[str]:
231
249
  return [str(f.relative_to(self.output_folder)) for f in self.output_folder.glob("**/*") if f.is_file()]
232
250
 
@@ -3,6 +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 pathlib import Path
6
7
  from typing import List, Dict, Any, Set, Union, Literal, Optional, cast
7
8
 
8
9
  from .base import BaseType
@@ -452,3 +453,34 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
452
453
  return self.yaml_data.get("licenseInfo", {}).get("company", "")
453
454
  # typespec azure case without custom license and swagger case
454
455
  return "Microsoft Corporation"
456
+
457
+ def get_root_dir(self) -> Path:
458
+ if self.options["no-namespace-folders"]:
459
+ # when output folder contains parts different from the namespace, we fall back to current folder directly.
460
+ return Path(".")
461
+ return Path(*self.namespace.split("."))
462
+
463
+ def get_generation_dir(self, namespace: str) -> Path:
464
+ """The directory to generate the code in.
465
+
466
+ If 'generation-subdir' is specified, it will be used as a subdirectory.
467
+ """
468
+ root_dir = self.get_root_dir()
469
+ retval = self._get_relative_generation_dir(root_dir, namespace)
470
+ return retval
471
+
472
+ def _get_relative_generation_dir(self, root_dir: Path, namespace: str) -> Path:
473
+ if self.options["no-namespace-folders"]:
474
+ return Path(".")
475
+ if self.options.get("generation-subdir"):
476
+ # For the main namespace, return root_dir + generation-subdir
477
+ if namespace in ("", self.namespace):
478
+ return root_dir / self.options["generation-subdir"]
479
+
480
+ # For subnamespaces, extract the subnamespace part and append it to generation-subdir
481
+ if namespace.startswith(self.namespace + "."):
482
+ subnamespace_parts = namespace[len(self.namespace) + 1 :].split(".")
483
+ return root_dir / self.options["generation-subdir"] / Path(*subnamespace_parts)
484
+
485
+ # No generation-subdir specified, use the namespace path directly
486
+ return Path(*namespace.split("."))
@@ -194,14 +194,13 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
194
194
  def any_response_has_headers(self) -> bool:
195
195
  return any(response.headers for response in self.responses)
196
196
 
197
- @property
198
- def default_error_deserialization(self) -> Optional[str]:
197
+ def default_error_deserialization(self, serialize_namespace: str) -> Optional[str]:
199
198
  default_exceptions = [e for e in self.exceptions if "default" in e.status_codes and e.type]
200
199
  if not default_exceptions:
201
200
  return None
202
201
  exception_schema = default_exceptions[0].type
203
202
  if isinstance(exception_schema, ModelType):
204
- return exception_schema.type_annotation(skip_quote=True)
203
+ return exception_schema.type_annotation(skip_quote=True, serialize_namespace=serialize_namespace)
205
204
  return None if self.code_model.options["models-mode"] == "dpg" else "'object'"
206
205
 
207
206
  @property
@@ -469,7 +468,7 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
469
468
  file_import.add_submodule_import(relative_path, "_deserialize_xml", ImportType.LOCAL)
470
469
  elif self.need_deserialize:
471
470
  file_import.add_submodule_import(relative_path, "_deserialize", ImportType.LOCAL)
472
- if self.default_error_deserialization or self.non_default_errors:
471
+ if self.default_error_deserialization(serialize_namespace) or self.non_default_errors:
473
472
  file_import.add_submodule_import(relative_path, "_failsafe_deserialize", ImportType.LOCAL)
474
473
  return file_import
475
474
 
@@ -173,7 +173,7 @@ class PagingOperationBase(OperationBase[PagingResponseType]):
173
173
  serialize_namespace, module_name="_utils.model_base"
174
174
  )
175
175
  file_import.merge(self.item_type.imports(**kwargs))
176
- if self.default_error_deserialization or self.need_deserialize:
176
+ if self.default_error_deserialization(serialize_namespace) or self.need_deserialize:
177
177
  file_import.add_submodule_import(relative_path, "_deserialize", ImportType.LOCAL)
178
178
  return file_import
179
179
 
@@ -9,6 +9,7 @@ from collections import namedtuple
9
9
  import re
10
10
  from typing import List, Any, Union
11
11
  from pathlib import Path
12
+ from packaging.version import parse as parse_version
12
13
  from jinja2 import PackageLoader, Environment, FileSystemLoader, StrictUndefined
13
14
 
14
15
  from ... import ReaderAndWriter
@@ -52,10 +53,9 @@ _PACKAGE_FILES = [
52
53
  "LICENSE.jinja2",
53
54
  "MANIFEST.in.jinja2",
54
55
  "README.md.jinja2",
55
- "setup.py.jinja2",
56
56
  ]
57
57
 
58
- _REGENERATE_FILES = {"setup.py", "MANIFEST.in"}
58
+ _REGENERATE_FILES = {"MANIFEST.in"}
59
59
  AsyncInfo = namedtuple("AsyncInfo", ["async_mode", "async_path"])
60
60
 
61
61
 
@@ -80,6 +80,15 @@ class JinjaSerializer(ReaderAndWriter):
80
80
  ) -> None:
81
81
  super().__init__(output_folder=output_folder, **kwargs)
82
82
  self.code_model = code_model
83
+ self._regenerate_setup_py()
84
+
85
+ def _regenerate_setup_py(self):
86
+ if self.code_model.options["keep-setup-py"] or self.code_model.options["basic-setup-py"]:
87
+ _PACKAGE_FILES.append("setup.py.jinja2")
88
+ _REGENERATE_FILES.add("setup.py")
89
+ else:
90
+ _PACKAGE_FILES.append("pyproject.toml.jinja2")
91
+ _REGENERATE_FILES.add("pyproject.toml")
83
92
 
84
93
  @property
85
94
  def has_aio_folder(self) -> bool:
@@ -101,12 +110,18 @@ class JinjaSerializer(ReaderAndWriter):
101
110
  return True
102
111
  # If the version file is already there and the version is greater than the current version, keep it.
103
112
  try:
104
- serialized_version_file = self.read_file(self.exec_path(self.code_model.namespace) / "_version.py")
113
+ serialized_version_file = self.read_file(
114
+ self.code_model.get_generation_dir(self.code_model.namespace) / "_version.py"
115
+ )
105
116
  match = re.search(r'VERSION\s*=\s*"([^"]+)"', str(serialized_version_file))
106
117
  serialized_version = match.group(1) if match else ""
107
118
  except (FileNotFoundError, IndexError):
108
119
  serialized_version = ""
109
- return serialized_version > self.code_model.options.get("package-version", "")
120
+ try:
121
+ return parse_version(serialized_version) > parse_version(self.code_model.options.get("package-version", ""))
122
+ except Exception: # pylint: disable=broad-except
123
+ # If parsing the version fails, we assume the version file is not valid and overwrite.
124
+ return False
110
125
 
111
126
  def serialize(self) -> None:
112
127
  env = Environment(
@@ -120,34 +135,37 @@ class JinjaSerializer(ReaderAndWriter):
120
135
 
121
136
  general_serializer = GeneralSerializer(code_model=self.code_model, env=env, async_mode=False)
122
137
  for client_namespace, client_namespace_type in self.code_model.client_namespace_types.items():
123
- exec_path = self.exec_path(client_namespace)
138
+ generation_path = self.code_model.get_generation_dir(client_namespace)
124
139
  if client_namespace == "":
125
- # Write the setup file
126
140
  if self.code_model.options["basic-setup-py"]:
127
- self.write_file(exec_path / Path("setup.py"), general_serializer.serialize_setup_file())
141
+ # Write the setup file
142
+ self.write_file(generation_path / Path("setup.py"), general_serializer.serialize_setup_file())
143
+ elif not self.code_model.options["keep-setup-py"]:
144
+ # remove setup.py file
145
+ self.remove_file(generation_path / Path("setup.py"))
128
146
 
129
147
  # add packaging files in root namespace (e.g. setup.py, README.md, etc.)
130
148
  if self.code_model.options.get("package-mode"):
131
- self._serialize_and_write_package_files(client_namespace)
149
+ self._serialize_and_write_package_files()
132
150
 
133
151
  # write apiview-properties.json
134
152
  if self.code_model.options.get("emit-cross-language-definition-file"):
135
153
  self.write_file(
136
- exec_path / Path("apiview-properties.json"),
154
+ generation_path / Path("apiview-properties.json"),
137
155
  general_serializer.serialize_cross_language_definition_file(),
138
156
  )
139
157
 
140
158
  # add generated samples and generated tests
141
159
  if self.code_model.options["show-operations"] and self.code_model.has_operations:
142
160
  if self.code_model.options["generate-sample"]:
143
- self._serialize_and_write_sample(env, namespace=client_namespace)
161
+ self._serialize_and_write_sample(env)
144
162
  if self.code_model.options["generate-test"]:
145
- self._serialize_and_write_test(env, namespace=client_namespace)
163
+ self._serialize_and_write_test(env)
146
164
 
147
165
  # add _metadata.json
148
166
  if self.code_model.metadata:
149
167
  self.write_file(
150
- exec_path / Path("_metadata.json"),
168
+ generation_path / Path("_metadata.json"),
151
169
  json.dumps(self.code_model.metadata, indent=2),
152
170
  )
153
171
  elif client_namespace_type.clients:
@@ -156,7 +174,7 @@ class JinjaSerializer(ReaderAndWriter):
156
174
  else:
157
175
  # add pkgutil init file if no clients in this namespace
158
176
  self.write_file(
159
- exec_path / Path("__init__.py"),
177
+ generation_path / Path("__init__.py"),
160
178
  general_serializer.serialize_pkgutil_init_file(),
161
179
  )
162
180
 
@@ -178,7 +196,7 @@ class JinjaSerializer(ReaderAndWriter):
178
196
 
179
197
  if not self.code_model.options["models-mode"]:
180
198
  # keep models file if users ended up just writing a models file
181
- model_path = exec_path / Path("models.py")
199
+ model_path = generation_path / Path("models.py")
182
200
  if self.read_file(model_path):
183
201
  self.write_file(model_path, self.read_file(model_path))
184
202
 
@@ -194,12 +212,15 @@ class JinjaSerializer(ReaderAndWriter):
194
212
  # to make sure all generated files could be packed into .zip/.whl/.tgz package
195
213
  if not client_namespace_type.clients and client_namespace_type.operation_groups and self.has_aio_folder:
196
214
  self.write_file(
197
- exec_path / Path("aio/__init__.py"),
215
+ generation_path / Path("aio/__init__.py"),
198
216
  general_serializer.serialize_pkgutil_init_file(),
199
217
  )
200
218
 
201
- def _serialize_and_write_package_files(self, client_namespace: str) -> None:
202
- root_of_sdk = self.exec_path(client_namespace)
219
+ def _serialize_and_write_package_files(self) -> None:
220
+ root_of_sdk = Path(".")
221
+ if self.code_model.options["no-namespace-folders"]:
222
+ compensation = Path("../" * (self.code_model.namespace.count(".") + 1))
223
+ root_of_sdk = root_of_sdk / compensation
203
224
  if self.code_model.options["package-mode"] in VALID_PACKAGE_MODE:
204
225
  env = Environment(
205
226
  loader=PackageLoader("pygen.codegen", "templates/packaging_templates"),
@@ -228,12 +249,13 @@ class JinjaSerializer(ReaderAndWriter):
228
249
  file = template_name.replace(".jinja2", "")
229
250
  output_name = root_of_sdk / file
230
251
  if not self.read_file(output_name) or file in _REGENERATE_FILES:
231
- if self.keep_version_file and file == "setup.py":
232
- # don't regenerate setup.py file if the version file is more up to date
252
+ if self.keep_version_file and file == "setup.py" and not self.code_model.options["azure-arm"]:
253
+ # don't regenerate setup.py file if the version file is more up to date for data-plane
233
254
  continue
255
+ file_path = self.get_output_folder() / Path(output_name)
234
256
  self.write_file(
235
257
  output_name,
236
- serializer.serialize_package_file(template_name, **params),
258
+ serializer.serialize_package_file(template_name, file_path, **params),
237
259
  )
238
260
 
239
261
  def _keep_patch_file(self, path_file: Path, env: Environment):
@@ -249,7 +271,7 @@ class JinjaSerializer(ReaderAndWriter):
249
271
  self, env: Environment, namespace: str, models: List[ModelType], enums: List[EnumType]
250
272
  ) -> None:
251
273
  # Write the models folder
252
- models_path = self.exec_path(namespace) / "models"
274
+ models_path = self.code_model.get_generation_dir(namespace) / "models"
253
275
  serializer = DpgModelSerializer if self.code_model.options["models-mode"] == "dpg" else MsrestModelSerializer
254
276
  if self.code_model.has_non_json_models(models):
255
277
  self.write_file(
@@ -317,7 +339,7 @@ class JinjaSerializer(ReaderAndWriter):
317
339
  self, operation_groups: List[OperationGroup], env: Environment, namespace: str
318
340
  ) -> None:
319
341
  operations_folder_name = self.code_model.operations_folder_name(namespace)
320
- exec_path = self.exec_path(namespace)
342
+ generation_path = self.code_model.get_generation_dir(namespace)
321
343
  for async_mode, async_path in self.serialize_loop:
322
344
  prefix_path = f"{async_path}{operations_folder_name}"
323
345
  # write init file
@@ -325,7 +347,7 @@ class JinjaSerializer(ReaderAndWriter):
325
347
  code_model=self.code_model, operation_groups=operation_groups, env=env, async_mode=async_mode
326
348
  )
327
349
  self.write_file(
328
- exec_path / Path(f"{prefix_path}/__init__.py"),
350
+ generation_path / Path(f"{prefix_path}/__init__.py"),
329
351
  operations_init_serializer.serialize(),
330
352
  )
331
353
 
@@ -344,26 +366,25 @@ class JinjaSerializer(ReaderAndWriter):
344
366
  client_namespace=namespace,
345
367
  )
346
368
  self.write_file(
347
- exec_path / Path(f"{prefix_path}/{filename}.py"),
369
+ generation_path / Path(f"{prefix_path}/{filename}.py"),
348
370
  operation_group_serializer.serialize(),
349
371
  )
350
372
 
351
373
  # if there was a patch file before, we keep it
352
- self._keep_patch_file(exec_path / Path(f"{prefix_path}/_patch.py"), env)
374
+ self._keep_patch_file(generation_path / Path(f"{prefix_path}/_patch.py"), env)
353
375
 
354
376
  def _serialize_and_write_version_file(
355
377
  self,
356
- namespace: str,
357
378
  general_serializer: GeneralSerializer,
358
379
  ):
359
- exec_path = self.exec_path(namespace)
380
+ generation_path = self.code_model.get_root_dir()
360
381
 
361
382
  def _read_version_file(original_version_file_name: str) -> str:
362
- return self.read_file(exec_path / original_version_file_name)
383
+ return self.read_file(generation_path / original_version_file_name)
363
384
 
364
385
  def _write_version_file(original_version_file_name: str) -> None:
365
386
  self.write_file(
366
- exec_path / Path("_version.py"),
387
+ generation_path / Path("_version.py"),
367
388
  _read_version_file(original_version_file_name),
368
389
  )
369
390
 
@@ -373,7 +394,7 @@ class JinjaSerializer(ReaderAndWriter):
373
394
  _write_version_file(original_version_file_name="version.py")
374
395
  elif self.code_model.options.get("package-version"):
375
396
  self.write_file(
376
- exec_path / Path("_version.py"),
397
+ generation_path / Path("_version.py"),
377
398
  general_serializer.serialize_version_file(),
378
399
  )
379
400
 
@@ -383,47 +404,47 @@ class JinjaSerializer(ReaderAndWriter):
383
404
  clients: List[Client],
384
405
  env: Environment,
385
406
  ) -> None:
386
- exec_path = self.exec_path(namespace)
407
+ generation_path = self.code_model.get_generation_dir(namespace)
387
408
  for async_mode, async_path in self.serialize_loop:
388
409
  general_serializer = GeneralSerializer(
389
410
  code_model=self.code_model, env=env, async_mode=async_mode, client_namespace=namespace
390
411
  )
391
412
  # when there is client.py, there must be __init__.py
392
413
  self.write_file(
393
- exec_path / Path(f"{async_path}__init__.py"),
414
+ generation_path / Path(f"{async_path}__init__.py"),
394
415
  general_serializer.serialize_init_file([c for c in clients if c.has_operations]),
395
416
  )
396
417
 
397
418
  # if there was a patch file before, we keep it
398
- self._keep_patch_file(exec_path / Path(f"{async_path}_patch.py"), env)
419
+ self._keep_patch_file(generation_path / Path(f"{async_path}_patch.py"), env)
399
420
 
400
421
  if self.code_model.clients_has_operations(clients):
401
422
 
402
423
  # write client file
403
424
  self.write_file(
404
- exec_path / Path(f"{async_path}{self.code_model.client_filename}.py"),
425
+ generation_path / Path(f"{async_path}{self.code_model.client_filename}.py"),
405
426
  general_serializer.serialize_service_client_file(clients),
406
427
  )
407
428
 
408
429
  # write config file
409
430
  self.write_file(
410
- exec_path / Path(f"{async_path}_configuration.py"),
431
+ generation_path / Path(f"{async_path}_configuration.py"),
411
432
  general_serializer.serialize_config_file(clients),
412
433
  )
413
434
 
414
435
  # sometimes we need define additional Mixin class for client in _utils.py
415
436
  self._serialize_and_write_utils_folder(env, namespace)
416
437
 
417
- def _serialize_and_write_utils_folder(self, env: Environment, namespace: str) -> None:
418
- exec_path = self.exec_path(namespace)
438
+ def _serialize_and_write_utils_folder(self, env: Environment, namespace: str):
439
+ generation_dir = self.code_model.get_generation_dir(namespace)
419
440
  general_serializer = GeneralSerializer(code_model=self.code_model, env=env, async_mode=False)
420
- utils_folder_path = exec_path / Path("_utils")
421
- if self.code_model.need_utils_folder(async_mode=False, client_namespace=namespace):
441
+ utils_folder_path = generation_dir / Path("_utils")
442
+ if self.code_model.need_utils_folder(async_mode=False, client_namespace=self.code_model.namespace):
422
443
  self.write_file(
423
444
  utils_folder_path / Path("__init__.py"),
424
445
  self.code_model.license_header,
425
446
  )
426
- if self.code_model.need_utils_utils(async_mode=False, client_namespace=namespace):
447
+ if self.code_model.need_utils_utils(async_mode=False, client_namespace=self.code_model.namespace):
427
448
  self.write_file(
428
449
  utils_folder_path / Path("utils.py"),
429
450
  general_serializer.need_utils_utils_file(),
@@ -443,56 +464,43 @@ class JinjaSerializer(ReaderAndWriter):
443
464
  )
444
465
 
445
466
  def _serialize_and_write_top_level_folder(self, env: Environment, namespace: str) -> None:
446
- exec_path = self.exec_path(namespace)
467
+ root_dir = self.code_model.get_root_dir()
447
468
  # write _utils folder
448
- self._serialize_and_write_utils_folder(env, namespace)
469
+ self._serialize_and_write_utils_folder(env, self.code_model.namespace)
449
470
 
450
471
  general_serializer = GeneralSerializer(code_model=self.code_model, env=env, async_mode=False)
451
472
 
452
473
  # write _version.py
453
- self._serialize_and_write_version_file(namespace, general_serializer)
474
+ self._serialize_and_write_version_file(general_serializer)
454
475
 
455
476
  # write the empty py.typed file
456
- self.write_file(exec_path / Path("py.typed"), "# Marker file for PEP 561.")
477
+ pytyped_value = "# Marker file for PEP 561."
478
+ # TODO: remove this when we remove legacy multiapi generation
479
+ if self.code_model.options["multiapi"]:
480
+ self.write_file(self.code_model.get_generation_dir(namespace) / Path("py.typed"), pytyped_value)
481
+ else:
482
+ self.write_file(root_dir / Path("py.typed"), pytyped_value)
457
483
 
458
484
  # write _validation.py
459
485
  if any(og for client in self.code_model.clients for og in client.operation_groups if og.need_validation):
460
486
  self.write_file(
461
- exec_path / Path("_validation.py"),
487
+ root_dir / Path("_validation.py"),
462
488
  general_serializer.serialize_validation_file(),
463
489
  )
464
490
 
465
491
  # write _types.py
466
492
  if self.code_model.named_unions:
467
493
  self.write_file(
468
- exec_path / Path("_types.py"),
494
+ root_dir / Path("_types.py"),
469
495
  TypesSerializer(code_model=self.code_model, env=env).serialize(),
470
496
  )
471
497
 
472
498
  def _serialize_and_write_metadata(self, env: Environment, namespace: str) -> None:
473
- metadata_serializer = MetadataSerializer(self.code_model, env, client_namespace=namespace)
474
- self.write_file(self.exec_path(namespace) / Path("_metadata.json"), metadata_serializer.serialize())
475
-
476
- @property
477
- def exec_path_compensation(self) -> Path:
478
- """Assume the process is running in the root folder of the package. If not, we need the path compensation."""
479
- return (
480
- Path("../" * (self.code_model.namespace.count(".") + 1))
481
- if self.code_model.options["no-namespace-folders"]
482
- else Path(".")
499
+ metadata_serializer = MetadataSerializer(self.code_model, env)
500
+ self.write_file(
501
+ self.code_model.get_generation_dir(namespace) / Path("_metadata.json"), metadata_serializer.serialize()
483
502
  )
484
503
 
485
- def exec_path_for_test_sample(self, namespace: str) -> Path:
486
- return self.exec_path_compensation / Path(*namespace.split("."))
487
-
488
- # pylint: disable=line-too-long
489
- def exec_path(self, namespace: str) -> Path:
490
- if self.code_model.options["no-namespace-folders"] and not self.code_model.options["multiapi"]:
491
- # when output folder contains parts different from the namespace, we fall back to current folder directly.
492
- # (e.g. https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/communication/azure-communication-callautomation/swagger/SWAGGER.md)
493
- return Path(".")
494
- return self.exec_path_compensation / Path(*namespace.split("."))
495
-
496
504
  # pylint: disable=line-too-long
497
505
  @property
498
506
  def sample_additional_folder(self) -> Path:
@@ -511,8 +519,8 @@ class JinjaSerializer(ReaderAndWriter):
511
519
  return Path("/".join(namespace_config.split(".")[num_of_package_namespace:]))
512
520
  return Path("")
513
521
 
514
- def _serialize_and_write_sample(self, env: Environment, namespace: str):
515
- out_path = self.exec_path_for_test_sample(namespace) / Path("generated_samples")
522
+ def _serialize_and_write_sample(self, env: Environment):
523
+ out_path = Path("./generated_samples")
516
524
  for client in self.code_model.clients:
517
525
  for op_group in client.operation_groups:
518
526
  for operation in op_group.operations:
@@ -544,9 +552,9 @@ class JinjaSerializer(ReaderAndWriter):
544
552
  log_error = f"error happens in sample {file}: {e}"
545
553
  _LOGGER.error(log_error)
546
554
 
547
- def _serialize_and_write_test(self, env: Environment, namespace: str):
555
+ def _serialize_and_write_test(self, env: Environment):
548
556
  self.code_model.for_test = True
549
- out_path = self.exec_path_for_test_sample(namespace) / Path("generated_tests")
557
+ out_path = Path("./generated_tests")
550
558
  general_serializer = TestGeneralSerializer(code_model=self.code_model, env=env)
551
559
  self.write_file(out_path / "conftest.py", general_serializer.serialize_conftest())
552
560
  if not self.code_model.options["azure-arm"]:
@@ -1079,18 +1079,19 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
1079
1079
  )
1080
1080
  condition = "elif"
1081
1081
  # default error handling
1082
- if builder.default_error_deserialization and self.code_model.options["models-mode"]:
1082
+ default_error_deserialization = builder.default_error_deserialization(self.serialize_namespace)
1083
+ if default_error_deserialization and self.code_model.options["models-mode"]:
1083
1084
  error_model = ", model=error"
1084
1085
  indent = " " if builder.non_default_errors else " "
1085
1086
  if builder.non_default_errors:
1086
1087
  retval.append(" else:")
1087
1088
  if self.code_model.options["models-mode"] == "dpg":
1088
1089
  retval.append(
1089
- f"{indent}error = _failsafe_deserialize({builder.default_error_deserialization}, response.json())"
1090
+ f"{indent}error = _failsafe_deserialize({default_error_deserialization}, response.json())"
1090
1091
  )
1091
1092
  else:
1092
1093
  retval.append(
1093
- f"{indent}error = self._deserialize.failsafe_deserialize({builder.default_error_deserialization}, "
1094
+ f"{indent}error = self._deserialize.failsafe_deserialize({default_error_deserialization}, "
1094
1095
  "pipeline_response)"
1095
1096
  )
1096
1097
  retval.append(