@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.
- package/dist/emitter/code-model.d.ts.map +1 -1
- package/dist/emitter/code-model.js +1 -0
- package/dist/emitter/code-model.js.map +1 -1
- package/dist/emitter/constants.d.ts +6 -0
- package/dist/emitter/constants.d.ts.map +1 -0
- package/dist/emitter/constants.js +27 -0
- package/dist/emitter/constants.js.map +1 -0
- package/dist/emitter/emitter.d.ts.map +1 -1
- package/dist/emitter/emitter.js +169 -94
- package/dist/emitter/emitter.js.map +1 -1
- package/dist/emitter/http.d.ts.map +1 -1
- package/dist/emitter/http.js +33 -1
- package/dist/emitter/http.js.map +1 -1
- package/dist/emitter/lib.d.ts +33 -2
- package/dist/emitter/lib.d.ts.map +1 -1
- package/dist/emitter/lib.js +17 -0
- package/dist/emitter/lib.js.map +1 -1
- package/dist/emitter/run-python3.js +1 -1
- package/dist/emitter/run-python3.js.map +1 -1
- package/dist/emitter/types.d.ts.map +1 -1
- package/dist/emitter/types.js +31 -5
- package/dist/emitter/types.js.map +1 -1
- package/emitter/src/code-model.ts +1 -0
- package/emitter/src/constants.ts +27 -0
- package/emitter/src/emitter.ts +216 -96
- package/emitter/src/http.ts +29 -1
- package/emitter/src/lib.ts +20 -0
- package/emitter/src/run-python3.ts +1 -1
- package/emitter/src/types.ts +35 -5
- package/emitter/temp/tsconfig.tsbuildinfo +1 -1
- package/emitter/test/utils.test.ts +3 -0
- package/emitter/tsconfig.build.json +2 -1
- package/emitter/tsconfig.json +2 -1
- package/eng/scripts/ci/config/eslint-ci.config.mjs +10 -0
- package/eng/scripts/ci/config/mypy.ini +1 -1
- package/eng/scripts/ci/config/pylintrc +1 -1
- package/eng/scripts/ci/config/pyrightconfig.json +1 -1
- package/eng/scripts/ci/dev_requirements.txt +1 -1
- package/eng/scripts/ci/lint.ts +11 -4
- package/eng/scripts/ci/regenerate-common.ts +407 -136
- package/eng/scripts/ci/regenerate.ts +103 -194
- package/eng/scripts/ci/run-tests.ts +153 -48
- package/eng/scripts/ci/run_apiview.py +22 -16
- package/eng/scripts/ci/run_mypy.py +28 -18
- package/eng/scripts/ci/run_pylint.py +23 -16
- package/eng/scripts/ci/run_pyright.py +5 -2
- package/eng/scripts/ci/run_sphinx_build.py +24 -9
- package/eng/scripts/ci/util.py +61 -25
- package/eng/scripts/setup/__pycache__/package_manager.cpython-311.pyc +0 -0
- package/eng/scripts/setup/__pycache__/venvtools.cpython-311.pyc +0 -0
- package/eng/scripts/setup/build_pygen_wheel.py +3 -3
- package/eng/scripts/setup/install.py +3 -3
- package/eng/scripts/setup/install.ts +1 -1
- package/eng/scripts/setup/prepare.py +2 -2
- package/eng/scripts/setup/run-python3.ts +1 -1
- package/eng/scripts/setup/run_batch.py +197 -0
- package/eng/scripts/setup/venvtools.py +0 -1
- package/generator/build/lib/pygen/__init__.py +1 -3
- package/generator/build/lib/pygen/codegen/__init__.py +0 -1
- package/generator/build/lib/pygen/codegen/models/base.py +0 -1
- package/generator/build/lib/pygen/codegen/models/client.py +2 -1
- package/generator/build/lib/pygen/codegen/models/code_model.py +1 -0
- package/generator/build/lib/pygen/codegen/models/enum_type.py +0 -1
- package/generator/build/lib/pygen/codegen/models/model_type.py +1 -7
- package/generator/build/lib/pygen/codegen/models/parameter.py +16 -14
- package/generator/build/lib/pygen/codegen/serializers/__init__.py +27 -12
- package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +12 -3
- package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +6 -2
- package/generator/build/lib/pygen/codegen/serializers/import_serializer.py +17 -4
- package/generator/build/lib/pygen/codegen/serializers/parameter_serializer.py +17 -11
- package/generator/build/lib/pygen/codegen/templates/model_base.py.jinja2 +19 -7
- package/generator/build/lib/pygen/codegen/templates/packaging_templates/README.md.jinja2 +11 -3
- package/generator/build/lib/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +1 -1
- package/generator/build/lib/pygen/codegen/templates/serialization.py.jinja2 +5 -1
- package/generator/build/lib/pygen/preprocess/__init__.py +37 -30
- package/generator/component-detection-pip-report.json +7 -7
- package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
- package/generator/pygen/__init__.py +1 -3
- package/generator/pygen/codegen/__init__.py +0 -1
- package/generator/pygen/codegen/models/base.py +0 -1
- package/generator/pygen/codegen/models/client.py +2 -1
- package/generator/pygen/codegen/models/code_model.py +1 -0
- package/generator/pygen/codegen/models/enum_type.py +0 -1
- package/generator/pygen/codegen/models/model_type.py +1 -7
- package/generator/pygen/codegen/models/parameter.py +16 -14
- package/generator/pygen/codegen/serializers/__init__.py +27 -12
- package/generator/pygen/codegen/serializers/builder_serializer.py +12 -3
- package/generator/pygen/codegen/serializers/general_serializer.py +6 -2
- package/generator/pygen/codegen/serializers/import_serializer.py +17 -4
- package/generator/pygen/codegen/serializers/parameter_serializer.py +17 -11
- package/generator/pygen/codegen/templates/model_base.py.jinja2 +19 -7
- package/generator/pygen/codegen/templates/packaging_templates/README.md.jinja2 +11 -3
- package/generator/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +1 -1
- package/generator/pygen/codegen/templates/serialization.py.jinja2 +5 -1
- package/generator/pygen/preprocess/__init__.py +37 -30
- package/generator/pygen.egg-info/PKG-INFO +1 -2
- package/generator/pygen.egg-info/requires.txt +1 -1
- package/generator/setup.py +1 -2
- package/package.json +24 -9
|
@@ -132,20 +132,18 @@ HEADERS_HIDE_IN_METHOD = (
|
|
|
132
132
|
"client-request-id",
|
|
133
133
|
"return-client-request-id",
|
|
134
134
|
)
|
|
135
|
-
|
|
136
|
-
"
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
"
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
"type":
|
|
146
|
-
|
|
147
|
-
"name": "MatchConditions",
|
|
148
|
-
},
|
|
135
|
+
ETAG_MATCH_DATA = {
|
|
136
|
+
"clientName": "etag",
|
|
137
|
+
"etagRole": "ifMatch",
|
|
138
|
+
"description": "check if resource is changed. Set None to skip checking etag.",
|
|
139
|
+
}
|
|
140
|
+
ETAG_NONE_MATCH_DATA = {
|
|
141
|
+
"clientName": "match_condition",
|
|
142
|
+
"etagRole": "ifNoneMatch",
|
|
143
|
+
"description": "The match condition to use upon the etag.",
|
|
144
|
+
"type": {
|
|
145
|
+
"type": "sdkcore",
|
|
146
|
+
"name": "MatchConditions",
|
|
149
147
|
},
|
|
150
148
|
}
|
|
151
149
|
CLOUD_SETTING = {
|
|
@@ -166,6 +164,11 @@ def get_wire_name_lower(parameter: dict[str, Any]) -> str:
|
|
|
166
164
|
return (parameter.get("wireName") or "").lower()
|
|
167
165
|
|
|
168
166
|
|
|
167
|
+
def _get_etag_role(parameter: dict[str, Any]) -> Optional[str]:
|
|
168
|
+
"""Return 'ifMatch', 'ifNoneMatch', or None for this header parameter."""
|
|
169
|
+
return parameter.get("etagRole")
|
|
170
|
+
|
|
171
|
+
|
|
169
172
|
def headers_convert(yaml_data: dict[str, Any], replace_data: Any) -> None:
|
|
170
173
|
if isinstance(replace_data, dict):
|
|
171
174
|
for k, v in replace_data.items():
|
|
@@ -281,9 +284,8 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
281
284
|
value["name"] = upper_name
|
|
282
285
|
|
|
283
286
|
# add type for reference
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
yaml_data.append(v["type"])
|
|
287
|
+
if "type" in ETAG_NONE_MATCH_DATA:
|
|
288
|
+
yaml_data.append(ETAG_NONE_MATCH_DATA["type"])
|
|
287
289
|
yaml_data.append(CLOUD_SETTING["type"]) # type: ignore
|
|
288
290
|
|
|
289
291
|
def update_client(self, yaml_data: dict[str, Any]) -> None:
|
|
@@ -317,9 +319,10 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
317
319
|
if p["location"] == "header" and wire_name_lower == "client-request-id":
|
|
318
320
|
yaml_data["requestIdHeaderName"] = wire_name_lower
|
|
319
321
|
if self.version_tolerant and p["location"] == "header":
|
|
320
|
-
|
|
322
|
+
role = _get_etag_role(p)
|
|
323
|
+
if role == "ifMatch" and not property_if_match:
|
|
321
324
|
property_if_match = p
|
|
322
|
-
elif
|
|
325
|
+
elif role == "ifNoneMatch" and not property_if_none_match:
|
|
323
326
|
property_if_none_match = p
|
|
324
327
|
# pylint: disable=line-too-long
|
|
325
328
|
# some service(e.g. https://github.com/Azure/azure-rest-api-specs/blob/main/specification/cosmos-db/data-plane/Microsoft.Tables/preview/2019-02-02/table.json)
|
|
@@ -327,17 +330,19 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
327
330
|
if not property_if_match and property_if_none_match:
|
|
328
331
|
property_if_match = property_if_none_match.copy()
|
|
329
332
|
property_if_match["wireName"] = "if-match"
|
|
333
|
+
property_if_match["etagRole"] = "ifMatch"
|
|
330
334
|
if not property_if_none_match and property_if_match:
|
|
331
335
|
property_if_none_match = property_if_match.copy()
|
|
332
336
|
property_if_none_match["wireName"] = "if-none-match"
|
|
337
|
+
property_if_none_match["etagRole"] = "ifNoneMatch"
|
|
333
338
|
|
|
334
339
|
if property_if_match and property_if_none_match:
|
|
335
340
|
# arrange if-match and if-none-match to the end of parameters
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
]
|
|
341
|
+
etag_params = {id(property_if_match), id(property_if_none_match)}
|
|
342
|
+
o["parameters"] = [item for item in o["parameters"] if id(item) not in etag_params] + [
|
|
343
|
+
property_if_match,
|
|
344
|
+
property_if_none_match,
|
|
345
|
+
]
|
|
341
346
|
|
|
342
347
|
o["hasEtag"] = True
|
|
343
348
|
yaml_data["hasEtag"] = True
|
|
@@ -362,11 +367,9 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
362
367
|
yaml_data["clientName"].lower(), PadType.PARAMETER, yaml_data
|
|
363
368
|
)
|
|
364
369
|
if yaml_data.get("propertyToParameterName"):
|
|
365
|
-
# need to create a new one with padded keys
|
|
370
|
+
# need to create a new one with padded values (but NOT keys, since keys are wire names)
|
|
366
371
|
yaml_data["propertyToParameterName"] = {
|
|
367
|
-
self.pad_reserved_words(
|
|
368
|
-
param_name, PadType.PARAMETER, yaml_data
|
|
369
|
-
).lower()
|
|
372
|
+
prop: self.pad_reserved_words(param_name, PadType.PARAMETER, yaml_data).lower()
|
|
370
373
|
for prop, param_name in yaml_data["propertyToParameterName"].items()
|
|
371
374
|
}
|
|
372
375
|
wire_name_lower = (yaml_data.get("wireName") or "").lower()
|
|
@@ -374,8 +377,12 @@ class PreProcessPlugin(YamlUpdatePlugin):
|
|
|
374
377
|
wire_name_lower in HEADERS_HIDE_IN_METHOD or yaml_data.get("clientDefaultValue") == "multipart/form-data"
|
|
375
378
|
):
|
|
376
379
|
yaml_data["hideInMethod"] = True
|
|
377
|
-
if self.version_tolerant and yaml_data["location"] == "header"
|
|
378
|
-
|
|
380
|
+
if self.version_tolerant and yaml_data["location"] == "header":
|
|
381
|
+
role = _get_etag_role(yaml_data)
|
|
382
|
+
if role == "ifMatch":
|
|
383
|
+
headers_convert(yaml_data, ETAG_MATCH_DATA)
|
|
384
|
+
elif role == "ifNoneMatch":
|
|
385
|
+
headers_convert(yaml_data, ETAG_NONE_MATCH_DATA)
|
|
379
386
|
if wire_name_lower in ["$host", "content-type", "accept"] and yaml_data["type"]["type"] == "constant":
|
|
380
387
|
yaml_data["clientDefaultValue"] = yaml_data["type"]["value"]
|
|
381
388
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1",
|
|
3
|
-
"pip_version": "26.
|
|
3
|
+
"pip_version": "26.1.1",
|
|
4
4
|
"install": [
|
|
5
5
|
{
|
|
6
6
|
"download_info": {
|
|
7
7
|
"url": "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl",
|
|
8
8
|
"archive_info": {
|
|
9
|
-
"hash": "sha256=a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb",
|
|
10
9
|
"hashes": {
|
|
11
10
|
"sha256": "a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb"
|
|
12
|
-
}
|
|
11
|
+
},
|
|
12
|
+
"hash": "sha256=a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"is_direct": false,
|
|
@@ -122,13 +122,13 @@
|
|
|
122
122
|
],
|
|
123
123
|
"environment": {
|
|
124
124
|
"implementation_name": "cpython",
|
|
125
|
-
"implementation_version": "3.11.
|
|
125
|
+
"implementation_version": "3.11.15",
|
|
126
126
|
"os_name": "posix",
|
|
127
127
|
"platform_machine": "x86_64",
|
|
128
|
-
"platform_release": "6.
|
|
128
|
+
"platform_release": "6.17.0-1010-azure",
|
|
129
129
|
"platform_system": "Linux",
|
|
130
|
-
"platform_version": "#
|
|
131
|
-
"python_full_version": "3.11.
|
|
130
|
+
"platform_version": "#10~24.04.1-Ubuntu SMP Fri Mar 6 22:00:57 UTC 2026",
|
|
131
|
+
"python_full_version": "3.11.15",
|
|
132
132
|
"platform_python_implementation": "CPython",
|
|
133
133
|
"python_version": "3.11",
|
|
134
134
|
"sys_platform": "linux"
|
|
Binary file
|
|
@@ -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
|
-
|
|
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
|
|
|
@@ -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] = {}
|
|
@@ -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
|
-
|
|
113
|
-
base_description
|
|
114
|
-
|
|
115
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
+
self._package_files.append("setup.py.jinja2")
|
|
85
|
+
self._regenerate_files.add("setup.py")
|
|
84
86
|
else:
|
|
85
|
-
|
|
86
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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}.{
|
|
113
|
-
for
|
|
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
|
-
|
|
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.
|
|
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": [
|
|
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]):
|