@autorest/python 6.0.1 → 6.1.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/ChangeLog.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Release History
2
2
 
3
+ ### 2022-07-20 - 6.1.0
4
+
5
+ | Library | Min Version |
6
+ | ----------------------------------------------------------------------- | ----------- |
7
+ | `@autorest/core` | `3.8.1` |
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.0` |
13
+
14
+ **New Features**
15
+
16
+ - Add new plugin `MultiClient` and new flag `--multiclientscript` to handle package structure of multi client #1328
17
+
18
+ **Bug Fixes**
19
+
20
+ - Fallback unrecognized type as string to avoid a fatal error. #1341
21
+ - Fix regression in default namespace for SDKs generated without `--namespace` flag #1354
22
+
23
+ **Other Changes**
24
+
25
+ - Generated code no longer supports Python 3.6 #1353
26
+ - Order json input and response template entries by whether they are required or not #1335
27
+ - Reduce extreme amount of `black` logs when running in `--debug` mode to just log errors
28
+
3
29
  ### 2022-06-29 - 6.0.1
4
30
 
5
31
  | Library | Min Version |
package/README.md CHANGED
@@ -23,7 +23,7 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio
23
23
 
24
24
  #### Python code gen
25
25
 
26
- ```yaml !$(multiapiscript)
26
+ ```yaml !$(multiapiscript) && !$(multiclientscript)
27
27
  # default values for version tolerant and black
28
28
  black: true
29
29
  ```
@@ -43,7 +43,7 @@ modelerfour:
43
43
  allow-no-input: true
44
44
  ```
45
45
 
46
- ```yaml !$(multiapiscript)
46
+ ```yaml !$(multiapiscript) && !$(multiclientscript)
47
47
  pass-thru:
48
48
  - model-deduplicator
49
49
  - subset-reducer
@@ -161,6 +161,25 @@ scope-postprocess/emitter:
161
161
  output-artifact: python-files
162
162
  ```
163
163
 
164
+ # Multi Client pipeline
165
+
166
+ ```yaml $(multiclientscript)
167
+ pipeline:
168
+ python/multiclientscript:
169
+ scope: multiclientscript
170
+ output-artifact: python-files
171
+
172
+ python/multiclientscript/emitter:
173
+ input: multiclientscript
174
+ scope: scope-multiclientscript/emitter
175
+
176
+ scope-multiclientscript/emitter:
177
+ input-artifact: python-files
178
+ output-uri-expr: $key
179
+
180
+ output-artifact: python-files
181
+ ```
182
+
164
183
  # Help
165
184
 
166
185
  ```yaml
@@ -19,21 +19,33 @@ _LOGGER = logging.getLogger(__name__)
19
19
 
20
20
 
21
21
  class ReaderAndWriter:
22
- def __init__(self, **kwargs: Any) -> None:
23
- pass
22
+ def __init__(self, *, output_folder: Union[str, Path], **kwargs: Any) -> None:
23
+ self.output_folder = Path(output_folder)
24
+ self.options = kwargs
24
25
 
25
26
  def read_file(self, path: Union[str, Path]) -> str:
26
27
  """How does one read a file in cadl?"""
27
- raise NotImplementedError("Haven't plugged in Cadl yet")
28
+ # make path relative to output folder
29
+ try:
30
+ with open(self.output_folder / Path(path), "r") as fd:
31
+ return fd.read()
32
+ except FileNotFoundError:
33
+ return ""
28
34
 
29
35
  def write_file(self, filename: Union[str, Path], file_content: str) -> None:
30
36
  """How does writing work in cadl?"""
31
- raise NotImplementedError("Haven't plugged in Cadl yet")
37
+ file_folder = Path(filename).parent
38
+ if not Path.is_dir(self.output_folder / file_folder):
39
+ Path.mkdir(self.output_folder / file_folder, parents=True)
40
+ with open(self.output_folder / Path(filename), "w") as fd:
41
+ fd.write(file_content)
32
42
 
33
43
 
34
44
  class ReaderAndWriterAutorest(ReaderAndWriter):
35
- def __init__(self, *, autorestapi: AutorestAPI) -> None:
36
- super().__init__()
45
+ def __init__(
46
+ self, *, output_folder: Union[str, Path], autorestapi: AutorestAPI
47
+ ) -> None:
48
+ super().__init__(output_folder=output_folder)
37
49
  self._autorestapi = autorestapi
38
50
 
39
51
  def read_file(self, path: Union[str, Path]) -> str:
@@ -49,10 +61,6 @@ class Plugin(ReaderAndWriter, ABC):
49
61
  :param autorestapi: An autorest API instance
50
62
  """
51
63
 
52
- def __init__(self, **kwargs: Any) -> None:
53
- super().__init__(**kwargs)
54
- self.options: Dict[str, Any] = {}
55
-
56
64
  @abstractmethod
57
65
  def process(self) -> bool:
58
66
  """The plugin process.
@@ -67,8 +75,10 @@ class Plugin(ReaderAndWriter, ABC):
67
75
  class PluginAutorest(Plugin, ReaderAndWriterAutorest):
68
76
  """For our Autorest plugins, we want to take autorest api as input as options, then pass it to the Plugin"""
69
77
 
70
- def __init__(self, autorestapi: AutorestAPI) -> None:
71
- super().__init__(autorestapi=autorestapi)
78
+ def __init__(
79
+ self, autorestapi: AutorestAPI, *, output_folder: Union[str, Path]
80
+ ) -> None:
81
+ super().__init__(autorestapi=autorestapi, output_folder=output_folder)
72
82
  self.options = self.get_options()
73
83
 
74
84
  @abstractmethod
@@ -79,15 +89,24 @@ class PluginAutorest(Plugin, ReaderAndWriterAutorest):
79
89
  class YamlUpdatePlugin(Plugin):
80
90
  """A plugin that update the YAML as input."""
81
91
 
92
+ def get_yaml(self) -> Dict[str, Any]:
93
+ # cadl file doesn't have to be relative to output folder
94
+ with open(self.options["cadl_file"], "r") as fd:
95
+ return yaml.safe_load(fd.read())
96
+
97
+ def write_yaml(self, yaml_string: str) -> None:
98
+ with open(self.options["cadl_file"], "w") as fd:
99
+ fd.write(yaml_string)
100
+
82
101
  def process(self) -> bool:
83
102
  # List the input file, should be only one
84
- yaml_data = yaml.safe_load(self.read_file("code-model-v4-no-tags.yaml"))
103
+ yaml_data = self.get_yaml()
85
104
 
86
105
  self.update_yaml(yaml_data)
87
106
 
88
107
  yaml_string = yaml.safe_dump(yaml_data)
89
108
 
90
- self.write_file("code-model-v4-no-tags.yaml", yaml_string)
109
+ self.write_yaml(yaml_string)
91
110
  return True
92
111
 
93
112
  @abstractmethod
@@ -103,6 +122,12 @@ class YamlUpdatePlugin(Plugin):
103
122
  class YamlUpdatePluginAutorest( # pylint: disable=abstract-method
104
123
  YamlUpdatePlugin, PluginAutorest
105
124
  ):
125
+ def get_yaml(self) -> Dict[str, Any]:
126
+ return yaml.safe_load(self.read_file("code-model-v4-no-tags.yaml"))
127
+
128
+ def write_yaml(self, yaml_string: str) -> None:
129
+ self.write_file("code-model-v4-no-tags.yaml", yaml_string)
130
+
106
131
  def get_options(self) -> Dict[str, Any]:
107
132
  inputs = self._autorestapi.list_inputs()
108
133
  _LOGGER.debug("Possible Inputs: %s", inputs)
@@ -0,0 +1,56 @@
1
+ # -------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See License.txt in the project root for
4
+ # license information.
5
+ # --------------------------------------------------------------------------
6
+ import re
7
+ import argparse
8
+
9
+
10
+ def to_snake_case(name: str) -> str:
11
+ def replace_upper_characters(m) -> str:
12
+ match_str = m.group().lower()
13
+ if m.start() > 0 and name[m.start() - 1] == "_":
14
+ # we are good if a '_' already exists
15
+ return match_str
16
+ # the first letter should not have _
17
+ prefix = "_" if m.start() > 0 else ""
18
+
19
+ # we will add an extra _ if there are multiple upper case chars together
20
+ next_non_upper_case_char_location = m.start() + len(match_str)
21
+ if (
22
+ len(match_str) > 2
23
+ and len(name) - next_non_upper_case_char_location > 1
24
+ and name[next_non_upper_case_char_location].isalpha()
25
+ ):
26
+
27
+ return (
28
+ prefix
29
+ + match_str[: len(match_str) - 1]
30
+ + "_"
31
+ + match_str[len(match_str) - 1]
32
+ )
33
+
34
+ return prefix + match_str
35
+
36
+ return re.sub("[A-Z]+", replace_upper_characters, name)
37
+
38
+
39
+ def parse_args(need_cadl_file: bool = True):
40
+ parser = argparse.ArgumentParser(
41
+ description="Run mypy against target folder. Add a local custom plugin to the path prior to execution. "
42
+ )
43
+ parser.add_argument(
44
+ "--output-folder",
45
+ dest="output_folder",
46
+ help="Output folder for generated SDK",
47
+ required=True,
48
+ )
49
+ parser.add_argument(
50
+ "--cadl-file",
51
+ dest="cadl_file",
52
+ help="Serialized cadl file",
53
+ required=need_cadl_file,
54
+ )
55
+
56
+ return parser.parse_args()
@@ -10,8 +10,9 @@ from typing import Any, Dict
10
10
  import black
11
11
 
12
12
  from .. import Plugin, PluginAutorest
13
+ from .._utils import parse_args
13
14
 
14
- _LOGGER = logging.getLogger(__name__)
15
+ logging.getLogger("blib2to3").setLevel(logging.ERROR)
15
16
 
16
17
  _BLACK_MODE = black.Mode()
17
18
  _BLACK_MODE.line_length = 120
@@ -20,12 +21,12 @@ _BLACK_MODE.line_length = 120
20
21
  class BlackScriptPlugin(Plugin): # pylint: disable=abstract-method
21
22
  def __init__(self, **kwargs):
22
23
  super().__init__(**kwargs)
23
- output_folder_uri = self.options["outputFolderUri"]
24
- if output_folder_uri.startswith("file:"):
25
- output_folder_uri = output_folder_uri[5:]
26
- if os.name == "nt" and output_folder_uri.startswith("///"):
27
- output_folder_uri = output_folder_uri[3:]
28
- self.output_folder = Path(output_folder_uri)
24
+ output_folder = self.options.get("output_folder", str(self.output_folder))
25
+ if output_folder.startswith("file:"):
26
+ output_folder = output_folder[5:]
27
+ if os.name == "nt" and output_folder.startswith("///"):
28
+ output_folder = output_folder[3:]
29
+ self.output_folder = Path(output_folder)
29
30
 
30
31
  def process(self) -> bool:
31
32
  # apply format_file on every file in the output folder
@@ -54,4 +55,10 @@ class BlackScriptPlugin(Plugin): # pylint: disable=abstract-method
54
55
 
55
56
  class BlackScriptPluginAutorest(BlackScriptPlugin, PluginAutorest):
56
57
  def get_options(self) -> Dict[str, Any]:
57
- return {"outputFolderUri": self._autorestapi.get_value("outputFolderUri")}
58
+ return {"output_folder": self._autorestapi.get_value("outputFolderUri")}
59
+
60
+
61
+ if __name__ == "__main__":
62
+ # CADL pipeline will call this
63
+ args = parse_args(need_cadl_file=False)
64
+ BlackScriptPlugin(output_folder=args.output_folder).process()
@@ -4,19 +4,20 @@
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
6
  import logging
7
- import sys
8
7
  from typing import Dict, Any, Union, cast
9
8
  from pathlib import Path
10
9
  import yaml
11
10
 
12
11
 
13
12
  from .. import Plugin, PluginAutorest
13
+ from .._utils import parse_args
14
14
  from .models.client import Client, Config
15
15
  from .models.code_model import CodeModel
16
16
  from .models import build_type
17
17
  from .models.request_builder import get_request_builder
18
18
  from .models.operation_group import OperationGroup
19
19
  from .serializers import JinjaSerializer, JinjaSerializerAutorest
20
+ from ._utils import DEFAULT_HEADER_TEXT
20
21
 
21
22
 
22
23
  def _build_convenience_layer(yaml_data: Dict[str, Any], code_model: CodeModel) -> None:
@@ -163,7 +164,7 @@ class CodeGenerator(Plugin):
163
164
  def _build_code_model_options(self) -> Dict[str, Any]:
164
165
  """Build en options dict from the user input while running autorest."""
165
166
  azure_arm = self.options.get("azure-arm", False)
166
- license_header = self.options["header-text"]
167
+ license_header = self.options.get("header-text", DEFAULT_HEADER_TEXT)
167
168
  if license_header:
168
169
  license_header = license_header.replace("\n", "\n# ")
169
170
  license_header = (
@@ -236,12 +237,12 @@ class CodeGenerator(Plugin):
236
237
  return options
237
238
 
238
239
  def get_yaml(self) -> Dict[str, Any]:
239
- # cadl should call this one
240
- raise NotImplementedError()
240
+ # cadl file doesn't have to be relative to output folder
241
+ with open(self.options["cadl_file"], "r") as fd:
242
+ return yaml.safe_load(fd.read())
241
243
 
242
- @staticmethod
243
- def get_serializer(code_model: CodeModel):
244
- return JinjaSerializer(code_model)
244
+ def get_serializer(self, code_model: CodeModel):
245
+ return JinjaSerializer(code_model, output_folder=self.output_folder)
245
246
 
246
247
  def process(self) -> bool:
247
248
  # List the input file, should be only one
@@ -351,20 +352,12 @@ class CodeGeneratorAutorest(CodeGenerator, PluginAutorest):
351
352
  return yaml.safe_load(file_content)
352
353
 
353
354
  def get_serializer(self, code_model: CodeModel): # type: ignore
354
- return JinjaSerializerAutorest(self._autorestapi, code_model)
355
-
356
-
357
- def main(yaml_model_file: str) -> None:
358
- from ..jsonrpc.localapi import ( # pylint: disable=import-outside-toplevel
359
- LocalAutorestAPI,
360
- )
361
-
362
- code_generator = CodeGeneratorAutorest(
363
- autorestapi=LocalAutorestAPI(reachable_files=[yaml_model_file])
364
- )
365
- if not code_generator.process():
366
- raise SystemExit("Process didn't finish gracefully")
355
+ return JinjaSerializerAutorest(
356
+ self._autorestapi, code_model, output_folder=self.output_folder
357
+ )
367
358
 
368
359
 
369
360
  if __name__ == "__main__":
370
- main(sys.argv[1])
361
+ # CADL pipeline will call this
362
+ args = parse_args()
363
+ CodeGenerator(output_folder=args.output_folder, cadl_file=args.cadl_file).process()
@@ -0,0 +1,14 @@
1
+ # -------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See License.txt in the project root for
4
+ # license information.
5
+ # --------------------------------------------------------------------------
6
+
7
+ DEFAULT_HEADER_TEXT = (
8
+ "# --------------------------------------------------------------------------\n"
9
+ "# Copyright (c) Microsoft Corporation. All rights reserved.\n"
10
+ "# Licensed under the MIT License. See License.txt in the project root for license information.\n"
11
+ "# Code generated by Microsoft (R) Python Code Generator.\n"
12
+ "# Changes may cause incorrect behavior and will be lost if the code is regenerated.\n"
13
+ "# --------------------------------------------------------------------------"
14
+ )
@@ -3,6 +3,7 @@
3
3
  # Licensed under the MIT License. See License.txt in the project root for
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
+ import logging
6
7
  from typing import Any, Dict, Union
7
8
  from .base_model import BaseModel
8
9
  from .code_model import CodeModel
@@ -140,6 +141,7 @@ TYPE_TO_OBJECT = {
140
141
  "any-object": AnyObjectType,
141
142
  "unixtime": UnixTimeType,
142
143
  }
144
+ _LOGGER = logging.getLogger(__name__)
143
145
 
144
146
 
145
147
  def build_type(yaml_data: Dict[str, Any], code_model: CodeModel) -> BaseType:
@@ -155,7 +157,14 @@ def build_type(yaml_data: Dict[str, Any], code_model: CodeModel) -> BaseType:
155
157
  code_model.types_map[yaml_id] = response
156
158
  response.fill_instance_from_yaml(yaml_data, code_model)
157
159
  else:
158
- response = TYPE_TO_OBJECT[yaml_data["type"]].from_yaml(yaml_data, code_model) # type: ignore
160
+ object_type = yaml_data.get("type", None)
161
+ if object_type is None:
162
+ _LOGGER.warning(
163
+ 'Unrecognized definition type "%s" is found, falling back it as "string"! ',
164
+ yaml_data["type"],
165
+ )
166
+ object_type = "string"
167
+ response = TYPE_TO_OBJECT[object_type].from_yaml(yaml_data, code_model) # type: ignore
159
168
  code_model.types_map[yaml_id] = response
160
169
  return response
161
170
 
@@ -3,6 +3,7 @@
3
3
  # Licensed under the MIT License. See License.txt in the project root for
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
+ from collections import OrderedDict
6
7
  from typing import Any, Dict, List, Optional, TYPE_CHECKING, cast
7
8
 
8
9
  from autorest.codegen.models.utils import add_to_pylint_disable
@@ -153,7 +154,17 @@ class ModelType(BaseType): # pylint: disable=too-many-instance-attributes
153
154
  # once we've finished, we want to reset created_json_template_representation to false
154
155
  # so we can call it again
155
156
  self._created_json_template_representation = False
156
- return representation
157
+ optional_keys = [
158
+ f'"{p.rest_api_name}"'
159
+ for p in self.properties
160
+ if getattr(p, "optional", False)
161
+ ]
162
+ return OrderedDict(
163
+ sorted(
164
+ representation.items(),
165
+ key=lambda item: f"{1 if item[0] in optional_keys else 0}{item[0]}",
166
+ )
167
+ )
157
168
 
158
169
  def get_polymorphic_subtypes(self, polymorphic_subtypes: List["ModelType"]) -> None:
159
170
  is_polymorphic_subtype = (
@@ -3,7 +3,7 @@
3
3
  # Licensed under the MIT License. See License.txt in the project root for
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
- from typing import Dict, List, Optional, Any, Union
6
+ from typing import Dict, List, Optional, Any, Union, cast
7
7
  from pathlib import Path
8
8
  from jinja2 import PackageLoader, Environment, FileSystemLoader, StrictUndefined
9
9
  from autorest.codegen.models.operation_group import OperationGroup
@@ -40,8 +40,10 @@ _REGENERATE_FILES = {"setup.py", "MANIFEST.in"}
40
40
 
41
41
 
42
42
  class JinjaSerializer(ReaderAndWriter): # pylint: disable=abstract-method
43
- def __init__(self, code_model: CodeModel, **kwargs: Any) -> None:
44
- super().__init__(**kwargs)
43
+ def __init__(
44
+ self, code_model: CodeModel, *, output_folder: Union[str, Path], **kwargs: Any
45
+ ) -> None:
46
+ super().__init__(output_folder=output_folder, **kwargs)
45
47
  self.code_model = code_model
46
48
 
47
49
  @property
@@ -133,7 +135,7 @@ class JinjaSerializer(ReaderAndWriter): # pylint: disable=abstract-method
133
135
  if self.read_file(namespace_path / Path("models.py")):
134
136
  self.write_file(
135
137
  namespace_path / Path("models.py"),
136
- self.read_file(namespace_path / Path("models.py")),
138
+ cast(str, self.read_file(namespace_path / Path("models.py"))),
137
139
  )
138
140
 
139
141
  if self.code_model.options["package_mode"]:
@@ -150,7 +152,9 @@ class JinjaSerializer(ReaderAndWriter): # pylint: disable=abstract-method
150
152
  self.write_file(output_name, render_result)
151
153
 
152
154
  def _prepare_params() -> Dict[Any, Any]:
153
- package_parts = package_name.split("-")[:-1]
155
+ package_parts = (self.code_model.options["package_name"] or "").split("-")[
156
+ :-1
157
+ ]
154
158
  token_cred = isinstance(
155
159
  getattr(self.code_model.credential, "type", None), TokenCredentialType
156
160
  )
@@ -177,14 +181,7 @@ class JinjaSerializer(ReaderAndWriter): # pylint: disable=abstract-method
177
181
  params.update(self.code_model.package_dependency)
178
182
  return params
179
183
 
180
- package_name = (
181
- self.code_model.options["package_name"]
182
- or self.code_model.client.name.lower()
183
- )
184
- count = package_name.count("-") + 1
185
- for _ in range(count):
186
- out_path = out_path / Path("..")
187
-
184
+ out_path = out_path / Path("../" * (self.code_model.namespace.count(".") + 1))
188
185
  if self.code_model.options["package_mode"] in ("dataplane", "mgmtplane"):
189
186
  env = Environment(
190
187
  loader=PackageLoader("autorest.codegen", "templates"),
@@ -484,5 +481,13 @@ class JinjaSerializer(ReaderAndWriter): # pylint: disable=abstract-method
484
481
 
485
482
 
486
483
  class JinjaSerializerAutorest(JinjaSerializer, ReaderAndWriterAutorest):
487
- def __init__(self, autorestapi: AutorestAPI, code_model: CodeModel) -> None:
488
- super().__init__(autorestapi=autorestapi, code_model=code_model)
484
+ def __init__(
485
+ self,
486
+ autorestapi: AutorestAPI,
487
+ code_model: CodeModel,
488
+ *,
489
+ output_folder: Union[str, Path],
490
+ ) -> None:
491
+ super().__init__(
492
+ autorestapi=autorestapi, code_model=code_model, output_folder=output_folder
493
+ )
@@ -79,9 +79,7 @@ def _improve_json_string(template_representation: str) -> Any:
79
79
 
80
80
  def _json_dumps_template(template_representation: Any) -> Any:
81
81
  # only for template use, since it wraps everything in strings
82
- return _improve_json_string(
83
- json.dumps(template_representation, sort_keys=True, indent=4)
84
- )
82
+ return _improve_json_string(json.dumps(template_representation, indent=4))
85
83
 
86
84
 
87
85
  def _get_polymorphic_subtype_template(polymorphic_subtype: ModelType) -> List[str]:
@@ -2,7 +2,7 @@
2
2
  # Microsoft Azure SDK for Python
3
3
 
4
4
  This is the Microsoft {{package_pprint_name}} Client Library.
5
- This package has been tested with Python 3.6+.
5
+ This package has been tested with Python 3.7+.
6
6
  For a more complete view of Azure libraries, see the [azure sdk python release](https://aka.ms/azsdk/python/all).
7
7
 
8
8
  # Usage
@@ -17,7 +17,7 @@ Additional code samples for different Azure services are available at [Samples R
17
17
 
18
18
  If you encounter any bugs or have suggestions, please file an issue in the
19
19
  [Issues](https://github.com/Azure/azure-sdk-for-python/issues)
20
- section of the project.
20
+ section of the project.
21
21
 
22
22
 
23
23
  ![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-python%2F{{package_name}}%2FREADME.png)
@@ -35,7 +35,7 @@ python -m pip install {{ package_name }}
35
35
 
36
36
  #### Prequisites
37
37
 
38
- - Python 3.6 or later is required to use this package.
38
+ - Python 3.7 or later is required to use this package.
39
39
  - You need an [Azure subscription][azure_sub] to use this package.
40
40
  - An existing {{ package_pprint_name }} instance.
41
41
 
@@ -54,7 +54,6 @@ setup(
54
54
  "Programming Language :: Python",
55
55
  "Programming Language :: Python :: 3 :: Only",
56
56
  "Programming Language :: Python :: 3",
57
- "Programming Language :: Python :: 3.6",
58
57
  "Programming Language :: Python :: 3.7",
59
58
  "Programming Language :: Python :: 3.8",
60
59
  "Programming Language :: Python :: 3.9",
@@ -94,7 +93,7 @@ setup(
94
93
  {% endif %}
95
94
  ],
96
95
  {% if package_mode %}
97
- python_requires=">=3.6",
96
+ python_requires=">=3.7",
98
97
  {% else %}
99
98
  long_description="""\
100
99
  {{ code_model.client.description }}
@@ -26,6 +26,7 @@ def GetPluginNames():
26
26
  "black",
27
27
  "multiapiscript",
28
28
  "postprocess",
29
+ "multiclientscript",
29
30
  ]
30
31
 
31
32
 
@@ -56,11 +57,16 @@ def Process(plugin_name: str, session_id: str) -> bool:
56
57
  from ..black import BlackScriptPluginAutorest as PluginToLoad # type: ignore
57
58
  elif plugin_name == "multiapiscript":
58
59
  from ..multiapi import MultiApiScriptPluginAutorest as PluginToLoad # type: ignore
60
+ elif plugin_name == "multiclientscript":
61
+ from ..multiclient import MultiClientPluginAutorest as PluginToLoad # type: ignore
59
62
  else:
60
63
  _LOGGER.fatal("Unknown plugin name %s", plugin_name)
61
64
  raise RuntimeError(f"Unknown plugin name {plugin_name}")
62
65
 
63
- plugin = PluginToLoad(autorestapi=stdstream_connection)
66
+ plugin = PluginToLoad(
67
+ autorestapi=stdstream_connection,
68
+ output_folder=stdstream_connection.get_value("output-folder"),
69
+ )
64
70
 
65
71
  try:
66
72
  _LOGGER.debug("Starting plugin %s", PluginToLoad.__name__)
@@ -8,20 +8,21 @@
8
8
  import logging
9
9
  from typing import Any, Dict, Set
10
10
 
11
- import m2r
11
+ import m2r2
12
12
 
13
13
  from .. import YamlUpdatePluginAutorest, YamlUpdatePlugin
14
+ from .._utils import parse_args
14
15
 
15
16
 
16
17
  _LOGGER = logging.getLogger(__name__)
17
18
 
18
19
 
19
- class AutorestRender(m2r.RestRenderer):
20
+ class AutorestRender(m2r2.RestRenderer):
20
21
  """Redefine the concept of inline HTML in the renderer, we don't want to define a new format
21
22
  in the description/summary.
22
23
  """
23
24
 
24
- def inline_html(self, html: str) -> str:
25
+ def inline_html(self, html: str) -> str: # pylint: disable=no-self-use
25
26
  """Do not render inline HTML with a role definition."""
26
27
  return f":code:`{html}`"
27
28
 
@@ -55,7 +56,7 @@ class M2R(YamlUpdatePlugin): # pylint: disable=abstract-method
55
56
  def convert_to_rst(string_to_convert: str) -> str:
56
57
  """Convert that string from MD to RST."""
57
58
  try:
58
- return m2r.convert(string_to_convert, renderer=AutorestRender()).strip()
59
+ return m2r2.convert(string_to_convert, renderer=AutorestRender()).strip()
59
60
  except Exception: # pylint: disable=broad-except
60
61
  return string_to_convert
61
62
 
@@ -63,3 +64,9 @@ class M2R(YamlUpdatePlugin): # pylint: disable=abstract-method
63
64
  class M2RAutorest(YamlUpdatePluginAutorest, M2R):
64
65
  def get_options(self) -> Dict[str, Any]:
65
66
  return {}
67
+
68
+
69
+ if __name__ == "__main__":
70
+ # CADL pipeline will call this
71
+ args = parse_args()
72
+ M2R(output_folder=args.output_folder, cadl_file=args.cadl_file).process()
@@ -11,6 +11,7 @@ import copy
11
11
  import logging
12
12
  from typing import Callable, Dict, Any, Iterable, List, Optional, Set
13
13
 
14
+ from .._utils import to_snake_case
14
15
  from .. import YamlUpdatePluginAutorest
15
16
 
16
17
  JSON_REGEXP = re.compile(r"^(application|text)/(.+\+)?json$")
@@ -1100,7 +1101,7 @@ class M4Reformatter(
1100
1101
  if yaml_data.get("globalParameters")
1101
1102
  else "",
1102
1103
  "namespace": self._autorestapi.get_value("namespace")
1103
- or yaml_data["language"]["default"]["name"],
1104
+ or to_snake_case(yaml_data["info"]["title"].replace(" ", "")),
1104
1105
  }
1105
1106
 
1106
1107
  def update_yaml(self, yaml_data: Dict[str, Any]) -> None:
@@ -41,7 +41,7 @@ class MultiApiScriptPluginAutorest(MultiApiScriptPlugin, PluginAutorest):
41
41
  return MultiAPIAutorest(
42
42
  autorestapi=self._autorestapi,
43
43
  input_package_name=self.options.get("package-name"),
44
- output_folder=self.options["output-folder"],
44
+ output_folder=self.output_folder,
45
45
  user_specified_default_api=self.options.get("default-api"),
46
46
  no_async=self.options.get("no-async", False),
47
47
  )
@@ -49,7 +49,6 @@ class MultiApiScriptPluginAutorest(MultiApiScriptPlugin, PluginAutorest):
49
49
  def get_options(self) -> Dict[str, Any]:
50
50
  options = {
51
51
  "package-name": self._autorestapi.get_value("package-name"),
52
- "output-folder": self._autorestapi.get_value("output-folder"),
53
52
  "default-api": self._autorestapi.get_value("default-api"),
54
53
  "no-async": self._autorestapi.get_value("no-async"),
55
54
  }
@@ -66,15 +65,13 @@ class MultiAPI(ReaderAndWriter): # pylint: disable=abstract-method
66
65
  user_specified_default_api: Optional[str] = None,
67
66
  **kwargs: Any,
68
67
  ) -> None:
69
- super().__init__(**kwargs)
68
+ super().__init__(output_folder=Path(output_folder).resolve(), **kwargs)
70
69
  if input_package_name is None:
71
70
  raise ValueError(
72
71
  "package-name is required, either provide it as args or check your readme configuration"
73
72
  )
74
73
  self.input_package_name = input_package_name
75
74
  _LOGGER.debug("Received package name %s", input_package_name)
76
-
77
- self.output_folder = Path(output_folder).resolve()
78
75
  _LOGGER.debug("Received output-folder %s", output_folder)
79
76
  self.output_package_name: str = ""
80
77
  self.no_async = no_async
@@ -194,4 +191,6 @@ class MultiAPIAutorest(MultiAPI, ReaderAndWriterAutorest):
194
191
 
195
192
  @property
196
193
  def serializer(self) -> MultiAPISerializer:
197
- return MultiAPISerializerAutorest(self._autorestapi)
194
+ return MultiAPISerializerAutorest(
195
+ self._autorestapi, output_folder=self.output_folder
196
+ )
@@ -4,7 +4,7 @@
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
6
  from pathlib import Path
7
- from typing import Any, Optional
7
+ from typing import Any, Optional, Union
8
8
  from jinja2 import PackageLoader, Environment
9
9
 
10
10
  from .import_serializer import FileImportSerializer
@@ -122,5 +122,7 @@ class MultiAPISerializer(ReaderAndWriter): # pylint: disable=abstract-method
122
122
 
123
123
 
124
124
  class MultiAPISerializerAutorest(MultiAPISerializer, ReaderAndWriterAutorest):
125
- def __init__(self, autorestapi: AutorestAPI) -> None:
126
- super().__init__(autorestapi=autorestapi)
125
+ def __init__(
126
+ self, autorestapi: AutorestAPI, *, output_folder: Union[str, Path]
127
+ ) -> None:
128
+ super().__init__(autorestapi=autorestapi, output_folder=output_folder)
@@ -0,0 +1,50 @@
1
+ # -------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See License.txt in the project root for
4
+ # license information.
5
+ # --------------------------------------------------------------------------
6
+ import logging
7
+ from typing import Any, Dict
8
+ from pathlib import Path
9
+ from jinja2 import Environment, PackageLoader
10
+ from .. import Plugin, PluginAutorest
11
+
12
+ _LOGGER = logging.getLogger(__name__)
13
+
14
+
15
+ class MultiClientPlugin(Plugin): # pylint: disable=abstract-method
16
+ def process(self) -> bool:
17
+ _LOGGER.info("Generating files for multi client")
18
+
19
+ env = Environment(
20
+ loader=PackageLoader("autorest.multiclient", "templates"),
21
+ keep_trailing_newline=True,
22
+ line_statement_prefix="##",
23
+ line_comment_prefix="###",
24
+ trim_blocks=True,
25
+ lstrip_blocks=True,
26
+ )
27
+
28
+ # __init__.py
29
+ template = env.get_template("init.py.jinja2")
30
+ self.write_file(Path("__init__.py"), template.render())
31
+
32
+ # _version.py
33
+ template = env.get_template("version.py.jinja2")
34
+ self.write_file(
35
+ Path("_version.py"),
36
+ template.render(
37
+ package_version=self.options.get("package-version") or "1.0.0b1"
38
+ ),
39
+ )
40
+
41
+ # py.typed
42
+ self.write_file(Path("py.typed"), "# Marker file for PEP 561.")
43
+
44
+ _LOGGER.info("Generating Done for multi client!")
45
+ return True
46
+
47
+
48
+ class MultiClientPluginAutorest(MultiClientPlugin, PluginAutorest):
49
+ def get_options(self) -> Dict[str, Any]:
50
+ return {"package-version": self._autorestapi.get_value("package-version")}
@@ -0,0 +1,8 @@
1
+ # --------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See License.txt in the project root for license information.
4
+ # Code generated by Microsoft (R) AutoRest Code Generator.
5
+ # Changes may cause incorrect behavior and will be lost if the code is regenerated.
6
+ # --------------------------------------------------------------------------
7
+ from ._version import VERSION
8
+ __version__ = VERSION
@@ -0,0 +1,8 @@
1
+ # coding=utf-8
2
+ # --------------------------------------------------------------------------
3
+ # Copyright (c) Microsoft Corporation. All rights reserved.
4
+ # Licensed under the MIT License. See License.txt in the project root for
5
+ # license information.
6
+ # --------------------------------------------------------------------------
7
+
8
+ VERSION = "{{ package_version }}"
@@ -66,7 +66,7 @@ class PostProcessPlugin(Plugin): # pylint: disable=abstract-method
66
66
  init_file = next(d for d in dir.iterdir() if d.name == "__init__.py")
67
67
  # we don't care about pkgutil inits, we skip over them
68
68
  file_content = self.read_file(init_file.relative_to(self.output_folder))
69
- if not "pkgutil" in file_content:
69
+ if "pkgutil" not in file_content:
70
70
  return dir, namespace
71
71
  except StopIteration:
72
72
  pass
@@ -6,10 +6,12 @@
6
6
  """The preprocessing autorest plugin.
7
7
  """
8
8
  from typing import Callable, Dict, Any, List, Optional
9
- from .helpers import to_snake_case, pad_reserved_words, add_redefined_builtin_info
9
+ from .._utils import to_snake_case
10
+ from .helpers import pad_reserved_words, add_redefined_builtin_info
10
11
  from .python_mappings import PadType
11
12
 
12
- from .. import YamlUpdatePlugin, PluginAutorest
13
+ from .. import YamlUpdatePlugin, YamlUpdatePluginAutorest
14
+ from .._utils import parse_args
13
15
 
14
16
 
15
17
  def _remove_paging_maxpagesize(yaml_data: Dict[str, Any]) -> None:
@@ -230,10 +232,18 @@ class PreProcessPlugin(YamlUpdatePlugin): # pylint: disable=abstract-method
230
232
  self.update_operation_groups(yaml_data)
231
233
 
232
234
 
233
- class PreProcessPluginAutorest(PluginAutorest, PreProcessPlugin):
235
+ class PreProcessPluginAutorest(YamlUpdatePluginAutorest, PreProcessPlugin):
234
236
  def get_options(self) -> Dict[str, Any]:
235
237
  options = {
236
238
  "version-tolerant": self._autorestapi.get_boolean_value("version-tolerant"),
237
239
  "azure-arm": self._autorestapi.get_boolean_value("azure-arm"),
238
240
  }
239
241
  return {k: v for k, v in options.items() if v is not None}
242
+
243
+
244
+ if __name__ == "__main__":
245
+ # CADL pipeline will call this
246
+ args = parse_args()
247
+ PreProcessPlugin(
248
+ output_folder=args.output_folder, cadl_file=args.cadl_file
249
+ ).process()
@@ -4,39 +4,9 @@
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
6
  from typing import Any, Dict
7
- import re
8
7
  from .python_mappings import PadType, RESERVED_WORDS, REDEFINED_BUILTINS
9
8
 
10
9
 
11
- def to_snake_case(name: str) -> str:
12
- def replace_upper_characters(m) -> str:
13
- match_str = m.group().lower()
14
- if m.start() > 0 and name[m.start() - 1] == "_":
15
- # we are good if a '_' already exists
16
- return match_str
17
- # the first letter should not have _
18
- prefix = "_" if m.start() > 0 else ""
19
-
20
- # we will add an extra _ if there are multiple upper case chars together
21
- next_non_upper_case_char_location = m.start() + len(match_str)
22
- if (
23
- len(match_str) > 2
24
- and len(name) - next_non_upper_case_char_location > 1
25
- and name[next_non_upper_case_char_location].isalpha()
26
- ):
27
-
28
- return (
29
- prefix
30
- + match_str[: len(match_str) - 1]
31
- + "_"
32
- + match_str[len(match_str) - 1]
33
- )
34
-
35
- return prefix + match_str
36
-
37
- return re.sub("[A-Z]+", replace_upper_characters, name)
38
-
39
-
40
10
  def pad_reserved_words(name: str, pad_type: PadType):
41
11
  # we want to pad hidden variables as well
42
12
  if not name:
package/install.py CHANGED
@@ -6,8 +6,8 @@
6
6
  # license information.
7
7
  # --------------------------------------------------------------------------
8
8
  import sys
9
- if not sys.version_info >= (3, 6, 0):
10
- raise Exception("Autorest for Python extension requires Python 3.6 at least")
9
+ if not sys.version_info >= (3, 7, 0):
10
+ raise Exception("Autorest for Python extension requires Python 3.7 at least")
11
11
 
12
12
  try:
13
13
  import pip
@@ -20,7 +20,7 @@ except ImportError:
20
20
  raise Exception("Your Python installation doesn't have venv available")
21
21
 
22
22
 
23
- # Now we have pip and Py >= 3.6, go to work
23
+ # Now we have pip and Py >= 3.7, go to work
24
24
 
25
25
  import subprocess
26
26
  from pathlib import Path
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autorest/python",
3
- "version": "6.0.1",
3
+ "version": "6.1.0",
4
4
  "description": "The Python extension for generators in AutoRest.",
5
5
  "scripts": {
6
6
  "prepare": "node run-python3.js prepare.py",
package/prepare.py CHANGED
@@ -6,8 +6,8 @@
6
6
  # license information.
7
7
  # --------------------------------------------------------------------------
8
8
  import sys
9
- if not sys.version_info >= (3, 6, 0):
10
- raise Exception("Autorest for Python extension requires Python 3.6 at least")
9
+ if not sys.version_info >= (3, 7, 0):
10
+ raise Exception("Autorest for Python extension requires Python 3.7 at least")
11
11
 
12
12
  from pathlib import Path
13
13
  import venv
package/requirements.txt CHANGED
@@ -1,14 +1,13 @@
1
- black==21.12b0
2
- click==8.0.3
3
- docutils==0.18.1
4
- Jinja2==3.0.3
1
+ black==22.6.0
2
+ click==8.1.3
3
+ docutils==0.18
4
+ Jinja2==3.1.2
5
5
  json-rpc==1.13.0
6
- m2r==0.2.1
7
- MarkupSafe==2.0.1
6
+ m2r2==0.3.2
7
+ MarkupSafe==2.1.1
8
8
  mistune==0.8.4
9
9
  mypy-extensions==0.4.3
10
10
  pathspec==0.9.0
11
- platformdirs==2.4.0
11
+ platformdirs==2.5.2
12
12
  PyYAML==6.0
13
- tomli==1.2.2
14
- typing-extensions==4.0.1
13
+ tomli==2.0.1
package/run-python3.js CHANGED
@@ -9,8 +9,8 @@
9
9
  const cp = require("child_process");
10
10
  const extension = require("@autorest/system-requirements");
11
11
 
12
- async function runPython3(scriptName, debug = "") {
13
- const command = await extension.patchPythonPath(["python", scriptName, debug], { version: ">=3.6", environmentVariable: "AUTOREST_PYTHON_EXE" });
12
+ async function runPython3(scriptName, ...args) {
13
+ const command = await extension.patchPythonPath(["python", scriptName, ...args], { version: ">=3.7", environmentVariable: "AUTOREST_PYTHON_EXE" });
14
14
  cp.execSync(command.join(" "), {
15
15
  stdio: [0, 1, 2]
16
16
  });
package/setup.py CHANGED
@@ -36,7 +36,6 @@ setup(
36
36
  'Development Status :: 4 - Beta',
37
37
  'Programming Language :: Python',
38
38
  'Programming Language :: Python :: 3',
39
- 'Programming Language :: Python :: 3.6',
40
39
  'Programming Language :: Python :: 3.7',
41
40
  'Programming Language :: Python :: 3.8',
42
41
  'License :: OSI Approved :: MIT License',
@@ -48,8 +47,8 @@ setup(
48
47
  "json-rpc",
49
48
  "Jinja2 >= 2.11", # I need "include" and auto-context + blank line are not indented by default
50
49
  "pyyaml",
51
- "mistune < 2.0.0", # Need to pin mistune's max version so m2r doesn't break
52
- "m2r",
50
+ "m2r2",
53
51
  "black",
52
+ "docutils<0.19", # m2r2 fails with docutils 0.19
54
53
  ],
55
54
  )
package/start.py CHANGED
@@ -6,8 +6,8 @@
6
6
  # license information.
7
7
  # --------------------------------------------------------------------------
8
8
  import sys
9
- if not sys.version_info >= (3, 6, 0):
10
- raise Exception("Autorest for Python extension requires Python 3.6 at least")
9
+ if not sys.version_info >= (3, 7, 0):
10
+ raise Exception("Autorest for Python extension requires Python 3.7 at least")
11
11
 
12
12
  from pathlib import Path
13
13
  import venv
@@ -1,121 +0,0 @@
1
- # -------------------------------------------------------------------------
2
- # Copyright (c) Microsoft Corporation. All rights reserved.
3
- # Licensed under the MIT License. See License.txt in the project root for
4
- # license information.
5
- # --------------------------------------------------------------------------
6
- from typing import Any, Dict
7
- from pathlib import Path
8
- from jinja2 import Environment, PackageLoader
9
-
10
- from ...jsonrpc import AutorestAPI
11
- from ... import ReaderAndWriter, ReaderAndWriterAutorest
12
-
13
-
14
- class MultiAPISerializer(ReaderAndWriter): # pylint: disable=abstract-method
15
- def __init__(
16
- self,
17
- conf: Dict[str, Any],
18
- async_mode: bool,
19
- service_client_filename: str,
20
- **kwargs: Any
21
- ):
22
- super().__init__(**kwargs)
23
- self.conf = conf
24
- self.async_mode = async_mode
25
- self.service_client_filename = service_client_filename
26
- self.env = Environment(
27
- loader=PackageLoader("autorest.multiapi", "templates"),
28
- keep_trailing_newline=True,
29
- line_statement_prefix="##",
30
- line_comment_prefix="###",
31
- trim_blocks=True,
32
- lstrip_blocks=True,
33
- )
34
-
35
- def _get_file_path(self, filename: str) -> Path:
36
- if self.async_mode:
37
- return Path("aio") / filename
38
- return Path(filename)
39
-
40
- def serialize(self):
41
- self.write_file(
42
- self._get_file_path("__init__.py"), self.serialize_multiapi_init()
43
- )
44
-
45
- service_client_filename_with_py_extension = self.service_client_filename + ".py"
46
- self.write_file(
47
- self._get_file_path(service_client_filename_with_py_extension),
48
- self.serialize_multiapi_client(),
49
- )
50
-
51
- configuration_filename = "_configuration.py"
52
- self.write_file(
53
- self._get_file_path(configuration_filename),
54
- self.serialize_multiapi_config(),
55
- )
56
-
57
- operation_mixins_filename = "_operations_mixin.py"
58
- if self.conf["mixin_operations"]:
59
- self.write_file(
60
- self._get_file_path(operation_mixins_filename),
61
- self.serialize_multiapi_operation_mixins(),
62
- )
63
-
64
- if self.read_file("_version.py"):
65
- self.write_file("_version.py", self.read_file("_version.py"))
66
- elif self.read_file("version.py"):
67
- self.write_file("_version.py", self.read_file("version.py"))
68
- else:
69
- self.write_file(Path("_version.py"), self.serialize_multiapi_version())
70
-
71
- # don't erase patch file
72
- if self.read_file("_patch.py"):
73
- self.write_file("_patch.py", self.read_file("_patch.py"))
74
-
75
- self.write_file(Path("models.py"), self.serialize_multiapi_models())
76
-
77
- self.write_file(Path("py.typed"), "# Marker file for PEP 561.")
78
-
79
- def serialize_multiapi_init(self) -> str:
80
- template = self.env.get_template("multiapi_init.py.jinja2")
81
- return template.render(
82
- service_client_filename=self.service_client_filename,
83
- client_name=self.conf["client_name"],
84
- async_mode=self.async_mode,
85
- )
86
-
87
- def serialize_multiapi_client(self) -> str:
88
- template = self.env.get_template("multiapi_service_client.py.jinja2")
89
- return template.render(**self.conf, async_mode=self.async_mode)
90
-
91
- def serialize_multiapi_config(self) -> str:
92
- template = self.env.get_template("multiapi_config.py.jinja2")
93
- return template.render(**self.conf, async_mode=self.async_mode)
94
-
95
- def serialize_multiapi_models(self) -> str:
96
- template = self.env.get_template("multiapi_models.py.jinja2")
97
- return template.render(**self.conf)
98
-
99
- def serialize_multiapi_version(self) -> str:
100
- template = self.env.get_template("multiapi_version.py.jinja2")
101
- return template.render()
102
-
103
- def serialize_multiapi_operation_mixins(self) -> str:
104
- template = self.env.get_template("multiapi_operations_mixin.py.jinja2")
105
- return template.render(**self.conf, async_mode=self.async_mode)
106
-
107
-
108
- class MultiAPISerializerAutorest(MultiAPISerializer, ReaderAndWriterAutorest):
109
- def __init__(
110
- self,
111
- autorestapi: AutorestAPI,
112
- conf: Dict[str, Any],
113
- async_mode: bool,
114
- service_client_filename: str,
115
- ):
116
- super().__init__(
117
- autorestapi=autorestapi,
118
- conf=conf,
119
- async_mode=async_mode,
120
- service_client_filename=service_client_filename,
121
- )