@typespec/http-client-python 0.28.3 → 0.29.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 (99) hide show
  1. package/dist/emitter/code-model.d.ts.map +1 -1
  2. package/dist/emitter/code-model.js +1 -0
  3. package/dist/emitter/code-model.js.map +1 -1
  4. package/dist/emitter/constants.d.ts +6 -0
  5. package/dist/emitter/constants.d.ts.map +1 -0
  6. package/dist/emitter/constants.js +27 -0
  7. package/dist/emitter/constants.js.map +1 -0
  8. package/dist/emitter/emitter.d.ts.map +1 -1
  9. package/dist/emitter/emitter.js +169 -94
  10. package/dist/emitter/emitter.js.map +1 -1
  11. package/dist/emitter/http.d.ts.map +1 -1
  12. package/dist/emitter/http.js +33 -1
  13. package/dist/emitter/http.js.map +1 -1
  14. package/dist/emitter/lib.d.ts +33 -2
  15. package/dist/emitter/lib.d.ts.map +1 -1
  16. package/dist/emitter/lib.js +17 -0
  17. package/dist/emitter/lib.js.map +1 -1
  18. package/dist/emitter/run-python3.js +1 -1
  19. package/dist/emitter/run-python3.js.map +1 -1
  20. package/dist/emitter/types.d.ts.map +1 -1
  21. package/dist/emitter/types.js +31 -5
  22. package/dist/emitter/types.js.map +1 -1
  23. package/emitter/src/code-model.ts +1 -0
  24. package/emitter/src/constants.ts +27 -0
  25. package/emitter/src/emitter.ts +216 -96
  26. package/emitter/src/http.ts +29 -1
  27. package/emitter/src/lib.ts +20 -0
  28. package/emitter/src/run-python3.ts +1 -1
  29. package/emitter/src/types.ts +35 -5
  30. package/emitter/temp/tsconfig.tsbuildinfo +1 -1
  31. package/emitter/test/utils.test.ts +3 -0
  32. package/emitter/tsconfig.build.json +2 -1
  33. package/emitter/tsconfig.json +2 -1
  34. package/eng/scripts/ci/config/eslint-ci.config.mjs +10 -0
  35. package/eng/scripts/ci/config/mypy.ini +1 -1
  36. package/eng/scripts/ci/config/pylintrc +1 -1
  37. package/eng/scripts/ci/config/pyrightconfig.json +1 -1
  38. package/eng/scripts/ci/dev_requirements.txt +1 -1
  39. package/eng/scripts/ci/lint.ts +11 -4
  40. package/eng/scripts/ci/regenerate-common.ts +407 -136
  41. package/eng/scripts/ci/regenerate.ts +103 -194
  42. package/eng/scripts/ci/run-tests.ts +153 -48
  43. package/eng/scripts/ci/run_apiview.py +22 -16
  44. package/eng/scripts/ci/run_mypy.py +28 -18
  45. package/eng/scripts/ci/run_pylint.py +23 -16
  46. package/eng/scripts/ci/run_pyright.py +5 -2
  47. package/eng/scripts/ci/run_sphinx_build.py +24 -9
  48. package/eng/scripts/ci/util.py +61 -25
  49. package/eng/scripts/setup/__pycache__/package_manager.cpython-311.pyc +0 -0
  50. package/eng/scripts/setup/__pycache__/venvtools.cpython-311.pyc +0 -0
  51. package/eng/scripts/setup/build_pygen_wheel.py +3 -3
  52. package/eng/scripts/setup/install.py +3 -3
  53. package/eng/scripts/setup/install.ts +1 -1
  54. package/eng/scripts/setup/prepare.py +2 -2
  55. package/eng/scripts/setup/run-python3.ts +1 -1
  56. package/eng/scripts/setup/run_batch.py +197 -0
  57. package/eng/scripts/setup/venvtools.py +0 -1
  58. package/generator/build/lib/pygen/__init__.py +1 -3
  59. package/generator/build/lib/pygen/codegen/__init__.py +0 -1
  60. package/generator/build/lib/pygen/codegen/models/base.py +0 -1
  61. package/generator/build/lib/pygen/codegen/models/client.py +2 -1
  62. package/generator/build/lib/pygen/codegen/models/code_model.py +1 -0
  63. package/generator/build/lib/pygen/codegen/models/enum_type.py +0 -1
  64. package/generator/build/lib/pygen/codegen/models/model_type.py +1 -7
  65. package/generator/build/lib/pygen/codegen/models/parameter.py +16 -14
  66. package/generator/build/lib/pygen/codegen/serializers/__init__.py +27 -12
  67. package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +12 -3
  68. package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +6 -2
  69. package/generator/build/lib/pygen/codegen/serializers/import_serializer.py +17 -4
  70. package/generator/build/lib/pygen/codegen/serializers/parameter_serializer.py +17 -11
  71. package/generator/build/lib/pygen/codegen/templates/model_base.py.jinja2 +19 -7
  72. package/generator/build/lib/pygen/codegen/templates/packaging_templates/README.md.jinja2 +11 -3
  73. package/generator/build/lib/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +1 -1
  74. package/generator/build/lib/pygen/codegen/templates/serialization.py.jinja2 +5 -1
  75. package/generator/build/lib/pygen/preprocess/__init__.py +37 -30
  76. package/generator/component-detection-pip-report.json +7 -7
  77. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  78. package/generator/pygen/__init__.py +1 -3
  79. package/generator/pygen/codegen/__init__.py +0 -1
  80. package/generator/pygen/codegen/models/base.py +0 -1
  81. package/generator/pygen/codegen/models/client.py +2 -1
  82. package/generator/pygen/codegen/models/code_model.py +1 -0
  83. package/generator/pygen/codegen/models/enum_type.py +0 -1
  84. package/generator/pygen/codegen/models/model_type.py +1 -7
  85. package/generator/pygen/codegen/models/parameter.py +16 -14
  86. package/generator/pygen/codegen/serializers/__init__.py +27 -12
  87. package/generator/pygen/codegen/serializers/builder_serializer.py +12 -3
  88. package/generator/pygen/codegen/serializers/general_serializer.py +6 -2
  89. package/generator/pygen/codegen/serializers/import_serializer.py +17 -4
  90. package/generator/pygen/codegen/serializers/parameter_serializer.py +17 -11
  91. package/generator/pygen/codegen/templates/model_base.py.jinja2 +19 -7
  92. package/generator/pygen/codegen/templates/packaging_templates/README.md.jinja2 +11 -3
  93. package/generator/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +1 -1
  94. package/generator/pygen/codegen/templates/serialization.py.jinja2 +5 -1
  95. package/generator/pygen/preprocess/__init__.py +37 -30
  96. package/generator/pygen.egg-info/PKG-INFO +1 -2
  97. package/generator/pygen.egg-info/requires.txt +1 -1
  98. package/generator/setup.py +1 -2
  99. package/package.json +24 -9
@@ -16,7 +16,6 @@ from .utils import TYPESPEC_PACKAGE_MODE, VALID_PACKAGE_MODE
16
16
 
17
17
  from ._version import VERSION
18
18
 
19
-
20
19
  __version__ = VERSION
21
20
  _LOGGER = logging.getLogger(__name__)
22
21
 
@@ -237,8 +236,7 @@ class ReaderAndWriter:
237
236
  def write_file(self, filename: Union[str, Path], file_content: str) -> None:
238
237
  """Directly writing to disk"""
239
238
  file_folder = Path(filename).parent
240
- if not Path.is_dir(self.output_folder / file_folder):
241
- Path.mkdir(self.output_folder / file_folder, parents=True)
239
+ Path.mkdir(self.output_folder / file_folder, parents=True, exist_ok=True)
242
240
  with open(self.output_folder / Path(filename), "w", encoding="utf-8") as fd:
243
241
  fd.write(file_content)
244
242
 
@@ -13,7 +13,6 @@ from ..utils import parse_args
13
13
  from .models.code_model import CodeModel
14
14
  from .serializers import JinjaSerializer
15
15
 
16
-
17
16
  _LOGGER = logging.getLogger(__name__)
18
17
 
19
18
 
@@ -7,7 +7,6 @@ from typing import Any, TYPE_CHECKING, Optional
7
7
  from abc import ABC, abstractmethod
8
8
  from .imports import FileImport
9
9
 
10
-
11
10
  if TYPE_CHECKING:
12
11
  from .code_model import CodeModel
13
12
  from .model_type import ModelType
@@ -291,11 +291,12 @@ class Client(_ClientConfigBase[ClientGlobalParameterList]):
291
291
  ImportType.SDKCORE,
292
292
  )
293
293
 
294
- # import for "Self"
294
+ # import for "Self" (typing.Self available from Python 3.11+)
295
295
  file_import.add_submodule_import(
296
296
  "typing_extensions",
297
297
  "Self",
298
298
  ImportType.STDLIB,
299
+ version_modules=(((3, 11), "typing", None),),
299
300
  )
300
301
  if self.need_cloud_setting:
301
302
  file_import.add_submodule_import("typing", "cast", ImportType.STDLIB)
@@ -95,6 +95,7 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
95
95
  t for t in self.types_map.values() if isinstance(t, CombinedType) and t.name
96
96
  ]
97
97
  self.cross_language_package_id = self.yaml_data.get("crossLanguagePackageId")
98
+ self.cross_language_version = self.yaml_data.get("crossLanguageVersion")
98
99
  self.for_test: bool = False
99
100
  # key is typespec namespace, value is models/clients/opeartion_groups/enums cache in the namespace
100
101
  self._client_namespace_types: dict[str, ClientNamespaceType] = {}
@@ -10,7 +10,6 @@ from .imports import FileImport, ImportType, TypingSection
10
10
  from .utils import NamespaceType, add_to_pylint_disable
11
11
  from ...utils import NAME_LENGTH_LIMIT
12
12
 
13
-
14
13
  if TYPE_CHECKING:
15
14
  from .code_model import CodeModel
16
15
 
@@ -5,8 +5,7 @@
5
5
  # --------------------------------------------------------------------------
6
6
  from enum import Enum
7
7
  from collections import OrderedDict
8
- from typing import Any, Optional, TYPE_CHECKING, cast
9
- import sys
8
+ from typing import Any, Literal, Optional, TYPE_CHECKING, cast
10
9
  from .utils import add_to_pylint_disable, NamespaceType, LOCALS_LENGTH_LIMIT
11
10
  from .base import BaseType
12
11
  from .constant_type import ConstantType
@@ -14,11 +13,6 @@ from .property import Property
14
13
  from .imports import FileImport, ImportType, TypingSection
15
14
  from ...utils import NAME_LENGTH_LIMIT
16
15
 
17
- if sys.version_info >= (3, 8):
18
- from typing import Literal
19
- else:
20
- from typing_extensions import Literal # type: ignore
21
-
22
16
  if TYPE_CHECKING:
23
17
  from .code_model import CodeModel
24
18
 
@@ -61,6 +61,7 @@ class _ParameterBase(BaseModel, abc.ABC): # pylint: disable=too-many-instance-a
61
61
  ) -> None:
62
62
  super().__init__(yaml_data, code_model)
63
63
  self.wire_name: str = yaml_data.get("wireName", "")
64
+ self.etag_role: Optional[str] = yaml_data.get("etagRole", None)
64
65
  self.client_name: str = self.yaml_data["clientName"]
65
66
  self.optional: bool = self.yaml_data["optional"]
66
67
  self.implementation: Optional[str] = yaml_data.get("implementation", None)
@@ -108,25 +109,24 @@ class _ParameterBase(BaseModel, abc.ABC): # pylint: disable=too-many-instance-a
108
109
  type_description = self.type.description(is_operation_file=True)
109
110
  if type_description:
110
111
  base_description = add_to_description(base_description, type_description)
111
- if self.optional and isinstance(self.type, ConstantType):
112
- if self.is_api_version:
113
- base_description = add_to_description(
114
- base_description,
115
- f"Known values are {self.get_declaration()}.",
116
- )
117
- else:
118
- base_description = add_to_description(
119
- base_description,
120
- f"Known values are {self.get_declaration()} and None.",
121
- )
112
+ if (self.optional or self.is_api_version) and isinstance(self.type, ConstantType):
113
+ base_description = add_to_description(
114
+ base_description,
115
+ f"Known values are {self.get_declaration()} and None.",
116
+ )
122
117
  if not (self.optional or self.client_default_value):
123
118
  base_description = add_to_description(base_description, "Required.")
124
- if self.client_default_value is not None:
119
+ if self.is_api_version:
120
+ base_description = add_to_description(
121
+ base_description,
122
+ "Default value is None. If not set, the operation's default API version will be used.",
123
+ )
124
+ elif self.client_default_value is not None:
125
125
  base_description = add_to_description(
126
126
  base_description,
127
127
  f"Default value is {self.client_default_value_declaration}.",
128
128
  )
129
- if self.optional and self.client_default_value is None:
129
+ elif self.optional:
130
130
  base_description = add_to_description(
131
131
  base_description,
132
132
  f"Default value is {self.client_default_value_declaration}.",
@@ -378,6 +378,8 @@ class ClientParameter(Parameter):
378
378
  def method_location(self) -> ParameterMethodLocation:
379
379
  if self.constant:
380
380
  return ParameterMethodLocation.KWARG
381
+ if self.is_api_version:
382
+ return ParameterMethodLocation.KWARG
381
383
  if (
382
384
  self.is_host
383
385
  and (self.code_model.options["version-tolerant"] or self.code_model.options["low-level-client"])
@@ -409,6 +411,6 @@ class ConfigParameter(Parameter):
409
411
 
410
412
  @property
411
413
  def method_location(self) -> ParameterMethodLocation:
412
- if self.constant:
414
+ if self.constant or self.is_api_version:
413
415
  return ParameterMethodLocation.KWARG
414
416
  return ParameterMethodLocation.POSITIONAL
@@ -42,15 +42,15 @@ __all__ = [
42
42
  "JinjaSerializer",
43
43
  ]
44
44
 
45
- _PACKAGE_FILES = [
45
+ _DEFAULT_PACKAGE_FILES = (
46
46
  "CHANGELOG.md.jinja2",
47
47
  "dev_requirements.txt.jinja2",
48
48
  "LICENSE.jinja2",
49
49
  "MANIFEST.in.jinja2",
50
50
  "README.md.jinja2",
51
- ]
51
+ )
52
52
 
53
- _REGENERATE_FILES = {"MANIFEST.in"}
53
+ _DEFAULT_REGENERATE_FILES = frozenset({"MANIFEST.in"})
54
54
  AsyncInfo = namedtuple("AsyncInfo", ["async_mode", "async_path"])
55
55
 
56
56
 
@@ -75,15 +75,17 @@ class JinjaSerializer(ReaderAndWriter):
75
75
  ) -> None:
76
76
  super().__init__(output_folder=output_folder, **kwargs)
77
77
  self.code_model = code_model
78
+ self._package_files: list[str] = list(_DEFAULT_PACKAGE_FILES)
79
+ self._regenerate_files: set[str] = set(_DEFAULT_REGENERATE_FILES)
78
80
  self._regenerate_setup_py()
79
81
 
80
82
  def _regenerate_setup_py(self):
81
83
  if self.code_model.options["keep-setup-py"] or self.code_model.options["basic-setup-py"]:
82
- _PACKAGE_FILES.append("setup.py.jinja2")
83
- _REGENERATE_FILES.add("setup.py")
84
+ self._package_files.append("setup.py.jinja2")
85
+ self._regenerate_files.add("setup.py")
84
86
  else:
85
- _PACKAGE_FILES.append("pyproject.toml.jinja2")
86
- _REGENERATE_FILES.add("pyproject.toml")
87
+ self._package_files.append("pyproject.toml.jinja2")
88
+ self._regenerate_files.add("pyproject.toml")
87
89
 
88
90
  @property
89
91
  def has_aio_folder(self) -> bool:
@@ -180,6 +182,17 @@ class JinjaSerializer(ReaderAndWriter):
180
182
  elif client_namespace_type.clients:
181
183
  # add clients folder if there are clients in this namespace
182
184
  self._serialize_client_and_config_files(client_namespace, client_namespace_type.clients, env)
185
+ # When generation-subdir is configured, generated code goes into a subdirectory
186
+ # (e.g., _generated/). We also need an __init__.py in the parent namespace dir
187
+ # so that the package is discoverable by find_packages() / pip install.
188
+ root_dir = self.code_model.get_root_dir()
189
+ if self.code_model.options.get("generation-subdir") and not self.read_file(
190
+ root_dir / Path("__init__.py")
191
+ ):
192
+ self.write_file(
193
+ root_dir / Path("__init__.py"),
194
+ general_serializer.serialize_pkgutil_init_file(),
195
+ )
183
196
  else:
184
197
  # add pkgutil init file if no clients in this namespace
185
198
  self.write_file(
@@ -242,7 +255,7 @@ class JinjaSerializer(ReaderAndWriter):
242
255
  lstrip_blocks=True,
243
256
  )
244
257
 
245
- package_files = _PACKAGE_FILES
258
+ package_files = list(self._package_files)
246
259
  if not self.code_model.license_description:
247
260
  package_files.remove("LICENSE.jinja2")
248
261
  elif Path(self.code_model.options["package-mode"]).exists():
@@ -261,7 +274,7 @@ class JinjaSerializer(ReaderAndWriter):
261
274
  continue
262
275
  file = template_name.replace(".jinja2", "")
263
276
  output_file = root_of_sdk / file
264
- if not self.read_file(output_file) or file in _REGENERATE_FILES:
277
+ if not self.read_file(output_file) or file in self._regenerate_files:
265
278
  if self.keep_version_file and file == "setup.py" and not self.code_model.options["azure-arm"]:
266
279
  # don't regenerate setup.py file if the version file is more up to date for data-plane
267
280
  continue
@@ -482,32 +495,34 @@ class JinjaSerializer(ReaderAndWriter):
482
495
 
483
496
  def _serialize_and_write_top_level_folder(self, env: Environment, namespace: str) -> None:
484
497
  root_dir = self.code_model.get_root_dir()
498
+ generation_dir = self.code_model.get_generation_dir(namespace)
485
499
  # write _utils folder
486
500
  self._serialize_and_write_utils_folder(env, self.code_model.namespace)
487
501
 
488
502
  general_serializer = GeneralSerializer(code_model=self.code_model, env=env, async_mode=False)
489
503
 
490
504
  # write _version.py
505
+ # Always write at root namespace since pyproject.toml references it there
491
506
  self._serialize_and_write_version_file(general_serializer)
492
- # if there's a subdir, we need to write another version file in the subdir
493
507
  if self.code_model.options.get("generation-subdir"):
494
508
  self._serialize_and_write_version_file(general_serializer, namespace)
495
509
 
496
510
  # write the empty py.typed file
511
+ # Always write at root namespace since MANIFEST.in references it there
497
512
  pytyped_value = "# Marker file for PEP 561."
498
513
  self.write_file(root_dir / Path("py.typed"), pytyped_value)
499
514
 
500
515
  # write _validation.py
501
516
  if any(og for client in self.code_model.clients for og in client.operation_groups if og.need_validation):
502
517
  self.write_file(
503
- root_dir / Path("_validation.py"),
518
+ generation_dir / Path("_validation.py"),
504
519
  general_serializer.serialize_validation_file(),
505
520
  )
506
521
 
507
522
  # write _types.py
508
523
  if self.code_model.named_unions:
509
524
  self.write_file(
510
- root_dir / Path("_types.py"),
525
+ generation_dir / Path("_types.py"),
511
526
  TypesSerializer(code_model=self.code_model, env=env).serialize(),
512
527
  )
513
528
 
@@ -107,10 +107,18 @@ def _serialize_grouped_body(builder: BuilderType) -> list[str]:
107
107
  groupers = [p for p in builder.parameters if p.grouper]
108
108
  for grouper in groupers:
109
109
  retval.append(f"if {grouper.client_name} is not None:")
110
+ # Keys in property_to_parameter_name are original client names (e.g. "from", "custom_header").
111
+ # Attribute access needs the padded client_name from the model property (e.g. "from_property").
112
+ # Build lookup from both wire_name and client_name to handle all cases.
113
+ grouper_model = cast(ModelType, grouper.type)
114
+ prop_name_to_client = {}
115
+ for prop in grouper_model.properties:
116
+ prop_name_to_client[prop.wire_name] = prop.client_name
117
+ prop_name_to_client[prop.client_name] = prop.client_name
110
118
  retval.extend(
111
119
  [
112
- f" {parameter} = {grouper.client_name}.{property}"
113
- for property, parameter in grouper.property_to_parameter_name.items()
120
+ f" {parameter} = {grouper.client_name}.{prop_name_to_client[prop_name]}"
121
+ for prop_name, parameter in grouper.property_to_parameter_name.items()
114
122
  ]
115
123
  )
116
124
  return retval
@@ -1353,7 +1361,8 @@ class _PagingOperationSerializer(_OperationSerializer[PagingOperationType]):
1353
1361
  except StopIteration:
1354
1362
  pass
1355
1363
 
1356
- retval.append(f'_request = HttpRequest("{builder.next_link_verb}", {next_link_str}{query_str})')
1364
+ header_str = ", headers=_headers"
1365
+ retval.append(f'_request = HttpRequest("{builder.next_link_verb}", {next_link_str}{header_str}{query_str})')
1357
1366
  retval.extend(self._postprocess_http_request(builder, "_request.url"))
1358
1367
 
1359
1368
  return retval
@@ -28,7 +28,7 @@ VERSION_MAP = {
28
28
  "corehttp": "1.0.0b6",
29
29
  }
30
30
 
31
- MIN_PYTHON_VERSION = "3.9"
31
+ MIN_PYTHON_VERSION = "3.10"
32
32
  MAX_PYTHON_VERSION = "3.13"
33
33
 
34
34
 
@@ -155,7 +155,10 @@ class GeneralSerializer(BaseSerializer):
155
155
  "VERSION_MAP": VERSION_MAP,
156
156
  "MIN_PYTHON_VERSION": MIN_PYTHON_VERSION,
157
157
  "MAX_PYTHON_VERSION": MAX_PYTHON_VERSION,
158
- "ADDITIONAL_DEPENDENCIES": [f"{item[0]}>={item[1]}" for item in additional_version_map.items()],
158
+ "ADDITIONAL_DEPENDENCIES": [
159
+ dep if dep.startswith('"') else f'"{dep}"'
160
+ for dep in (f"{item[0]}>={item[1]}" for item in additional_version_map.items())
161
+ ],
159
162
  }
160
163
  params |= {"options": self.code_model.options}
161
164
  params |= kwargs
@@ -326,6 +329,7 @@ class GeneralSerializer(BaseSerializer):
326
329
  {
327
330
  "CrossLanguagePackageId": self.code_model.cross_language_package_id,
328
331
  "CrossLanguageDefinitionId": cross_langauge_def_dict,
332
+ "CrossLanguageVersion": self.code_model.cross_language_version,
329
333
  },
330
334
  indent=4,
331
335
  )
@@ -50,20 +50,25 @@ def _serialize_import_type(imports: list[ImportModel], delimiter: str) -> str:
50
50
  import_list = []
51
51
  for module_name in sorted(set(i.module_name for i in imports)):
52
52
  normal_imports = [i for i in imports if i.module_name == module_name and not i.version_modules]
53
- versioned_imports = [i for i in imports if i.module_name == module_name and i.version_modules]
54
53
  if normal_imports:
55
54
  import_list.append(_serialize_package(normal_imports, delimiter))
56
- for i in versioned_imports:
57
- import_list.append(_serialize_versioned_package(i, delimiter))
58
55
  return delimiter.join(import_list)
59
56
 
60
57
 
58
+ def _serialize_versioned_imports(imports: list[ImportModel], delimiter: str) -> str:
59
+ """Serialize all versioned imports together, to be emitted after all regular imports."""
60
+ return delimiter.join(_serialize_versioned_package(i, delimiter) for i in imports if i.version_modules)
61
+
62
+
61
63
  def _get_import_clauses(imports: list[ImportModel], delimiter: str) -> list[str]:
62
64
  import_clause = []
63
65
  for import_type in ImportType:
64
- imports_with_import_type = [i for i in imports if i.import_type == import_type]
66
+ imports_with_import_type = [i for i in imports if i.import_type == import_type and not i.version_modules]
65
67
  if imports_with_import_type:
66
68
  import_clause.append(_serialize_import_type(imports_with_import_type, delimiter))
69
+ versioned = [i for i in imports if i.version_modules]
70
+ if versioned:
71
+ import_clause.append(_serialize_versioned_imports(versioned, delimiter))
67
72
  return import_clause
68
73
 
69
74
 
@@ -79,6 +84,13 @@ class FileImportSerializer:
79
84
  if any(self.file_import.get_imports_from_section(TypingSection.TYPING)):
80
85
  self.file_import.add_submodule_import("typing", "TYPE_CHECKING", ImportType.STDLIB)
81
86
 
87
+ def _add_sys_import_if_needed(self):
88
+ all_imports = list(self.file_import.get_imports_from_section(TypingSection.REGULAR)) + list(
89
+ self.file_import.get_imports_from_section(TypingSection.TYPING)
90
+ )
91
+ if any(i.version_modules for i in all_imports):
92
+ self.file_import.add_import("sys", ImportType.STDLIB)
93
+
82
94
  def get_typing_definitions(self) -> str:
83
95
  def declare_definition(type_name: str, type_definition: TypeDefinition) -> list[str]:
84
96
  ret: list[str] = []
@@ -95,6 +107,7 @@ class FileImportSerializer:
95
107
 
96
108
  def __str__(self) -> str:
97
109
  self._add_type_checking_import()
110
+ self._add_sys_import_if_needed()
98
111
  regular_imports = ""
99
112
  regular_imports_list = self._get_imports_list(TypingSection.REGULAR)
100
113
 
@@ -39,16 +39,6 @@ SPECIAL_HEADER_SERIALIZATION: dict[str, list[str]] = {
39
39
  "client-request-id": [],
40
40
  "x-ms-client-request-id": [],
41
41
  "return-client-request-id": [],
42
- "etag": [
43
- """if_match = prep_if_match(etag, match_condition)""",
44
- """if if_match is not None:""",
45
- """ _headers["If-Match"] = _SERIALIZER.header("if_match", if_match, "str")""",
46
- ],
47
- "match-condition": [
48
- """if_none_match = prep_if_none_match(etag, match_condition)""",
49
- """if if_none_match is not None:""",
50
- """ _headers["If-None-Match"] = _SERIALIZER.header("if_none_match", if_none_match, "str")""",
51
- ],
52
42
  }
53
43
 
54
44
 
@@ -157,6 +147,20 @@ class ParameterSerializer:
157
147
  ):
158
148
  return SPECIAL_HEADER_SERIALIZATION[param.wire_name.lower()]
159
149
 
150
+ if not is_legacy and param.location == ParameterLocation.HEADER and param.etag_role is not None:
151
+ header_name = param.wire_name
152
+ if param.etag_role == "ifMatch":
153
+ return [
154
+ """if_match = prep_if_match(etag, match_condition)""",
155
+ """if if_match is not None:""",
156
+ f""" _headers["{header_name}"] = _SERIALIZER.header("if_match", if_match, "str")""",
157
+ ]
158
+ return [
159
+ """if_none_match = prep_if_none_match(etag, match_condition)""",
160
+ """if if_none_match is not None:""",
161
+ f""" _headers["{header_name}"] = _SERIALIZER.header("if_none_match", if_none_match, "str")""",
162
+ ]
163
+
160
164
  set_parameter = "_{}['{}'] = {}".format(
161
165
  kwarg_name,
162
166
  param.wire_name,
@@ -202,11 +206,13 @@ class ParameterSerializer:
202
206
  if is_content_type_optional and not type_annotation.startswith("Optional[")
203
207
  else type_annotation
204
208
  )
205
- if kwarg.client_default_value is not None or kwarg.optional or kwarg.constant:
209
+ if kwarg.client_default_value is not None or kwarg.optional or kwarg.constant or kwarg.is_api_version:
206
210
  if check_client_input and kwarg.check_client_input:
207
211
  default_value = f"self._config.{kwarg.client_name}"
208
212
  elif kwarg.constant:
209
213
  default_value = kwarg.type.get_declaration(None)
214
+ elif kwarg.is_api_version and kwarg.client_default_value is None and kwarg.api_versions:
215
+ default_value = f'"{kwarg.api_versions[-1]}"'
210
216
  else:
211
217
  default_value = kwarg.client_default_value_declaration
212
218
  if check_kwarg_dict and (kwarg.location in [ParameterLocation.HEADER, ParameterLocation.QUERY]):
@@ -19,17 +19,22 @@ from datetime import datetime, date, time, timedelta, timezone
19
19
  from json import JSONEncoder
20
20
  import xml.etree.ElementTree as ET
21
21
  from collections.abc import MutableMapping
22
- from typing_extensions import Self
23
22
  import isodate
24
23
  from {{ code_model.core_library }}.exceptions import DeserializationError
25
24
  from {{ code_model.core_library }}{{ "" if code_model.is_azure_flavor else ".utils" }} import CaseInsensitiveEnumMeta
26
25
  from {{ code_model.core_library }}.{{ "" if code_model.is_azure_flavor else "runtime." }}pipeline import PipelineResponse
27
26
  from {{ code_model.core_library }}.serialization import _Null
27
+
28
28
  {% if code_model.has_external_type %}
29
29
  from {{ code_model.core_library }}.serialization import TypeHandlerRegistry
30
30
  {% endif %}
31
31
  from {{ code_model.core_library }}.rest import HttpResponse
32
32
 
33
+ if sys.version_info >= (3, 11):
34
+ from typing import Self
35
+ else:
36
+ from typing_extensions import Self
37
+
33
38
  _LOGGER = logging.getLogger(__name__)
34
39
 
35
40
  __all__ = ["SdkJSONEncoder", "Model", "rest_field", "rest_discriminator"]
@@ -617,11 +622,7 @@ class Model(_MyMutableMapping):
617
622
  class_name = self.__class__.__name__
618
623
  if len(args) > 1:
619
624
  raise TypeError(f"{class_name}.__init__() takes 2 positional arguments but {len(args) + 1} were given")
620
- dict_to_pass = {
621
- rest_field._rest_name: rest_field._default
622
- for rest_field in self._attr_to_rest_field.values()
623
- if rest_field._default is not _UNSET
624
- }
625
+ dict_to_pass: dict[str, typing.Any] = {}
625
626
  if args:
626
627
  if isinstance(args[0], ET.Element):
627
628
  dict_to_pass.update(self._init_from_xml(args[0]))
@@ -641,6 +642,14 @@ class Model(_MyMutableMapping):
641
642
  if v is not None
642
643
  }
643
644
  )
645
+ # Apply client default values for fields the caller didn't set so that
646
+ # defaults are part of `_data` and therefore included during serialization.
647
+ for rf in self._attr_to_rest_field.values():
648
+ if rf._default is _UNSET:
649
+ continue
650
+ if rf._rest_name in dict_to_pass:
651
+ continue
652
+ dict_to_pass[rf._rest_name] = _create_value(rf, rf._default)
644
653
  super().__init__(dict_to_pass)
645
654
 
646
655
  def _init_from_xml(self, element: ET.Element) -> dict[str, typing.Any]:
@@ -1160,7 +1169,10 @@ class _RestField:
1160
1169
  # by this point, type and rest_name will have a value bc we default
1161
1170
  # them in __new__ of the Model class
1162
1171
  # Use _data.get() directly to avoid triggering __getitem__ which clears the cache
1163
- item = obj._data.get(self._rest_name)
1172
+ item = obj._data.get(self._rest_name, _UNSET)
1173
+ if item is _UNSET:
1174
+ # Field not set by user; return the client default if one exists, otherwise None
1175
+ return self._default if self._default is not _UNSET else None
1164
1176
  if item is None:
1165
1177
  return item
1166
1178
  if self._is_model:
@@ -3,7 +3,7 @@
3
3
  # Microsoft Azure SDK for Python
4
4
 
5
5
  This is the Microsoft {{options["package-pprint-name"]}} Client Library.
6
- This package has been tested with Python 3.9+.
6
+ This package has been tested with Python 3.10+.
7
7
  For a more complete view of Azure libraries, see the [azure sdk python release](https://aka.ms/azsdk/python/all).
8
8
 
9
9
  # Usage
@@ -14,6 +14,10 @@ For docs and references, see [Python SDK References](https://docs.microsoft.com/
14
14
  Code samples for this package can be found at [{{options["package-pprint-name"]}}](https://docs.microsoft.com/samples/browse/?languages=python&term=Getting%20started%20-%20Managing&terms=Getting%20started%20-%20Managing) on docs.microsoft.com.
15
15
  Additional code samples for different Azure services are available at [Samples Repo](https://aka.ms/azsdk/python/mgmt/samples)
16
16
 
17
+ # Use with AI tools
18
+
19
+ AI coding tools such as VS Code and GitHub Copilot can help you write and debug code that uses this library. See [Using the Azure SDK for Python with AI tools](https://aka.ms/azsdk/python/ai) for available integrations.
20
+
17
21
  # Provide Feedback
18
22
 
19
23
  If you encounter any bugs or have suggestions, please file an issue in the
@@ -36,10 +40,14 @@ python -m pip install {{ options["package-name"] }}
36
40
 
37
41
  #### Prequisites
38
42
 
39
- - Python 3.9 or later is required to use this package.
43
+ - Python 3.10 or later is required to use this package.
40
44
  - You need an [Azure subscription][azure_sub] to use this package.
41
45
  - An existing {{ options["package-pprint-name"] }} instance.
42
46
 
47
+ ### Use with AI tools
48
+
49
+ AI coding tools such as VS Code and GitHub Copilot can help you write and debug code that uses this library. See [Using the Azure SDK for Python with AI tools](https://aka.ms/azsdk/python/ai) for available integrations.
50
+
43
51
  {% if token_credential and client_name %}
44
52
  #### Create with an Azure Active Directory Credential
45
53
  To use an [Azure Active Directory (AAD) token credential][authenticate_with_token],
@@ -113,7 +121,7 @@ This package is generated by `@typespec/http-client-python` with Typespec.
113
121
 
114
122
  ### Prequisites
115
123
 
116
- - Python 3.9 or later is required to use this package.
124
+ - Python 3.10 or later is required to use this package.
117
125
 
118
126
  ### Install the package
119
127
 
@@ -57,7 +57,7 @@ dependencies = [
57
57
  {% endfor %}
58
58
  {% endif %}
59
59
  {% for dep in ADDITIONAL_DEPENDENCIES %}
60
- "{{ dep }}",
60
+ {{ dep }},
61
61
  {% endfor %}
62
62
  ]
63
63
  dynamic = [
@@ -35,11 +35,15 @@ except ImportError:
35
35
  import xml.etree.ElementTree as ET
36
36
 
37
37
  import isodate # type: ignore
38
- from typing_extensions import Self
39
38
 
40
39
  from {{ code_model.core_library }}.exceptions import DeserializationError, SerializationError
41
40
  from {{ code_model.core_library }}.serialization import NULL as CoreNull
42
41
 
42
+ if sys.version_info >= (3, 11):
43
+ from typing import Self
44
+ else:
45
+ from typing_extensions import Self
46
+
43
47
  _BOM = codecs.BOM_UTF8.decode(encoding="utf-8")
44
48
 
45
49
  JSON = MutableMapping[str, Any]