@autorest/python 6.1.4 → 6.1.6
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/ChangeLog.md +35 -0
- package/autorest/__init__.py +10 -3
- package/autorest/black/__init__.py +2 -8
- package/autorest/codegen/models/base_builder.py +4 -1
- package/autorest/codegen/models/credential_types.py +3 -3
- package/autorest/codegen/models/operation.py +23 -17
- package/autorest/codegen/models/operation_group.py +5 -0
- package/autorest/codegen/models/parameter.py +7 -0
- package/autorest/codegen/models/primitive_types.py +3 -3
- package/autorest/codegen/models/response.py +7 -9
- package/autorest/codegen/serializers/__init__.py +6 -0
- package/autorest/codegen/serializers/builder_serializer.py +31 -3
- package/autorest/codegen/serializers/general_serializer.py +4 -0
- package/autorest/codegen/templates/operation_groups_container.py.jinja2 +1 -1
- package/autorest/codegen/templates/validation.py.jinja2 +38 -0
- package/autorest/m4reformatter/__init__.py +7 -1
- package/package.json +1 -1
package/ChangeLog.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Release History
|
|
2
2
|
|
|
3
|
+
### 2022-09-15 - 6.1.6
|
|
4
|
+
|
|
5
|
+
| Library | Min Version |
|
|
6
|
+
| ----------------------------------------------------------------------- | ----------- |
|
|
7
|
+
| `@autorest/core` | `3.8.4` |
|
|
8
|
+
| `@autorest/modelerfour` | `4.23.5` |
|
|
9
|
+
| `azure-core` dep of generated code | `1.24.0` |
|
|
10
|
+
| `isodate` dep of generated code | `0.6.1` |
|
|
11
|
+
| `msrest` dep of generated code (If generating legacy code) | `0.7.1` |
|
|
12
|
+
| `azure-mgmt-core` dep of generated code (If generating mgmt plane code) | `1.3.2` |
|
|
13
|
+
|
|
14
|
+
**Bug Fixes**
|
|
15
|
+
|
|
16
|
+
- Fix `--clear-output-folder` when `--black=true` #1410
|
|
17
|
+
|
|
18
|
+
### 2022-09-06 - 6.1.5
|
|
19
|
+
|
|
20
|
+
| Library | Min Version |
|
|
21
|
+
| ----------------------------------------------------------------------- | ----------- |
|
|
22
|
+
| `@autorest/core` | `3.8.4` |
|
|
23
|
+
| `@autorest/modelerfour` | `4.23.5` |
|
|
24
|
+
| `azure-core` dep of generated code | `1.24.0` |
|
|
25
|
+
| `isodate` dep of generated code | `0.6.1` |
|
|
26
|
+
| `msrest` dep of generated code (If generating legacy code) | `0.7.1` |
|
|
27
|
+
| `azure-mgmt-core` dep of generated code (If generating mgmt plane code) | `1.3.2` |
|
|
28
|
+
|
|
29
|
+
**Bug Fixes**
|
|
30
|
+
|
|
31
|
+
- Fix `api_version` error when there are multi different `api-version`(not multiapi) #1429
|
|
32
|
+
- Fix generator raising `KeyError` in corner case when generating an LRO-paging operation #1425
|
|
33
|
+
|
|
34
|
+
**Other Changes**
|
|
35
|
+
|
|
36
|
+
- Default `304` errors to throw `azure.core.exception.ResourceNotFoundError`s #1415
|
|
37
|
+
|
|
3
38
|
### 2022-08-31 - 6.1.4
|
|
4
39
|
|
|
5
40
|
| Library | Min Version |
|
package/autorest/__init__.py
CHANGED
|
@@ -7,7 +7,7 @@ import logging
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
import json
|
|
9
9
|
from abc import ABC, abstractmethod
|
|
10
|
-
from typing import Any, Dict, Union
|
|
10
|
+
from typing import Any, Dict, Union, List
|
|
11
11
|
|
|
12
12
|
import yaml
|
|
13
13
|
|
|
@@ -22,6 +22,7 @@ _LOGGER = logging.getLogger(__name__)
|
|
|
22
22
|
class ReaderAndWriter:
|
|
23
23
|
def __init__(self, *, output_folder: Union[str, Path], **kwargs: Any) -> None:
|
|
24
24
|
self.output_folder = Path(output_folder)
|
|
25
|
+
self._list_file: List[str] = []
|
|
25
26
|
try:
|
|
26
27
|
with open(
|
|
27
28
|
Path(self.output_folder) / Path("..") / Path("python.json"), "r"
|
|
@@ -37,7 +38,7 @@ class ReaderAndWriter:
|
|
|
37
38
|
self.options.update(python_json)
|
|
38
39
|
|
|
39
40
|
def read_file(self, path: Union[str, Path]) -> str:
|
|
40
|
-
"""
|
|
41
|
+
"""Directly reading from disk"""
|
|
41
42
|
# make path relative to output folder
|
|
42
43
|
try:
|
|
43
44
|
with open(self.output_folder / Path(path), "r") as fd:
|
|
@@ -46,13 +47,16 @@ class ReaderAndWriter:
|
|
|
46
47
|
return ""
|
|
47
48
|
|
|
48
49
|
def write_file(self, filename: Union[str, Path], file_content: str) -> None:
|
|
49
|
-
"""
|
|
50
|
+
"""Directly writing to disk"""
|
|
50
51
|
file_folder = Path(filename).parent
|
|
51
52
|
if not Path.is_dir(self.output_folder / file_folder):
|
|
52
53
|
Path.mkdir(self.output_folder / file_folder, parents=True)
|
|
53
54
|
with open(self.output_folder / Path(filename), "w") as fd:
|
|
54
55
|
fd.write(file_content)
|
|
55
56
|
|
|
57
|
+
def list_file(self) -> List[str]:
|
|
58
|
+
return [str(f) for f in self.output_folder.glob("**/*") if f.is_file()]
|
|
59
|
+
|
|
56
60
|
|
|
57
61
|
class ReaderAndWriterAutorest(ReaderAndWriter):
|
|
58
62
|
def __init__(
|
|
@@ -67,6 +71,9 @@ class ReaderAndWriterAutorest(ReaderAndWriter):
|
|
|
67
71
|
def write_file(self, filename: Union[str, Path], file_content: str) -> None:
|
|
68
72
|
return self._autorestapi.write_file(filename, file_content)
|
|
69
73
|
|
|
74
|
+
def list_file(self) -> List[str]:
|
|
75
|
+
return self._autorestapi.list_inputs()
|
|
76
|
+
|
|
70
77
|
|
|
71
78
|
class Plugin(ReaderAndWriter, ABC):
|
|
72
79
|
"""A base class for autorest plugin.
|
|
@@ -30,16 +30,10 @@ class BlackScriptPlugin(Plugin): # pylint: disable=abstract-method
|
|
|
30
30
|
|
|
31
31
|
def process(self) -> bool:
|
|
32
32
|
# apply format_file on every file in the output folder
|
|
33
|
-
list(
|
|
34
|
-
map(
|
|
35
|
-
self.format_file,
|
|
36
|
-
[f for f in self.output_folder.glob("**/*") if f.is_file()],
|
|
37
|
-
)
|
|
38
|
-
)
|
|
33
|
+
list(map(self.format_file, [Path(f) for f in self.list_file()]))
|
|
39
34
|
return True
|
|
40
35
|
|
|
41
|
-
def format_file(self,
|
|
42
|
-
file = full_path.relative_to(self.output_folder)
|
|
36
|
+
def format_file(self, file: Path) -> None:
|
|
43
37
|
file_content = self.read_file(file)
|
|
44
38
|
if not file.suffix == ".py":
|
|
45
39
|
self.write_file(file, file_content)
|
|
@@ -31,7 +31,9 @@ if TYPE_CHECKING:
|
|
|
31
31
|
_LOGGER = logging.getLogger(__name__)
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
class BaseBuilder(
|
|
34
|
+
class BaseBuilder(
|
|
35
|
+
Generic[ParameterListType], BaseModel
|
|
36
|
+
): # pylint: disable=too-many-instance-attributes
|
|
35
37
|
"""Base class for Operations and Request Builders"""
|
|
36
38
|
|
|
37
39
|
def __init__(
|
|
@@ -56,6 +58,7 @@ class BaseBuilder(Generic[ParameterListType], BaseModel):
|
|
|
56
58
|
self.group_name: str = yaml_data["groupName"]
|
|
57
59
|
self.is_overload: bool = yaml_data["isOverload"]
|
|
58
60
|
self.api_versions: List[str] = yaml_data["apiVersions"]
|
|
61
|
+
self.added_on: Optional[str] = yaml_data.get("addedOn")
|
|
59
62
|
|
|
60
63
|
if code_model.options["version_tolerant"] and yaml_data.get("abstract"):
|
|
61
64
|
_LOGGER.warning(
|
|
@@ -159,18 +159,18 @@ class TokenCredentialType(
|
|
|
159
159
|
"""Type of a token credential. Used by BearerAuth and ARMChallenge policies"""
|
|
160
160
|
|
|
161
161
|
def type_annotation(self, **kwargs: Any) -> str: # pylint: disable=no-self-use
|
|
162
|
-
if kwargs.
|
|
162
|
+
if kwargs.get("async_mode"):
|
|
163
163
|
return '"AsyncTokenCredential"'
|
|
164
164
|
return '"TokenCredential"'
|
|
165
165
|
|
|
166
166
|
def docstring_type(self, **kwargs: Any) -> str: # pylint: disable=no-self-use
|
|
167
|
-
if kwargs.
|
|
167
|
+
if kwargs.get("async_mode"):
|
|
168
168
|
return "~azure.core.credentials_async.AsyncTokenCredential"
|
|
169
169
|
return "~azure.core.credentials.TokenCredential"
|
|
170
170
|
|
|
171
171
|
def imports(self, **kwargs: Any) -> FileImport: # pylint: disable=no-self-use
|
|
172
172
|
file_import = FileImport()
|
|
173
|
-
if kwargs.
|
|
173
|
+
if kwargs.get("async_mode"):
|
|
174
174
|
file_import.add_submodule_import(
|
|
175
175
|
"azure.core.credentials_async",
|
|
176
176
|
"AsyncTokenCredential",
|
|
@@ -209,6 +209,12 @@ class OperationBase( # pylint: disable=too-many-public-methods
|
|
|
209
209
|
file_import.add_submodule_import(
|
|
210
210
|
"typing", "Union", ImportType.STDLIB, TypingSection.CONDITIONAL
|
|
211
211
|
)
|
|
212
|
+
if self.added_on:
|
|
213
|
+
file_import.add_submodule_import(
|
|
214
|
+
f"{'.' if async_mode else ''}.._validation",
|
|
215
|
+
"api_version_validation",
|
|
216
|
+
ImportType.LOCAL,
|
|
217
|
+
)
|
|
212
218
|
return file_import
|
|
213
219
|
|
|
214
220
|
def imports_for_multiapi(self, async_mode: bool, **kwargs: Any) -> FileImport:
|
|
@@ -244,6 +250,11 @@ class OperationBase( # pylint: disable=too-many-public-methods
|
|
|
244
250
|
for kwarg in kwargs_to_pop
|
|
245
251
|
)
|
|
246
252
|
|
|
253
|
+
@property
|
|
254
|
+
def need_validation(self) -> bool:
|
|
255
|
+
"""Whether we need parameter / operation validation. For API version."""
|
|
256
|
+
return bool(self.added_on) or any(p for p in self.parameters if p.added_on)
|
|
257
|
+
|
|
247
258
|
def get_request_builder_import(
|
|
248
259
|
self,
|
|
249
260
|
request_builder: Union[RequestBuilder, OverloadedRequestBuilder],
|
|
@@ -291,27 +302,22 @@ class OperationBase( # pylint: disable=too-many-public-methods
|
|
|
291
302
|
file_import.merge(self.parameters.body_parameter.type.imports(**kwargs))
|
|
292
303
|
|
|
293
304
|
# Exceptions
|
|
294
|
-
|
|
295
|
-
"
|
|
296
|
-
|
|
305
|
+
errors = [
|
|
306
|
+
"map_error",
|
|
307
|
+
"HttpResponseError",
|
|
308
|
+
"ClientAuthenticationError",
|
|
309
|
+
"ResourceNotFoundError",
|
|
310
|
+
"ResourceExistsError",
|
|
311
|
+
"ResourceNotModifiedError",
|
|
312
|
+
]
|
|
313
|
+
for error in errors:
|
|
314
|
+
file_import.add_submodule_import(
|
|
315
|
+
"azure.core.exceptions", error, ImportType.AZURECORE
|
|
316
|
+
)
|
|
297
317
|
if self.code_model.options["azure_arm"]:
|
|
298
318
|
file_import.add_submodule_import(
|
|
299
319
|
"azure.mgmt.core.exceptions", "ARMErrorFormat", ImportType.AZURECORE
|
|
300
320
|
)
|
|
301
|
-
file_import.add_submodule_import(
|
|
302
|
-
"azure.core.exceptions", "HttpResponseError", ImportType.AZURECORE
|
|
303
|
-
)
|
|
304
|
-
file_import.add_submodule_import(
|
|
305
|
-
"azure.core.exceptions",
|
|
306
|
-
"ClientAuthenticationError",
|
|
307
|
-
ImportType.AZURECORE,
|
|
308
|
-
)
|
|
309
|
-
file_import.add_submodule_import(
|
|
310
|
-
"azure.core.exceptions", "ResourceNotFoundError", ImportType.AZURECORE
|
|
311
|
-
)
|
|
312
|
-
file_import.add_submodule_import(
|
|
313
|
-
"azure.core.exceptions", "ResourceExistsError", ImportType.AZURECORE
|
|
314
|
-
)
|
|
315
321
|
|
|
316
322
|
if self.has_kwargs_to_pop_with_default(
|
|
317
323
|
self.parameters.kwargs_to_pop, ParameterLocation.HEADER
|
|
@@ -66,6 +66,11 @@ class OperationGroup(BaseModel):
|
|
|
66
66
|
return " # type: ignore"
|
|
67
67
|
return ""
|
|
68
68
|
|
|
69
|
+
@property
|
|
70
|
+
def need_validation(self) -> bool:
|
|
71
|
+
"""Whether any of its operations need validation"""
|
|
72
|
+
return any(o for o in self.operations if o.need_validation)
|
|
73
|
+
|
|
69
74
|
def imports(self, async_mode: bool) -> FileImport:
|
|
70
75
|
file_import = FileImport()
|
|
71
76
|
|
|
@@ -82,6 +82,7 @@ class _ParameterBase(
|
|
|
82
82
|
self.in_flattened_body: bool = self.yaml_data.get("inFlattenedBody", False)
|
|
83
83
|
self.grouper: bool = self.yaml_data.get("grouper", False)
|
|
84
84
|
self.check_client_input: bool = self.yaml_data.get("checkClientInput", False)
|
|
85
|
+
self.added_on: Optional[str] = self.yaml_data.get("addedOn")
|
|
85
86
|
|
|
86
87
|
@property
|
|
87
88
|
def constant(self) -> bool:
|
|
@@ -152,6 +153,12 @@ class _ParameterBase(
|
|
|
152
153
|
)
|
|
153
154
|
if self.optional and self.client_default_value is None:
|
|
154
155
|
file_import.add_submodule_import("typing", "Optional", ImportType.STDLIB)
|
|
156
|
+
if self.added_on:
|
|
157
|
+
file_import.add_submodule_import(
|
|
158
|
+
f"{'.' if async_mode else ''}.._validation",
|
|
159
|
+
"api_version_validation",
|
|
160
|
+
ImportType.LOCAL,
|
|
161
|
+
)
|
|
155
162
|
return file_import
|
|
156
163
|
|
|
157
164
|
@property
|
|
@@ -122,13 +122,13 @@ class BinaryIteratorType(PrimitiveType):
|
|
|
122
122
|
return "IO"
|
|
123
123
|
|
|
124
124
|
def docstring_type(self, **kwargs: Any) -> str:
|
|
125
|
-
return "AsyncIterator[bytes]" if kwargs.
|
|
125
|
+
return "AsyncIterator[bytes]" if kwargs.get("async_mode") else "Iterator[bytes]"
|
|
126
126
|
|
|
127
127
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
128
128
|
return self.docstring_type(**kwargs)
|
|
129
129
|
|
|
130
130
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
131
|
-
iterator = "Async iterator" if kwargs.
|
|
131
|
+
iterator = "Async iterator" if kwargs.get("async_mode") else "Iterator"
|
|
132
132
|
return f"{iterator} of the response bytes"
|
|
133
133
|
|
|
134
134
|
@property
|
|
@@ -137,7 +137,7 @@ class BinaryIteratorType(PrimitiveType):
|
|
|
137
137
|
|
|
138
138
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
139
139
|
file_import = FileImport()
|
|
140
|
-
iterator = "AsyncIterator" if kwargs.
|
|
140
|
+
iterator = "AsyncIterator" if kwargs.get("async_mode") else "Iterator"
|
|
141
141
|
file_import.add_submodule_import("typing", iterator, ImportType.STDLIB)
|
|
142
142
|
return file_import
|
|
143
143
|
|
|
@@ -170,7 +170,7 @@ class PagingResponse(Response):
|
|
|
170
170
|
|
|
171
171
|
def _imports_shared(self, **kwargs: Any) -> FileImport:
|
|
172
172
|
file_import = super()._imports_shared(**kwargs)
|
|
173
|
-
async_mode = kwargs.
|
|
173
|
+
async_mode = kwargs.get("async_mode", False)
|
|
174
174
|
pager_import_path = ".".join(self.get_pager_path(async_mode).split(".")[:-1])
|
|
175
175
|
pager = self.get_pager(async_mode)
|
|
176
176
|
|
|
@@ -179,7 +179,7 @@ class PagingResponse(Response):
|
|
|
179
179
|
|
|
180
180
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
181
181
|
file_import = self._imports_shared(**kwargs)
|
|
182
|
-
async_mode = kwargs.
|
|
182
|
+
async_mode = kwargs.get("async_mode")
|
|
183
183
|
if async_mode:
|
|
184
184
|
file_import.add_submodule_import(
|
|
185
185
|
"azure.core.async_paging", "AsyncList", ImportType.AZURECORE
|
|
@@ -234,16 +234,14 @@ class LROResponse(Response):
|
|
|
234
234
|
return self.get_base_polling_method_path(async_mode).split(".")[-1]
|
|
235
235
|
|
|
236
236
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
237
|
-
return f"{self.get_poller(kwargs.
|
|
237
|
+
return f"{self.get_poller(kwargs.get('async_mode', False))}[{super().type_annotation(**kwargs)}]"
|
|
238
238
|
|
|
239
239
|
def docstring_type(self, **kwargs: Any) -> str:
|
|
240
|
-
return f"~{self.get_poller_path(kwargs.
|
|
240
|
+
return f"~{self.get_poller_path(kwargs.get('async_mode', False))}[{super().docstring_type(**kwargs)}]"
|
|
241
241
|
|
|
242
242
|
def docstring_text(self, **kwargs) -> str:
|
|
243
243
|
super_text = super().docstring_text(**kwargs)
|
|
244
|
-
base_description = (
|
|
245
|
-
f"An instance of {self.get_poller(kwargs.pop('async_mode'))} that returns "
|
|
246
|
-
)
|
|
244
|
+
base_description = f"An instance of {self.get_poller(kwargs.get('async_mode', False))} that returns "
|
|
247
245
|
if not self.code_model.options["version_tolerant"]:
|
|
248
246
|
base_description += "either "
|
|
249
247
|
return base_description + super_text
|
|
@@ -297,11 +295,11 @@ class LROResponse(Response):
|
|
|
297
295
|
class LROPagingResponse(LROResponse, PagingResponse):
|
|
298
296
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
299
297
|
paging_type_annotation = PagingResponse.type_annotation(self, **kwargs)
|
|
300
|
-
return f"{self.get_poller(kwargs.
|
|
298
|
+
return f"{self.get_poller(kwargs.get('async_mode', False))}[{paging_type_annotation}]"
|
|
301
299
|
|
|
302
300
|
def docstring_type(self, **kwargs: Any) -> str:
|
|
303
301
|
paging_docstring_type = PagingResponse.docstring_type(self, **kwargs)
|
|
304
|
-
return f"~{self.get_poller_path(kwargs.
|
|
302
|
+
return f"~{self.get_poller_path(kwargs.get('async_mode', False))}[{paging_docstring_type}]"
|
|
305
303
|
|
|
306
304
|
def docstring_text(self, **kwargs) -> str:
|
|
307
305
|
base_description = (
|
|
@@ -428,6 +428,12 @@ class JinjaSerializer(ReaderAndWriter): # pylint: disable=abstract-method
|
|
|
428
428
|
general_serializer.serialize_serialization_file(),
|
|
429
429
|
)
|
|
430
430
|
|
|
431
|
+
if any(og for og in self.code_model.operation_groups if og.need_validation):
|
|
432
|
+
self.write_file(
|
|
433
|
+
namespace_path / Path("_validation.py"),
|
|
434
|
+
general_serializer.serialize_validation_file(),
|
|
435
|
+
)
|
|
436
|
+
|
|
431
437
|
# Write the config file
|
|
432
438
|
if self.code_model.request_builders:
|
|
433
439
|
self.write_file(
|
|
@@ -173,6 +173,22 @@ def _get_json_response_template_to_status_codes(
|
|
|
173
173
|
return retval
|
|
174
174
|
|
|
175
175
|
|
|
176
|
+
def _api_version_validation(builder: OperationType) -> str:
|
|
177
|
+
retval: List[str] = []
|
|
178
|
+
if builder.added_on:
|
|
179
|
+
retval.append(f' method_added_on="{builder.added_on}",')
|
|
180
|
+
params_added_on = defaultdict(list)
|
|
181
|
+
for parameter in builder.parameters:
|
|
182
|
+
if parameter.added_on:
|
|
183
|
+
params_added_on[parameter.added_on].append(parameter.client_name)
|
|
184
|
+
if params_added_on:
|
|
185
|
+
retval.append(f" params_added_on={dict(params_added_on)},")
|
|
186
|
+
if retval:
|
|
187
|
+
retval_str = "\n".join(retval)
|
|
188
|
+
return f"@api_version_validation(\n{retval_str}\n)"
|
|
189
|
+
return ""
|
|
190
|
+
|
|
191
|
+
|
|
176
192
|
class _BuilderBaseSerializer(Generic[BuilderType]): # pylint: disable=abstract-method
|
|
177
193
|
def __init__(self, code_model: CodeModel, async_mode: bool) -> None:
|
|
178
194
|
self.code_model = code_model
|
|
@@ -590,8 +606,10 @@ class _OperationSerializer(
|
|
|
590
606
|
|
|
591
607
|
def decorators(self, builder: OperationType) -> List[str]:
|
|
592
608
|
"""Decorators for the method"""
|
|
593
|
-
|
|
594
|
-
|
|
609
|
+
retval = super().decorators(builder)
|
|
610
|
+
if _api_version_validation(builder):
|
|
611
|
+
retval.append(_api_version_validation(builder))
|
|
612
|
+
return retval
|
|
595
613
|
|
|
596
614
|
def param_description(
|
|
597
615
|
self, builder: OperationType
|
|
@@ -1043,6 +1061,8 @@ class _OperationSerializer(
|
|
|
1043
1061
|
retval.append(" 404: ResourceNotFoundError,")
|
|
1044
1062
|
if not 409 in builder.non_default_error_status_codes:
|
|
1045
1063
|
retval.append(" 409: ResourceExistsError,")
|
|
1064
|
+
if not 304 in builder.non_default_error_status_codes:
|
|
1065
|
+
retval.append(" 304: ResourceNotModifiedError,")
|
|
1046
1066
|
for excep in builder.non_default_errors:
|
|
1047
1067
|
error_model_str = ""
|
|
1048
1068
|
if (
|
|
@@ -1071,6 +1091,11 @@ class _OperationSerializer(
|
|
|
1071
1091
|
" 409: lambda response: ResourceExistsError(response=response"
|
|
1072
1092
|
f"{error_model_str}{error_format_str}),"
|
|
1073
1093
|
)
|
|
1094
|
+
elif status_code == 304:
|
|
1095
|
+
retval.append(
|
|
1096
|
+
" 304: lambda response: ResourceNotModifiedError(response=response"
|
|
1097
|
+
f"{error_model_str}{error_format_str}),"
|
|
1098
|
+
)
|
|
1074
1099
|
elif not error_model_str and not error_format_str:
|
|
1075
1100
|
retval.append(f" {status_code}: HttpResponseError,")
|
|
1076
1101
|
else:
|
|
@@ -1080,7 +1105,8 @@ class _OperationSerializer(
|
|
|
1080
1105
|
)
|
|
1081
1106
|
else:
|
|
1082
1107
|
retval.append(
|
|
1083
|
-
" 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError"
|
|
1108
|
+
" 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, "
|
|
1109
|
+
"304: ResourceNotModifiedError"
|
|
1084
1110
|
)
|
|
1085
1111
|
retval.append("}")
|
|
1086
1112
|
retval.append("error_map.update(kwargs.pop('error_map', {}) or {})")
|
|
@@ -1130,6 +1156,8 @@ class _PagingOperationSerializer(
|
|
|
1130
1156
|
return ["@overload"]
|
|
1131
1157
|
if self.code_model.options["tracing"] and builder.want_tracing:
|
|
1132
1158
|
retval.append("@distributed_trace")
|
|
1159
|
+
if _api_version_validation(builder):
|
|
1160
|
+
retval.append(_api_version_validation(builder))
|
|
1133
1161
|
return retval
|
|
1134
1162
|
|
|
1135
1163
|
def call_next_link_request_builder(self, builder: PagingOperationType) -> List[str]:
|
|
@@ -120,3 +120,7 @@ class GeneralSerializer:
|
|
|
120
120
|
def serialize_serialization_file(self) -> str:
|
|
121
121
|
template = self.env.get_template("serialization.py.jinja2")
|
|
122
122
|
return template.render(code_model=self.code_model)
|
|
123
|
+
|
|
124
|
+
def serialize_validation_file(self) -> str:
|
|
125
|
+
template = self.env.get_template("validation.py.jinja2")
|
|
126
|
+
return template.render(code_model=self.code_model)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{{ code_model.options['license_header'] }}
|
|
2
|
+
import functools
|
|
3
|
+
|
|
4
|
+
def api_version_validation(**kwargs):
|
|
5
|
+
params_added_on = kwargs.pop("params_added_on", {})
|
|
6
|
+
method_added_on = kwargs.pop("method_added_on", "")
|
|
7
|
+
|
|
8
|
+
def decorator(func):
|
|
9
|
+
@functools.wraps(func)
|
|
10
|
+
def wrapper(*args, **kwargs):
|
|
11
|
+
try:
|
|
12
|
+
# this assumes the client has an _api_version attribute
|
|
13
|
+
client = args[0]
|
|
14
|
+
client_api_version = client._config.api_version # pylint: disable=protected-access
|
|
15
|
+
except AttributeError:
|
|
16
|
+
return func(*args, **kwargs)
|
|
17
|
+
|
|
18
|
+
if method_added_on > client_api_version:
|
|
19
|
+
raise ValueError(
|
|
20
|
+
f"'{func.__name__}' is not available in API version "
|
|
21
|
+
f"{client_api_version}. Pass service API version {method_added_on} or newer to your client."
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
unsupported = {
|
|
25
|
+
parameter: api_version
|
|
26
|
+
for api_version, parameters in params_added_on.items()
|
|
27
|
+
for parameter in parameters
|
|
28
|
+
if parameter in kwargs and api_version > client_api_version
|
|
29
|
+
}
|
|
30
|
+
if unsupported:
|
|
31
|
+
raise ValueError("".join([
|
|
32
|
+
f"'{param}' is not available in API version {client_api_version}. "
|
|
33
|
+
f"Use service API version {version} or newer.\n"
|
|
34
|
+
for param, version in unsupported.items()
|
|
35
|
+
]))
|
|
36
|
+
return func(*args, **kwargs)
|
|
37
|
+
return wrapper
|
|
38
|
+
return decorator
|
|
@@ -426,6 +426,10 @@ class M4Reformatter(
|
|
|
426
426
|
): # pylint: disable=too-many-public-methods
|
|
427
427
|
"""Add Python naming information."""
|
|
428
428
|
|
|
429
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
430
|
+
super().__init__(*args, **kwargs)
|
|
431
|
+
self.check_client_input: bool = False
|
|
432
|
+
|
|
429
433
|
@property
|
|
430
434
|
def azure_arm(self) -> bool:
|
|
431
435
|
return bool(self._autorestapi.get_boolean_value("azure-arm"))
|
|
@@ -802,7 +806,7 @@ class M4Reformatter(
|
|
|
802
806
|
param["inDocstring"] = False
|
|
803
807
|
if self.legacy:
|
|
804
808
|
param["implementation"] = "Method"
|
|
805
|
-
param["checkClientInput"] =
|
|
809
|
+
param["checkClientInput"] = self.check_client_input
|
|
806
810
|
if has_flattened_body and param.get("targetProperty"):
|
|
807
811
|
retval.append(self.update_flattened_parameter(param, body_parameter))
|
|
808
812
|
continue
|
|
@@ -935,6 +939,8 @@ class M4Reformatter(
|
|
|
935
939
|
|
|
936
940
|
client_name = "base_url" if self.legacy else "endpoint"
|
|
937
941
|
global_parameter["language"]["default"]["description"] = "Service URL."
|
|
942
|
+
elif name == "api_version":
|
|
943
|
+
self.check_client_input = True
|
|
938
944
|
global_params.append(
|
|
939
945
|
self.update_parameter(
|
|
940
946
|
global_parameter, override_client_name=client_name
|