@autorest/python 6.37.2 → 6.38.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 (26) hide show
  1. package/autorest/__init__.py +13 -0
  2. package/autorest/codegen.py +1 -0
  3. package/generator/build/lib/pygen/__init__.py +29 -2
  4. package/generator/build/lib/pygen/codegen/models/code_model.py +32 -0
  5. package/generator/build/lib/pygen/codegen/models/enum_type.py +4 -1
  6. package/generator/build/lib/pygen/codegen/models/model_type.py +4 -1
  7. package/generator/build/lib/pygen/codegen/models/operation.py +3 -4
  8. package/generator/build/lib/pygen/codegen/models/paging_operation.py +1 -1
  9. package/generator/build/lib/pygen/codegen/serializers/__init__.py +85 -74
  10. package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +4 -3
  11. package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +86 -13
  12. package/generator/build/lib/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +109 -0
  13. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  14. package/generator/pygen/__init__.py +29 -2
  15. package/generator/pygen/codegen/models/code_model.py +32 -0
  16. package/generator/pygen/codegen/models/enum_type.py +4 -1
  17. package/generator/pygen/codegen/models/model_type.py +4 -1
  18. package/generator/pygen/codegen/models/operation.py +3 -4
  19. package/generator/pygen/codegen/models/paging_operation.py +1 -1
  20. package/generator/pygen/codegen/serializers/__init__.py +85 -74
  21. package/generator/pygen/codegen/serializers/builder_serializer.py +4 -3
  22. package/generator/pygen/codegen/serializers/general_serializer.py +86 -13
  23. package/generator/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +109 -0
  24. package/generator/pygen.egg-info/SOURCES.txt +1 -0
  25. package/package.json +2 -2
  26. package/scripts/__pycache__/venvtools.cpython-310.pyc +0 -0
@@ -22,12 +22,25 @@ class ReaderAndWriterAutorest(ReaderAndWriter):
22
22
  super().__init__(output_folder=output_folder)
23
23
  self._autorestapi = autorestapi
24
24
 
25
+ def get_output_folder(self) -> Path:
26
+ # Get the output folder from AutoRest configuration to resolve against the correct base and
27
+ # convert URI to file system path by removing file:// prefix if present
28
+ return Path(self._autorestapi.get_value("outputFolderUri").lstrip("file:"))
29
+
25
30
  def read_file(self, path: Union[str, Path]) -> str:
26
31
  return self._autorestapi.read_file(path)
27
32
 
28
33
  def write_file(self, filename: Union[str, Path], file_content: str) -> None:
29
34
  return self._autorestapi.write_file(filename, file_content)
30
35
 
36
+ def remove_file(self, filename: Union[str, Path]) -> None:
37
+ try:
38
+ file_path = self.get_output_folder() / Path(filename)
39
+ if file_path.is_file():
40
+ file_path.unlink()
41
+ except (FileNotFoundError, OSError):
42
+ pass
43
+
31
44
  def list_file(self) -> List[str]:
32
45
  return self._autorestapi.list_inputs()
33
46
 
@@ -64,6 +64,7 @@ class CodeGeneratorAutorest(CodeGenerator, PluginAutorest):
64
64
  "no-async": self._autorestapi.get_boolean_value("no-async"),
65
65
  "no-namespace-folders": self._autorestapi.get_boolean_value("no-namespace-folders"),
66
66
  "basic-setup-py": self._autorestapi.get_boolean_value("basic-setup-py"),
67
+ "keep-setup-py": self._autorestapi.get_boolean_value("keep-setup-py"),
67
68
  "package-name": self._autorestapi.get_value("package-name"),
68
69
  "package-version": self._autorestapi.get_value("package-version"),
69
70
  "client-side-validation": self._autorestapi.get_boolean_value("client-side-validation"),
@@ -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,13 +39,14 @@ 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:
45
46
  self._data = options.copy() if options else {}
46
47
  self._validate_combinations()
47
48
 
48
- def __getitem__(self, key: str) -> Any:
49
+ def __getitem__(self, key: str) -> Any: # pylint: disable=too-many-return-statements
49
50
  if key == "head-as-boolean" and self.get("azure-arm"):
50
51
  # override to always true if azure-arm is set
51
52
  return True
@@ -60,6 +61,15 @@ class OptionsDict(MutableMapping):
60
61
  if key == "package-mode" and self._data.get("packaging-files-dir"):
61
62
  # if packaging-files-dir is set, use it as package-mode
62
63
  return self._data["packaging-files-dir"]
64
+ if key == "generation-subdir":
65
+ data = self._data.get("generation-subdir")
66
+ if data:
67
+ # Remove leading dot or ./ from generation-subdir
68
+ if data.startswith("./"):
69
+ data = data[2:]
70
+ elif data.startswith("."):
71
+ data = data[1:]
72
+ return data
63
73
  return self._get_default(key)
64
74
 
65
75
  def __setitem__(self, key: str, value: Any) -> None:
@@ -106,6 +116,8 @@ class OptionsDict(MutableMapping):
106
116
  models_mode_default = "dpg"
107
117
  # switch to falsy value for easier code writing
108
118
  return models_mode_default
119
+ if key == "emit-cross-language-definition-file":
120
+ return self.get("flavor") == "azure"
109
121
  return self.DEFAULTS[key]
110
122
 
111
123
  def _validate_combinations(self) -> None:
@@ -134,6 +146,10 @@ class OptionsDict(MutableMapping):
134
146
  "We are working on creating a new multiapi SDK for version tolerant and it is not available yet."
135
147
  )
136
148
 
149
+ # If multiapi, do not generate default pyproject.toml
150
+ if self.get("multiapi"):
151
+ self["keep-setup-py"] = True
152
+
137
153
  if self.get("client-side-validation") and self.get("version-tolerant"):
138
154
  raise ValueError("Can not generate version tolerant with --client-side-validation. ")
139
155
 
@@ -210,6 +226,9 @@ class ReaderAndWriter:
210
226
  _LOGGER.warning("Loading python.json file. This behavior will be depreacted")
211
227
  self.options.update(python_json)
212
228
 
229
+ def get_output_folder(self) -> Path:
230
+ return self.output_folder
231
+
213
232
  def read_file(self, path: Union[str, Path]) -> str:
214
233
  """Directly reading from disk"""
215
234
  # make path relative to output folder
@@ -227,6 +246,14 @@ class ReaderAndWriter:
227
246
  with open(self.output_folder / Path(filename), "w", encoding="utf-8") as fd:
228
247
  fd.write(file_content)
229
248
 
249
+ def remove_file(self, filename: Union[str, Path]) -> None:
250
+ try:
251
+ file_path = self.output_folder / Path(filename)
252
+ if file_path.is_file():
253
+ file_path.unlink()
254
+ except FileNotFoundError:
255
+ pass
256
+
230
257
  def list_file(self) -> List[str]:
231
258
  return [str(f.relative_to(self.output_folder)) for f in self.output_folder.glob("**/*") if f.is_file()]
232
259
 
@@ -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("."))
@@ -52,7 +52,10 @@ class EnumValue(BaseType):
52
52
  """The python type used for RST syntax input and type annotation."""
53
53
 
54
54
  type_annotation = self.value_type.type_annotation(**kwargs)
55
- enum_type_annotation = f"{self.enum_type.client_namespace}.models.{self.name}"
55
+ client_namespace = self.enum_type.client_namespace
56
+ if self.code_model.options.get("generation-subdir"):
57
+ client_namespace += f".{self.code_model.options['generation-subdir']}"
58
+ enum_type_annotation = f"{client_namespace}.models.{self.name}"
56
59
  return f"{type_annotation} or ~{enum_type_annotation}"
57
60
 
58
61
  def get_json_template_representation(
@@ -292,7 +292,10 @@ class GeneratedModelType(ModelType):
292
292
 
293
293
  def docstring_type(self, **kwargs: Any) -> str:
294
294
  type_annotation = self.type_annotation(need_model_alias=False, skip_quote=True, **kwargs)
295
- return f"~{self.client_namespace}.models.{type_annotation}"
295
+ client_namespace = self.client_namespace
296
+ if self.code_model.options.get("generation-subdir"):
297
+ client_namespace += f".{self.code_model.options['generation-subdir']}"
298
+ return f"~{client_namespace}.models.{type_annotation}"
296
299
 
297
300
  def docstring_text(self, **kwargs: Any) -> str:
298
301
  return self.name
@@ -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
 
@@ -7,8 +7,9 @@ import logging
7
7
  import json
8
8
  from collections import namedtuple
9
9
  import re
10
- from typing import List, Any, Union
10
+ from typing import List, Any, Union, Optional
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
+ 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"),
@@ -231,9 +252,10 @@ class JinjaSerializer(ReaderAndWriter):
231
252
  if self.keep_version_file and file == "setup.py" and not self.code_model.options["azure-arm"]:
232
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,29 @@ 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,
379
+ namespace: Optional[str] = None,
358
380
  ):
359
- exec_path = self.exec_path(namespace)
381
+ if namespace:
382
+ generation_path = self.code_model.get_generation_dir(namespace)
383
+ else:
384
+ generation_path = self.code_model.get_root_dir()
360
385
 
361
386
  def _read_version_file(original_version_file_name: str) -> str:
362
- return self.read_file(exec_path / original_version_file_name)
387
+ return self.read_file(generation_path / original_version_file_name)
363
388
 
364
389
  def _write_version_file(original_version_file_name: str) -> None:
365
390
  self.write_file(
366
- exec_path / Path("_version.py"),
391
+ generation_path / Path("_version.py"),
367
392
  _read_version_file(original_version_file_name),
368
393
  )
369
394
 
@@ -373,7 +398,7 @@ class JinjaSerializer(ReaderAndWriter):
373
398
  _write_version_file(original_version_file_name="version.py")
374
399
  elif self.code_model.options.get("package-version"):
375
400
  self.write_file(
376
- exec_path / Path("_version.py"),
401
+ generation_path / Path("_version.py"),
377
402
  general_serializer.serialize_version_file(),
378
403
  )
379
404
 
@@ -383,47 +408,47 @@ class JinjaSerializer(ReaderAndWriter):
383
408
  clients: List[Client],
384
409
  env: Environment,
385
410
  ) -> None:
386
- exec_path = self.exec_path(namespace)
411
+ generation_path = self.code_model.get_generation_dir(namespace)
387
412
  for async_mode, async_path in self.serialize_loop:
388
413
  general_serializer = GeneralSerializer(
389
414
  code_model=self.code_model, env=env, async_mode=async_mode, client_namespace=namespace
390
415
  )
391
416
  # when there is client.py, there must be __init__.py
392
417
  self.write_file(
393
- exec_path / Path(f"{async_path}__init__.py"),
418
+ generation_path / Path(f"{async_path}__init__.py"),
394
419
  general_serializer.serialize_init_file([c for c in clients if c.has_operations]),
395
420
  )
396
421
 
397
422
  # if there was a patch file before, we keep it
398
- self._keep_patch_file(exec_path / Path(f"{async_path}_patch.py"), env)
423
+ self._keep_patch_file(generation_path / Path(f"{async_path}_patch.py"), env)
399
424
 
400
425
  if self.code_model.clients_has_operations(clients):
401
426
 
402
427
  # write client file
403
428
  self.write_file(
404
- exec_path / Path(f"{async_path}{self.code_model.client_filename}.py"),
429
+ generation_path / Path(f"{async_path}{self.code_model.client_filename}.py"),
405
430
  general_serializer.serialize_service_client_file(clients),
406
431
  )
407
432
 
408
433
  # write config file
409
434
  self.write_file(
410
- exec_path / Path(f"{async_path}_configuration.py"),
435
+ generation_path / Path(f"{async_path}_configuration.py"),
411
436
  general_serializer.serialize_config_file(clients),
412
437
  )
413
438
 
414
439
  # sometimes we need define additional Mixin class for client in _utils.py
415
440
  self._serialize_and_write_utils_folder(env, namespace)
416
441
 
417
- def _serialize_and_write_utils_folder(self, env: Environment, namespace: str) -> None:
418
- exec_path = self.exec_path(namespace)
442
+ def _serialize_and_write_utils_folder(self, env: Environment, namespace: str):
443
+ generation_dir = self.code_model.get_generation_dir(namespace)
419
444
  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):
445
+ utils_folder_path = generation_dir / Path("_utils")
446
+ if self.code_model.need_utils_folder(async_mode=False, client_namespace=self.code_model.namespace):
422
447
  self.write_file(
423
448
  utils_folder_path / Path("__init__.py"),
424
449
  self.code_model.license_header,
425
450
  )
426
- if self.code_model.need_utils_utils(async_mode=False, client_namespace=namespace):
451
+ if self.code_model.need_utils_utils(async_mode=False, client_namespace=self.code_model.namespace):
427
452
  self.write_file(
428
453
  utils_folder_path / Path("utils.py"),
429
454
  general_serializer.need_utils_utils_file(),
@@ -443,60 +468,46 @@ class JinjaSerializer(ReaderAndWriter):
443
468
  )
444
469
 
445
470
  def _serialize_and_write_top_level_folder(self, env: Environment, namespace: str) -> None:
446
- exec_path = self.exec_path(namespace)
471
+ root_dir = self.code_model.get_root_dir()
447
472
  # write _utils folder
448
- self._serialize_and_write_utils_folder(env, namespace)
473
+ self._serialize_and_write_utils_folder(env, self.code_model.namespace)
449
474
 
450
475
  general_serializer = GeneralSerializer(code_model=self.code_model, env=env, async_mode=False)
451
476
 
452
477
  # write _version.py
453
- self._serialize_and_write_version_file(namespace, general_serializer)
478
+ self._serialize_and_write_version_file(general_serializer)
479
+ # if there's a subdir, we need to write another version file in the subdir
480
+ if self.code_model.options.get("generation-subdir"):
481
+ self._serialize_and_write_version_file(general_serializer, namespace)
454
482
 
455
483
  # write the empty py.typed file
456
- self.write_file(exec_path / Path("py.typed"), "# Marker file for PEP 561.")
484
+ pytyped_value = "# Marker file for PEP 561."
485
+ # TODO: remove this when we remove legacy multiapi generation
486
+ if self.code_model.options["multiapi"]:
487
+ self.write_file(self.code_model.get_generation_dir(namespace) / Path("py.typed"), pytyped_value)
488
+ else:
489
+ self.write_file(root_dir / Path("py.typed"), pytyped_value)
457
490
 
458
491
  # write _validation.py
459
492
  if any(og for client in self.code_model.clients for og in client.operation_groups if og.need_validation):
460
493
  self.write_file(
461
- exec_path / Path("_validation.py"),
494
+ root_dir / Path("_validation.py"),
462
495
  general_serializer.serialize_validation_file(),
463
496
  )
464
497
 
465
498
  # write _types.py
466
499
  if self.code_model.named_unions:
467
500
  self.write_file(
468
- exec_path / Path("_types.py"),
501
+ root_dir / Path("_types.py"),
469
502
  TypesSerializer(code_model=self.code_model, env=env).serialize(),
470
503
  )
471
504
 
472
505
  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(".")
506
+ metadata_serializer = MetadataSerializer(self.code_model, env)
507
+ self.write_file(
508
+ self.code_model.get_generation_dir(namespace) / Path("_metadata.json"), metadata_serializer.serialize()
483
509
  )
484
510
 
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 (
491
- self.code_model.options["no-namespace-folders"]
492
- and not self.code_model.options["multiapi"]
493
- and not self.code_model.options["azure-arm"]
494
- ):
495
- # when output folder contains parts different from the namespace, we fall back to current folder directly.
496
- # (e.g. https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/communication/azure-communication-callautomation/swagger/SWAGGER.md)
497
- return Path(".")
498
- return self.exec_path_compensation / Path(*namespace.split("."))
499
-
500
511
  # pylint: disable=line-too-long
501
512
  @property
502
513
  def sample_additional_folder(self) -> Path:
@@ -515,8 +526,8 @@ class JinjaSerializer(ReaderAndWriter):
515
526
  return Path("/".join(namespace_config.split(".")[num_of_package_namespace:]))
516
527
  return Path("")
517
528
 
518
- def _serialize_and_write_sample(self, env: Environment, namespace: str):
519
- out_path = self.exec_path_for_test_sample(namespace) / Path("generated_samples")
529
+ def _serialize_and_write_sample(self, env: Environment):
530
+ out_path = Path("./generated_samples")
520
531
  for client in self.code_model.clients:
521
532
  for op_group in client.operation_groups:
522
533
  for operation in op_group.operations:
@@ -548,9 +559,9 @@ class JinjaSerializer(ReaderAndWriter):
548
559
  log_error = f"error happens in sample {file}: {e}"
549
560
  _LOGGER.error(log_error)
550
561
 
551
- def _serialize_and_write_test(self, env: Environment, namespace: str):
562
+ def _serialize_and_write_test(self, env: Environment):
552
563
  self.code_model.for_test = True
553
- out_path = self.exec_path_for_test_sample(namespace) / Path("generated_tests")
564
+ out_path = Path("./generated_tests")
554
565
  general_serializer = TestGeneralSerializer(code_model=self.code_model, env=env)
555
566
  self.write_file(out_path / "conftest.py", general_serializer.serialize_conftest())
556
567
  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(