@autorest/python 6.1.5 → 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 CHANGED
@@ -1,5 +1,20 @@
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
+
3
18
  ### 2022-09-06 - 6.1.5
4
19
 
5
20
  | Library | Min Version |
@@ -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
- """How does one read a file in cadl?"""
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
- """How does writing work in cadl?"""
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, full_path) -> None:
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(Generic[ParameterListType], BaseModel):
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(
@@ -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],
@@ -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
@@ -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
- super_decorators = super().decorators(builder)
594
- return super_decorators
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
@@ -1138,6 +1156,8 @@ class _PagingOperationSerializer(
1138
1156
  return ["@overload"]
1139
1157
  if self.code_model.options["tracing"] and builder.want_tracing:
1140
1158
  retval.append("@distributed_trace")
1159
+ if _api_version_validation(builder):
1160
+ retval.append(_api_version_validation(builder))
1141
1161
  return retval
1142
1162
 
1143
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)
@@ -17,4 +17,4 @@
17
17
  {% endif %}
18
18
  {% for operation_group in operation_groups %}
19
19
  {% include "operation_group.py.jinja2" %}
20
- {% endfor %}
20
+ {% endfor %}
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autorest/python",
3
- "version": "6.1.5",
3
+ "version": "6.1.6",
4
4
  "description": "The Python extension for generators in AutoRest.",
5
5
  "scripts": {
6
6
  "prepare": "node run-python3.js prepare.py",