@typespec/http-client-python 0.29.2 → 0.30.0-alpha.20260529.2

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 (33) hide show
  1. package/dist/emitter/http.d.ts.map +1 -1
  2. package/dist/emitter/http.js +3 -2
  3. package/dist/emitter/http.js.map +1 -1
  4. package/dist/emitter/types.d.ts.map +1 -1
  5. package/dist/emitter/types.js +3 -2
  6. package/dist/emitter/types.js.map +1 -1
  7. package/dist/emitter/utils.d.ts +5 -0
  8. package/dist/emitter/utils.d.ts.map +1 -1
  9. package/dist/emitter/utils.js +6 -2
  10. package/dist/emitter/utils.js.map +1 -1
  11. package/emitter/src/http.ts +3 -1
  12. package/emitter/src/types.ts +3 -1
  13. package/emitter/src/utils.ts +8 -2
  14. package/emitter/temp/tsconfig.tsbuildinfo +1 -1
  15. package/eng/scripts/Build-Packages.ps1 +17 -2
  16. package/eng/scripts/ci/dev_requirements.txt +2 -2
  17. package/eng/scripts/ci/run_pylint.py +9 -0
  18. package/eng/scripts/setup/__pycache__/package_manager.cpython-311.pyc +0 -0
  19. package/eng/scripts/setup/__pycache__/venvtools.cpython-311.pyc +0 -0
  20. package/eng/scripts/setup/package_manager.py +13 -3
  21. package/generator/build/lib/pygen/codegen/models/operation.py +6 -1
  22. package/generator/build/lib/pygen/codegen/models/parameter.py +1 -0
  23. package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +3 -2
  24. package/generator/build/lib/pygen/preprocess/__init__.py +17 -5
  25. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  26. package/generator/pygen/codegen/models/operation.py +6 -1
  27. package/generator/pygen/codegen/models/parameter.py +1 -0
  28. package/generator/pygen/codegen/serializers/builder_serializer.py +3 -2
  29. package/generator/pygen/preprocess/__init__.py +17 -5
  30. package/generator/pygen.egg-info/PKG-INFO +1 -1
  31. package/generator/pygen.egg-info/requires.txt +1 -1
  32. package/generator/setup.py +1 -1
  33. package/package.json +5 -5
@@ -75,6 +75,13 @@ New-Item -ItemType Directory -Force -Path "$outputPath/packages" | Out-Null
75
75
  $emitterVersion = node -p -e "require('$packageRoot/package.json').version"
76
76
  Write-Host "Package version: $emitterVersion"
77
77
 
78
+ # Stamp prerelease version if BuildNumber is provided
79
+ if ($BuildNumber) {
80
+ $versionTag = $Prerelease ? "-alpha" : "-beta"
81
+ $emitterVersion = "$emitterVersion$versionTag.$BuildNumber"
82
+ Write-Host "Stamped prerelease version: $emitterVersion"
83
+ }
84
+
78
85
  Push-Location "$packageRoot"
79
86
  try {
80
87
  # Step 1: Build the emitter and generator
@@ -87,12 +94,20 @@ try {
87
94
  Invoke-LoggedCommand "npm run lint" -GroupOutput
88
95
  }
89
96
 
90
- # Step 3: Create npm package
97
+ # Step 3: Stamp version in package.json if prerelease
98
+ if ($BuildNumber) {
99
+ Write-Host "`n=== Updating package.json version to $emitterVersion ===" -ForegroundColor Cyan
100
+ $packageJson = Get-Content -Raw "package.json" | ConvertFrom-Json -AsHashtable
101
+ $packageJson.version = $emitterVersion
102
+ $packageJson | ConvertTo-Json -Depth 100 | Out-File -Path "package.json" -Encoding utf8 -NoNewline -Force
103
+ }
104
+
105
+ # Step 4: Create npm package
91
106
  Write-Host "`n=== Creating npm package ===" -ForegroundColor Cyan
92
107
  Invoke-LoggedCommand "npm pack"
93
108
  Copy-Item "typespec-http-client-python-$emitterVersion.tgz" -Destination "$outputPath/packages"
94
109
 
95
- # Step 4: Verify package can be installed
110
+ # Step 5: Verify package can be installed
96
111
  Write-Host "`n=== Verifying package installation ===" -ForegroundColor Cyan
97
112
  Invoke-LoggedCommand "npm install typespec-http-client-python-$emitterVersion.tgz" -GroupOutput
98
113
 
@@ -5,8 +5,8 @@ tox-uv
5
5
  mypy==1.19.1
6
6
  colorama==0.4.6
7
7
  debugpy==1.8.2
8
- pytest==8.3.2
8
+ pytest==9.0.3
9
9
  coverage==7.6.1
10
- black==26.3.1
10
+ black==24.4.0
11
11
  ptvsd==4.3.2
12
12
  types-PyYAML==6.0.12.8
@@ -47,6 +47,15 @@ def _single_dir_pylint(mod):
47
47
  ]
48
48
  if is_azure:
49
49
  pylint_args.append("--load-plugins=pylint_guidelines_checker")
50
+ # Per-package suppressions for generated code where pylint's ignore-paths
51
+ # is not honored (pylint 4.x + recursive + plugin). Keep the scope as
52
+ # narrow as possible: only this package, only the offending check.
53
+ per_package_disables = {
54
+ "azure-client-generator-core-exact-name": ["client-incorrect-naming-convention"],
55
+ }
56
+ extra_disables = per_package_disables.get(mod.name)
57
+ if extra_disables:
58
+ pylint_args.append("--disable={}".format(",".join(extra_disables)))
50
59
  pylint_args.append(str(inner_class.absolute()))
51
60
  try:
52
61
  check_call(pylint_args)
@@ -100,10 +100,20 @@ def install_packages(packages: list, venv_context=None, package_manager: str = N
100
100
 
101
101
  try:
102
102
  if cwd:
103
- subprocess.check_call(install_cmd + packages, cwd=cwd)
103
+ result = subprocess.run(install_cmd + packages, cwd=cwd, capture_output=True, text=True)
104
104
  else:
105
- subprocess.check_call(install_cmd + packages)
106
- except subprocess.CalledProcessError as e:
105
+ result = subprocess.run(install_cmd + packages, capture_output=True, text=True)
106
+ if result.returncode != 0:
107
+ error_output = (result.stderr or "") + (result.stdout or "")
108
+ error_msg = f"Failed to install packages with {package_manager}: command exited with code {result.returncode}\n{error_output}"
109
+ if "401" in error_output or "Unauthorized" in error_output:
110
+ error_msg += (
111
+ "\n\nReceived a 401 Unauthorized error from the Azure feed. "
112
+ "A pip dependency may not yet be cached. To fix this, follow the authentication steps at: "
113
+ "https://github.com/Azure/azure-sdk-for-python/blob/main/CONTRIBUTING.md#authentication-for-upstream-pull-through"
114
+ )
115
+ raise RuntimeError(error_msg)
116
+ except OSError as e:
107
117
  raise RuntimeError(f"Failed to install packages with {package_manager}: {e}")
108
118
 
109
119
 
@@ -91,11 +91,16 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
91
91
  self.has_etag: bool = self.yaml_data.get("hasEtag", False)
92
92
  self.cross_language_definition_id: Optional[str] = self.yaml_data.get("crossLanguageDefinitionId")
93
93
 
94
+ @property
95
+ def exact_name_params(self) -> set[str]:
96
+ """Return the set of client names for parameters with isExactName."""
97
+ return {p.client_name for p in self.parameters.method if p.is_exact_name}
98
+
94
99
  @property
95
100
  def stream_value(self) -> Union[str, bool]:
96
101
  return (
97
102
  f'kwargs.pop("stream", {self.has_stream_response})'
98
- if self.expose_stream_keyword and self.has_response_body
103
+ if self.expose_stream_keyword and self.has_response_body and "stream" not in self.exact_name_params
99
104
  else self.has_stream_response
100
105
  )
101
106
 
@@ -87,6 +87,7 @@ class _ParameterBase(BaseModel, abc.ABC): # pylint: disable=too-many-instance-a
87
87
  self.default_to_unset_sentinel: bool = self.yaml_data.get("defaultToUnsetSentinel", False)
88
88
  self.hide_in_method: bool = self.yaml_data.get("hideInMethod", False)
89
89
  self.is_continuation_token: bool = bool(self.yaml_data.get("isContinuationToken"))
90
+ self.is_exact_name: bool = self.yaml_data.get("isExactName", False)
90
91
 
91
92
  def get_declaration(self, value: Any = None) -> Any:
92
93
  return self.type.get_declaration(value)
@@ -627,7 +627,8 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
627
627
  return ""
628
628
 
629
629
  def pop_kwargs_from_signature(self, builder: OperationType) -> list[str]:
630
- kwargs_to_pop = builder.parameters.kwargs_to_pop
630
+ exact_names = builder.exact_name_params
631
+ kwargs_to_pop = [k for k in builder.parameters.kwargs_to_pop if k.client_name not in exact_names]
631
632
  kwargs = self.parameter_serializer.pop_kwargs_from_signature(
632
633
  kwargs_to_pop,
633
634
  check_kwarg_dict=True,
@@ -645,7 +646,7 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
645
646
  body_parameter=builder.parameters.body_parameter if builder.parameters.has_body else None,
646
647
  )
647
648
  for p in builder.parameters.parameters:
648
- if p.hide_in_operation_signature and not p.is_continuation_token:
649
+ if p.hide_in_operation_signature and not p.is_continuation_token and p.client_name not in exact_names:
649
650
  kwargs.append(f'{p.client_name} = kwargs.pop("{p.client_name}", None)')
650
651
  cls_annotation = builder.cls_type_annotation(
651
652
  async_mode=self.async_mode, serialize_namespace=self.serialize_namespace
@@ -264,9 +264,10 @@ class PreProcessPlugin(YamlUpdatePlugin):
264
264
  for type in yaml_data:
265
265
  for property in type.get("properties", []):
266
266
  property["description"] = update_description(property.get("description", ""))
267
- property["clientName"] = self.pad_reserved_words(
268
- property["clientName"].lower(), PadType.PROPERTY, property
269
- )
267
+ if not property.get("isExactName", False):
268
+ property["clientName"] = self.pad_reserved_words(
269
+ property["clientName"].lower(), PadType.PROPERTY, property
270
+ )
270
271
  add_redefined_builtin_info(property["clientName"], property)
271
272
  if type.get("name"):
272
273
  pad_type = PadType.MODEL if type["type"] == "model" else PadType.ENUM_CLASS
@@ -362,14 +363,25 @@ class PreProcessPlugin(YamlUpdatePlugin):
362
363
 
363
364
  def update_parameter(self, yaml_data: dict[str, Any]) -> None:
364
365
  yaml_data["description"] = update_description(yaml_data.get("description", ""))
365
- if not (yaml_data["location"] == "header" and yaml_data["clientName"] in ("content_type", "accept")):
366
+ if not yaml_data.get("isExactName", False) and not (
367
+ yaml_data["location"] == "header" and yaml_data["clientName"] in ("content_type", "accept")
368
+ ):
366
369
  yaml_data["clientName"] = self.pad_reserved_words(
367
370
  yaml_data["clientName"].lower(), PadType.PARAMETER, yaml_data
368
371
  )
369
372
  if yaml_data.get("propertyToParameterName"):
370
373
  # need to create a new one with padded values (but NOT keys, since keys are wire names)
374
+ # build a lookup of exact-name properties from the body type's properties
375
+ exact_name_props = set()
376
+ for prop in yaml_data.get("type", {}).get("properties", []):
377
+ if prop.get("isExactName", False):
378
+ exact_name_props.add(prop.get("wireName", ""))
371
379
  yaml_data["propertyToParameterName"] = {
372
- prop: self.pad_reserved_words(param_name, PadType.PARAMETER, yaml_data).lower()
380
+ prop: (
381
+ param_name
382
+ if prop in exact_name_props
383
+ else self.pad_reserved_words(param_name, PadType.PARAMETER, yaml_data).lower()
384
+ )
373
385
  for prop, param_name in yaml_data["propertyToParameterName"].items()
374
386
  }
375
387
  wire_name_lower = (yaml_data.get("wireName") or "").lower()
@@ -91,11 +91,16 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
91
91
  self.has_etag: bool = self.yaml_data.get("hasEtag", False)
92
92
  self.cross_language_definition_id: Optional[str] = self.yaml_data.get("crossLanguageDefinitionId")
93
93
 
94
+ @property
95
+ def exact_name_params(self) -> set[str]:
96
+ """Return the set of client names for parameters with isExactName."""
97
+ return {p.client_name for p in self.parameters.method if p.is_exact_name}
98
+
94
99
  @property
95
100
  def stream_value(self) -> Union[str, bool]:
96
101
  return (
97
102
  f'kwargs.pop("stream", {self.has_stream_response})'
98
- if self.expose_stream_keyword and self.has_response_body
103
+ if self.expose_stream_keyword and self.has_response_body and "stream" not in self.exact_name_params
99
104
  else self.has_stream_response
100
105
  )
101
106
 
@@ -87,6 +87,7 @@ class _ParameterBase(BaseModel, abc.ABC): # pylint: disable=too-many-instance-a
87
87
  self.default_to_unset_sentinel: bool = self.yaml_data.get("defaultToUnsetSentinel", False)
88
88
  self.hide_in_method: bool = self.yaml_data.get("hideInMethod", False)
89
89
  self.is_continuation_token: bool = bool(self.yaml_data.get("isContinuationToken"))
90
+ self.is_exact_name: bool = self.yaml_data.get("isExactName", False)
90
91
 
91
92
  def get_declaration(self, value: Any = None) -> Any:
92
93
  return self.type.get_declaration(value)
@@ -627,7 +627,8 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
627
627
  return ""
628
628
 
629
629
  def pop_kwargs_from_signature(self, builder: OperationType) -> list[str]:
630
- kwargs_to_pop = builder.parameters.kwargs_to_pop
630
+ exact_names = builder.exact_name_params
631
+ kwargs_to_pop = [k for k in builder.parameters.kwargs_to_pop if k.client_name not in exact_names]
631
632
  kwargs = self.parameter_serializer.pop_kwargs_from_signature(
632
633
  kwargs_to_pop,
633
634
  check_kwarg_dict=True,
@@ -645,7 +646,7 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
645
646
  body_parameter=builder.parameters.body_parameter if builder.parameters.has_body else None,
646
647
  )
647
648
  for p in builder.parameters.parameters:
648
- if p.hide_in_operation_signature and not p.is_continuation_token:
649
+ if p.hide_in_operation_signature and not p.is_continuation_token and p.client_name not in exact_names:
649
650
  kwargs.append(f'{p.client_name} = kwargs.pop("{p.client_name}", None)')
650
651
  cls_annotation = builder.cls_type_annotation(
651
652
  async_mode=self.async_mode, serialize_namespace=self.serialize_namespace
@@ -264,9 +264,10 @@ class PreProcessPlugin(YamlUpdatePlugin):
264
264
  for type in yaml_data:
265
265
  for property in type.get("properties", []):
266
266
  property["description"] = update_description(property.get("description", ""))
267
- property["clientName"] = self.pad_reserved_words(
268
- property["clientName"].lower(), PadType.PROPERTY, property
269
- )
267
+ if not property.get("isExactName", False):
268
+ property["clientName"] = self.pad_reserved_words(
269
+ property["clientName"].lower(), PadType.PROPERTY, property
270
+ )
270
271
  add_redefined_builtin_info(property["clientName"], property)
271
272
  if type.get("name"):
272
273
  pad_type = PadType.MODEL if type["type"] == "model" else PadType.ENUM_CLASS
@@ -362,14 +363,25 @@ class PreProcessPlugin(YamlUpdatePlugin):
362
363
 
363
364
  def update_parameter(self, yaml_data: dict[str, Any]) -> None:
364
365
  yaml_data["description"] = update_description(yaml_data.get("description", ""))
365
- if not (yaml_data["location"] == "header" and yaml_data["clientName"] in ("content_type", "accept")):
366
+ if not yaml_data.get("isExactName", False) and not (
367
+ yaml_data["location"] == "header" and yaml_data["clientName"] in ("content_type", "accept")
368
+ ):
366
369
  yaml_data["clientName"] = self.pad_reserved_words(
367
370
  yaml_data["clientName"].lower(), PadType.PARAMETER, yaml_data
368
371
  )
369
372
  if yaml_data.get("propertyToParameterName"):
370
373
  # need to create a new one with padded values (but NOT keys, since keys are wire names)
374
+ # build a lookup of exact-name properties from the body type's properties
375
+ exact_name_props = set()
376
+ for prop in yaml_data.get("type", {}).get("properties", []):
377
+ if prop.get("isExactName", False):
378
+ exact_name_props.add(prop.get("wireName", ""))
371
379
  yaml_data["propertyToParameterName"] = {
372
- prop: self.pad_reserved_words(param_name, PadType.PARAMETER, yaml_data).lower()
380
+ prop: (
381
+ param_name
382
+ if prop in exact_name_props
383
+ else self.pad_reserved_words(param_name, PadType.PARAMETER, yaml_data).lower()
384
+ )
373
385
  for prop, param_name in yaml_data["propertyToParameterName"].items()
374
386
  }
375
387
  wire_name_lower = (yaml_data.get("wireName") or "").lower()
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: black==26.3.1
18
+ Requires-Dist: black==24.4.0
19
19
  Requires-Dist: docutils>=0.20.1
20
20
  Requires-Dist: Jinja2==3.1.6
21
21
  Requires-Dist: PyYAML==6.0.1
@@ -1,4 +1,4 @@
1
- black==26.3.1
1
+ black==24.4.0
2
2
  docutils>=0.20.1
3
3
  Jinja2==3.1.6
4
4
  PyYAML==6.0.1
@@ -46,7 +46,7 @@ setup(
46
46
  ]
47
47
  ),
48
48
  install_requires=[
49
- "black==26.3.1",
49
+ "black==24.4.0",
50
50
  "docutils>=0.20.1",
51
51
  "Jinja2==3.1.6",
52
52
  "PyYAML==6.0.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typespec/http-client-python",
3
- "version": "0.29.2",
3
+ "version": "0.30.0-alpha.20260529.2",
4
4
  "author": "Microsoft Corporation",
5
5
  "description": "TypeSpec emitter for Python SDKs",
6
6
  "homepage": "https://typespec.io",
@@ -64,7 +64,7 @@
64
64
  "@azure-tools/typespec-azure-core": ">=0.68.0 <1.0.0",
65
65
  "@azure-tools/typespec-azure-resource-manager": ">=0.68.0 <1.0.0",
66
66
  "@azure-tools/typespec-azure-rulesets": ">=0.68.0 <1.0.0",
67
- "@azure-tools/typespec-client-generator-core": ">=0.68.0 <1.0.0",
67
+ "@azure-tools/typespec-client-generator-core": ">=0.68.2 <1.0.0",
68
68
  "@typespec/compiler": "^1.12.0",
69
69
  "@typespec/http": "^1.12.0",
70
70
  "@typespec/openapi": "^1.12.0",
@@ -102,8 +102,8 @@
102
102
  "@azure-tools/typespec-azure-core": "~0.68.0",
103
103
  "@azure-tools/typespec-azure-resource-manager": "~0.68.0",
104
104
  "@azure-tools/typespec-azure-rulesets": "~0.68.0",
105
- "@azure-tools/typespec-client-generator-core": "~0.68.0",
106
- "@azure-tools/azure-http-specs": "0.1.0-alpha.40",
105
+ "@azure-tools/typespec-client-generator-core": "~0.68.2",
106
+ "@azure-tools/azure-http-specs": "0.1.0-alpha.41-dev.1",
107
107
  "@typespec/compiler": "^1.12.0",
108
108
  "@typespec/http": "^1.12.0",
109
109
  "@typespec/openapi": "^1.12.0",
@@ -126,4 +126,4 @@
126
126
  "typescript-eslint": "^8.49.0",
127
127
  "vitest": "^4.0.15"
128
128
  }
129
- }
129
+ }