@autorest/python 6.0.1 → 6.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ChangeLog.md +57 -0
- package/README.md +22 -3
- package/autorest/__init__.py +52 -14
- package/autorest/_utils.py +75 -0
- package/autorest/black/__init__.py +15 -8
- package/autorest/cadlflags/__init__.py +128 -0
- package/autorest/codegen/__init__.py +14 -21
- package/autorest/codegen/_utils.py +12 -0
- package/autorest/codegen/models/__init__.py +10 -1
- package/autorest/codegen/models/combined_type.py +4 -0
- package/autorest/codegen/models/model_type.py +18 -1
- package/autorest/codegen/models/parameter.py +5 -1
- package/autorest/codegen/models/property.py +1 -8
- package/autorest/codegen/models/request_builder_parameter.py +1 -1
- package/autorest/codegen/models/response.py +7 -0
- package/autorest/codegen/models/utils.py +0 -3
- package/autorest/codegen/serializers/__init__.py +20 -15
- package/autorest/codegen/serializers/builder_serializer.py +5 -5
- package/autorest/codegen/templates/README.md.jinja2 +3 -3
- package/autorest/codegen/templates/setup.py.jinja2 +1 -2
- package/autorest/jsonrpc/server.py +7 -1
- package/autorest/m2r/__init__.py +11 -4
- package/autorest/m4reformatter/__init__.py +13 -37
- package/autorest/multiapi/__init__.py +5 -6
- package/autorest/multiapi/serializers/__init__.py +5 -3
- package/autorest/multiapi/templates/multiapi_init.py.jinja2 +4 -0
- package/autorest/multiclient/__init__.py +50 -0
- package/autorest/multiclient/templates/init.py.jinja2 +8 -0
- package/autorest/multiclient/templates/version.py.jinja2 +8 -0
- package/autorest/postprocess/__init__.py +1 -1
- package/autorest/preprocess/__init__.py +138 -17
- package/autorest/preprocess/helpers.py +0 -30
- package/install.py +3 -3
- package/package.json +3 -2
- package/prepare.py +2 -2
- package/requirements.txt +8 -9
- package/run-python3.js +2 -2
- package/run_cadl.py +27 -0
- package/setup.py +2 -3
- package/start.py +2 -2
- package/autorest/multiapi/serializers/multiapi_serializer.py +0 -121
package/ChangeLog.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
# Release History
|
|
2
2
|
|
|
3
|
+
### 2022-08-16 - 6.1.2
|
|
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
|
+
**Bug Fixes**
|
|
15
|
+
|
|
16
|
+
- Correctly document polymorphic page responses #1389
|
|
17
|
+
- Add `__version__` to `__init__.py` for multiapi #1393
|
|
18
|
+
|
|
19
|
+
### 2022-07-20 - 6.1.1
|
|
20
|
+
|
|
21
|
+
| Library | Min Version |
|
|
22
|
+
| ----------------------------------------------------------------------- | ----------- |
|
|
23
|
+
| `@autorest/core` | `3.8.1` |
|
|
24
|
+
| `@autorest/modelerfour` | `4.23.5` |
|
|
25
|
+
| `azure-core` dep of generated code | `1.24.0` |
|
|
26
|
+
| `isodate` dep of generated code | `0.6.1` |
|
|
27
|
+
| `msrest` dep of generated code (If generating legacy code) | `0.7.1` |
|
|
28
|
+
| `azure-mgmt-core` dep of generated code (If generating mgmt plane code) | `1.3.0` |
|
|
29
|
+
|
|
30
|
+
**Other Changes**
|
|
31
|
+
|
|
32
|
+
- Get skeleton for cadl generation released #1358
|
|
33
|
+
|
|
34
|
+
### 2022-07-20 - 6.1.0
|
|
35
|
+
|
|
36
|
+
| Library | Min Version |
|
|
37
|
+
| ----------------------------------------------------------------------- | ----------- |
|
|
38
|
+
| `@autorest/core` | `3.8.1` |
|
|
39
|
+
| `@autorest/modelerfour` | `4.23.5` |
|
|
40
|
+
| `azure-core` dep of generated code | `1.24.0` |
|
|
41
|
+
| `isodate` dep of generated code | `0.6.1` |
|
|
42
|
+
| `msrest` dep of generated code (If generating legacy code) | `0.7.1` |
|
|
43
|
+
| `azure-mgmt-core` dep of generated code (If generating mgmt plane code) | `1.3.0` |
|
|
44
|
+
|
|
45
|
+
**New Features**
|
|
46
|
+
|
|
47
|
+
- Add new plugin `MultiClient` and new flag `--multiclientscript` to handle package structure of multi client #1328
|
|
48
|
+
|
|
49
|
+
**Bug Fixes**
|
|
50
|
+
|
|
51
|
+
- Fallback unrecognized type as string to avoid a fatal error. #1341
|
|
52
|
+
- Fix regression in default namespace for SDKs generated without `--namespace` flag #1354
|
|
53
|
+
|
|
54
|
+
**Other Changes**
|
|
55
|
+
|
|
56
|
+
- Generated code no longer supports Python 3.6 #1353
|
|
57
|
+
- Order json input and response template entries by whether they are required or not #1335
|
|
58
|
+
- Reduce extreme amount of `black` logs when running in `--debug` mode to just log errors
|
|
59
|
+
|
|
3
60
|
### 2022-06-29 - 6.0.1
|
|
4
61
|
|
|
5
62
|
| Library | Min Version |
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Generating with Autorest for Python
|
|
1
|
+
# Generating with Autorest for Python
|
|
2
2
|
|
|
3
3
|
See [here](https://github.com/Azure/autorest.python/wiki/Generating-with-autorest-for-python-v5.0.0) for Python-specific docs, and [here] for general docs
|
|
4
4
|
|
|
@@ -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
|
package/autorest/__init__.py
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
# --------------------------------------------------------------------------
|
|
6
6
|
import logging
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
import json
|
|
8
9
|
from abc import ABC, abstractmethod
|
|
9
10
|
from typing import Any, Dict, Union
|
|
10
11
|
|
|
@@ -19,21 +20,45 @@ _LOGGER = logging.getLogger(__name__)
|
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class ReaderAndWriter:
|
|
22
|
-
def __init__(self, **kwargs: Any) -> None:
|
|
23
|
-
|
|
23
|
+
def __init__(self, *, output_folder: Union[str, Path], **kwargs: Any) -> None:
|
|
24
|
+
self.output_folder = Path(output_folder)
|
|
25
|
+
try:
|
|
26
|
+
with open(
|
|
27
|
+
Path(self.output_folder) / Path("..") / Path("python.json"), "r"
|
|
28
|
+
) as fd:
|
|
29
|
+
python_json = json.load(fd)
|
|
30
|
+
except Exception: # pylint: disable=broad-except
|
|
31
|
+
python_json = {}
|
|
32
|
+
self.options = kwargs
|
|
33
|
+
if python_json:
|
|
34
|
+
_LOGGER.warning(
|
|
35
|
+
"Loading python.json file. This behavior will be depreacted"
|
|
36
|
+
)
|
|
37
|
+
self.options.update(python_json)
|
|
24
38
|
|
|
25
39
|
def read_file(self, path: Union[str, Path]) -> str:
|
|
26
40
|
"""How does one read a file in cadl?"""
|
|
27
|
-
|
|
41
|
+
# make path relative to output folder
|
|
42
|
+
try:
|
|
43
|
+
with open(self.output_folder / Path(path), "r") as fd:
|
|
44
|
+
return fd.read()
|
|
45
|
+
except FileNotFoundError:
|
|
46
|
+
return ""
|
|
28
47
|
|
|
29
48
|
def write_file(self, filename: Union[str, Path], file_content: str) -> None:
|
|
30
49
|
"""How does writing work in cadl?"""
|
|
31
|
-
|
|
50
|
+
file_folder = Path(filename).parent
|
|
51
|
+
if not Path.is_dir(self.output_folder / file_folder):
|
|
52
|
+
Path.mkdir(self.output_folder / file_folder, parents=True)
|
|
53
|
+
with open(self.output_folder / Path(filename), "w") as fd:
|
|
54
|
+
fd.write(file_content)
|
|
32
55
|
|
|
33
56
|
|
|
34
57
|
class ReaderAndWriterAutorest(ReaderAndWriter):
|
|
35
|
-
def __init__(
|
|
36
|
-
|
|
58
|
+
def __init__(
|
|
59
|
+
self, *, output_folder: Union[str, Path], autorestapi: AutorestAPI
|
|
60
|
+
) -> None:
|
|
61
|
+
super().__init__(output_folder=output_folder)
|
|
37
62
|
self._autorestapi = autorestapi
|
|
38
63
|
|
|
39
64
|
def read_file(self, path: Union[str, Path]) -> str:
|
|
@@ -49,10 +74,6 @@ class Plugin(ReaderAndWriter, ABC):
|
|
|
49
74
|
:param autorestapi: An autorest API instance
|
|
50
75
|
"""
|
|
51
76
|
|
|
52
|
-
def __init__(self, **kwargs: Any) -> None:
|
|
53
|
-
super().__init__(**kwargs)
|
|
54
|
-
self.options: Dict[str, Any] = {}
|
|
55
|
-
|
|
56
77
|
@abstractmethod
|
|
57
78
|
def process(self) -> bool:
|
|
58
79
|
"""The plugin process.
|
|
@@ -67,8 +88,10 @@ class Plugin(ReaderAndWriter, ABC):
|
|
|
67
88
|
class PluginAutorest(Plugin, ReaderAndWriterAutorest):
|
|
68
89
|
"""For our Autorest plugins, we want to take autorest api as input as options, then pass it to the Plugin"""
|
|
69
90
|
|
|
70
|
-
def __init__(
|
|
71
|
-
|
|
91
|
+
def __init__(
|
|
92
|
+
self, autorestapi: AutorestAPI, *, output_folder: Union[str, Path]
|
|
93
|
+
) -> None:
|
|
94
|
+
super().__init__(autorestapi=autorestapi, output_folder=output_folder)
|
|
72
95
|
self.options = self.get_options()
|
|
73
96
|
|
|
74
97
|
@abstractmethod
|
|
@@ -79,15 +102,24 @@ class PluginAutorest(Plugin, ReaderAndWriterAutorest):
|
|
|
79
102
|
class YamlUpdatePlugin(Plugin):
|
|
80
103
|
"""A plugin that update the YAML as input."""
|
|
81
104
|
|
|
105
|
+
def get_yaml(self) -> Dict[str, Any]:
|
|
106
|
+
# cadl file doesn't have to be relative to output folder
|
|
107
|
+
with open(self.options["cadl_file"], "r") as fd:
|
|
108
|
+
return yaml.safe_load(fd.read())
|
|
109
|
+
|
|
110
|
+
def write_yaml(self, yaml_string: str) -> None:
|
|
111
|
+
with open(self.options["cadl_file"], "w") as fd:
|
|
112
|
+
fd.write(yaml_string)
|
|
113
|
+
|
|
82
114
|
def process(self) -> bool:
|
|
83
115
|
# List the input file, should be only one
|
|
84
|
-
yaml_data =
|
|
116
|
+
yaml_data = self.get_yaml()
|
|
85
117
|
|
|
86
118
|
self.update_yaml(yaml_data)
|
|
87
119
|
|
|
88
120
|
yaml_string = yaml.safe_dump(yaml_data)
|
|
89
121
|
|
|
90
|
-
self.
|
|
122
|
+
self.write_yaml(yaml_string)
|
|
91
123
|
return True
|
|
92
124
|
|
|
93
125
|
@abstractmethod
|
|
@@ -103,6 +135,12 @@ class YamlUpdatePlugin(Plugin):
|
|
|
103
135
|
class YamlUpdatePluginAutorest( # pylint: disable=abstract-method
|
|
104
136
|
YamlUpdatePlugin, PluginAutorest
|
|
105
137
|
):
|
|
138
|
+
def get_yaml(self) -> Dict[str, Any]:
|
|
139
|
+
return yaml.safe_load(self.read_file("code-model-v4-no-tags.yaml"))
|
|
140
|
+
|
|
141
|
+
def write_yaml(self, yaml_string: str) -> None:
|
|
142
|
+
self.write_file("code-model-v4-no-tags.yaml", yaml_string)
|
|
143
|
+
|
|
106
144
|
def get_options(self) -> Dict[str, Any]:
|
|
107
145
|
inputs = self._autorestapi.list_inputs()
|
|
108
146
|
_LOGGER.debug("Possible Inputs: %s", inputs)
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
import re
|
|
8
|
+
import argparse
|
|
9
|
+
|
|
10
|
+
|
|
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
|
+
def parse_args(need_cadl_file: bool = True):
|
|
41
|
+
parser = argparse.ArgumentParser(
|
|
42
|
+
description="Run mypy against target folder. Add a local custom plugin to the path prior to execution. "
|
|
43
|
+
)
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"--output-folder",
|
|
46
|
+
dest="output_folder",
|
|
47
|
+
help="Output folder for generated SDK",
|
|
48
|
+
required=True,
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--cadl-file",
|
|
52
|
+
dest="cadl_file",
|
|
53
|
+
help="Serialized cadl file",
|
|
54
|
+
required=need_cadl_file,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return parser.parse_args()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_body_type_for_description(body_parameter: Dict[str, Any]) -> str:
|
|
61
|
+
if body_parameter["type"]["type"] == "binary":
|
|
62
|
+
return "binary"
|
|
63
|
+
if body_parameter["type"]["type"] == "string":
|
|
64
|
+
return "string"
|
|
65
|
+
return "JSON"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# used if we want to get a string / binary type etc
|
|
69
|
+
KNOWN_TYPES: Dict[str, Dict[str, Any]] = {
|
|
70
|
+
"string": {"type": "string"},
|
|
71
|
+
"binary": {"type": "binary"},
|
|
72
|
+
"anydict": {"type": "dict", "elementType": {"type": "any"}},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
JSON_REGEXP = re.compile(r"^(application|text)/(.+\+)?json$")
|
|
@@ -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
|
-
|
|
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
|
-
|
|
24
|
-
if
|
|
25
|
-
|
|
26
|
-
if os.name == "nt" and
|
|
27
|
-
|
|
28
|
-
self.output_folder = Path(
|
|
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 {"
|
|
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()
|
|
@@ -0,0 +1,128 @@
|
|
|
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, List
|
|
8
|
+
from .. import YamlUpdatePlugin
|
|
9
|
+
from .._utils import parse_args
|
|
10
|
+
|
|
11
|
+
_LOGGER = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
OAUTH_TYPE = "OAuth2"
|
|
14
|
+
KEY_TYPE = "Key"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_azure_key_credential(key: str) -> Dict[str, Any]:
|
|
18
|
+
return {
|
|
19
|
+
"type": KEY_TYPE,
|
|
20
|
+
"policy": {"type": "AzureKeyCredentialPolicy", "key": key},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CadlFlags(YamlUpdatePlugin): # pylint: disable=abstract-method
|
|
25
|
+
"""A plugin to apply flags from backdoor python.json into cadl yaml file"""
|
|
26
|
+
|
|
27
|
+
def update_yaml(self, yaml_data: Dict[str, Any]) -> None:
|
|
28
|
+
"""Convert in place the YAML str."""
|
|
29
|
+
if self.options.get("add-credential"):
|
|
30
|
+
self.update_credential(yaml_data)
|
|
31
|
+
if self.options.get("namespace"):
|
|
32
|
+
yaml_data["client"]["namespace"] = self.options["namespace"]
|
|
33
|
+
if self.options.get("title"):
|
|
34
|
+
yaml_data["client"]["name"] = self.options["title"]
|
|
35
|
+
|
|
36
|
+
def get_credential_scopes_from_flags(self, auth_policy: str) -> List[str]:
|
|
37
|
+
if self.options.get("azure-arm"):
|
|
38
|
+
return ["https://management.azure.com/.default"]
|
|
39
|
+
credential_scopes_temp = self.options.get("credential-scopes")
|
|
40
|
+
credential_scopes = (
|
|
41
|
+
credential_scopes_temp.split(",") if credential_scopes_temp else None
|
|
42
|
+
)
|
|
43
|
+
if self.options.get("credential-scopes", False) and not credential_scopes:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
"--credential-scopes takes a list of scopes in comma separated format. "
|
|
46
|
+
"For example: --credential-scopes=https://cognitiveservices.azure.com/.default"
|
|
47
|
+
)
|
|
48
|
+
if not credential_scopes:
|
|
49
|
+
_LOGGER.warning(
|
|
50
|
+
"You have default credential policy %s "
|
|
51
|
+
"but not the --credential-scopes flag set while generating non-management plane code. "
|
|
52
|
+
"This is not recommend because it forces the customer to pass credential scopes "
|
|
53
|
+
"through kwargs if they want to authenticate.",
|
|
54
|
+
auth_policy,
|
|
55
|
+
)
|
|
56
|
+
credential_scopes = []
|
|
57
|
+
return credential_scopes
|
|
58
|
+
|
|
59
|
+
def get_token_credential(self, credential_scopes: List[str]) -> Dict[str, Any]:
|
|
60
|
+
return {
|
|
61
|
+
"type": OAUTH_TYPE,
|
|
62
|
+
"policy": {
|
|
63
|
+
"type": "ARMChallengeAuthenticationPolicy"
|
|
64
|
+
if self.options.get("azure-arm")
|
|
65
|
+
else "BearerTokenCredentialPolicy",
|
|
66
|
+
"credentialScopes": credential_scopes,
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
def update_credential_from_flags(self) -> Dict[str, Any]:
|
|
71
|
+
default_auth_policy = (
|
|
72
|
+
"ARMChallengeAuthenticationPolicy"
|
|
73
|
+
if self.options.get("azure-arm")
|
|
74
|
+
else "BearerTokenCredentialPolicy"
|
|
75
|
+
)
|
|
76
|
+
auth_policy = (
|
|
77
|
+
self.options.get("credential-default-policy-type") or default_auth_policy
|
|
78
|
+
)
|
|
79
|
+
credential_scopes = self.get_credential_scopes_from_flags(auth_policy)
|
|
80
|
+
key = self.options.get("credential-key-header-name")
|
|
81
|
+
if auth_policy.lower() in (
|
|
82
|
+
"armchallengeauthenticationpolicy",
|
|
83
|
+
"bearertokencredentialpolicy",
|
|
84
|
+
):
|
|
85
|
+
if key:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
"You have passed in a credential key header name with default credential policy type "
|
|
88
|
+
f"{auth_policy}. This is not allowed, since credential key header "
|
|
89
|
+
"name is tied with AzureKeyCredentialPolicy. Instead, with this policy it is recommend you "
|
|
90
|
+
"pass in --credential-scopes."
|
|
91
|
+
)
|
|
92
|
+
return self.get_token_credential(credential_scopes)
|
|
93
|
+
# Otherwise you have AzureKeyCredentialPolicy
|
|
94
|
+
if self.options.get("credential-scopes"):
|
|
95
|
+
raise ValueError(
|
|
96
|
+
"You have passed in credential scopes with default credential policy type "
|
|
97
|
+
"AzureKeyCredentialPolicy. This is not allowed, since credential scopes is tied with "
|
|
98
|
+
f"{default_auth_policy}. Instead, with this policy "
|
|
99
|
+
"you must pass in --credential-key-header-name."
|
|
100
|
+
)
|
|
101
|
+
if not key:
|
|
102
|
+
key = "api-key"
|
|
103
|
+
_LOGGER.info(
|
|
104
|
+
"Defaulting the AzureKeyCredentialPolicy header's name to 'api-key'"
|
|
105
|
+
)
|
|
106
|
+
return get_azure_key_credential(key)
|
|
107
|
+
|
|
108
|
+
def update_credential(self, yaml_data: Dict[str, Any]) -> None:
|
|
109
|
+
credential_type = self.update_credential_from_flags()
|
|
110
|
+
yaml_data["types"].append(credential_type)
|
|
111
|
+
credential = {
|
|
112
|
+
"type": credential_type,
|
|
113
|
+
"optional": False,
|
|
114
|
+
"description": "Credential needed for the client to connect to Azure.",
|
|
115
|
+
"clientName": "credential",
|
|
116
|
+
"location": "other",
|
|
117
|
+
"restApiName": "credential",
|
|
118
|
+
"implementation": "Client",
|
|
119
|
+
"skipUrlEncoding": True,
|
|
120
|
+
"inOverload": False,
|
|
121
|
+
}
|
|
122
|
+
yaml_data["client"]["parameters"].append(credential)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
# CADL pipeline will call this
|
|
127
|
+
args = parse_args()
|
|
128
|
+
CadlFlags(output_folder=args.output_folder, cadl_file=args.cadl_file).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
|
|
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
|
|
240
|
-
|
|
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
|
-
|
|
243
|
-
|
|
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(
|
|
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
|
-
|
|
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,12 @@
|
|
|
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
|
+
"Copyright (c) Microsoft Corporation. All rights reserved.\n"
|
|
9
|
+
"Licensed under the MIT License. See License.txt in the project root for license information.\n"
|
|
10
|
+
"Code generated by Microsoft (R) Python Code Generator.\n"
|
|
11
|
+
"Changes may cause incorrect behavior and will be lost if the code is regenerated."
|
|
12
|
+
)
|
|
@@ -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
|
-
|
|
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
|
|
|
@@ -10,6 +10,7 @@ from .base_type import BaseType
|
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from .code_model import CodeModel
|
|
13
|
+
from .model_type import ModelType
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class CombinedType(BaseType):
|
|
@@ -82,6 +83,9 @@ class CombinedType(BaseType):
|
|
|
82
83
|
"You shouldn't get a JSON template representation of multiple types"
|
|
83
84
|
)
|
|
84
85
|
|
|
86
|
+
def get_polymorphic_subtypes(self, polymorphic_subtypes: List["ModelType"]) -> None:
|
|
87
|
+
raise ValueError("You shouldn't get polymorphic subtypes of multiple types")
|
|
88
|
+
|
|
85
89
|
@property
|
|
86
90
|
def instance_check_template(self) -> str:
|
|
87
91
|
"""Template of what an instance check of a variable for this type would look like"""
|
|
@@ -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
|
|
@@ -60,6 +61,7 @@ class ModelType(BaseType): # pylint: disable=too-many-instance-attributes
|
|
|
60
61
|
"discriminatorValue"
|
|
61
62
|
)
|
|
62
63
|
self._created_json_template_representation = False
|
|
64
|
+
self._got_polymorphic_subtypes = False
|
|
63
65
|
self.is_public: bool = self.yaml_data.get("isPublic", True)
|
|
64
66
|
self.snake_case_name: str = self.yaml_data["snakeCaseName"]
|
|
65
67
|
|
|
@@ -153,12 +155,26 @@ class ModelType(BaseType): # pylint: disable=too-many-instance-attributes
|
|
|
153
155
|
# once we've finished, we want to reset created_json_template_representation to false
|
|
154
156
|
# so we can call it again
|
|
155
157
|
self._created_json_template_representation = False
|
|
156
|
-
|
|
158
|
+
optional_keys = [
|
|
159
|
+
f'"{p.rest_api_name}"'
|
|
160
|
+
for p in self.properties
|
|
161
|
+
if getattr(p, "optional", False)
|
|
162
|
+
]
|
|
163
|
+
return OrderedDict(
|
|
164
|
+
sorted(
|
|
165
|
+
representation.items(),
|
|
166
|
+
key=lambda item: f"{1 if item[0] in optional_keys else 0}{item[0]}",
|
|
167
|
+
)
|
|
168
|
+
)
|
|
157
169
|
|
|
158
170
|
def get_polymorphic_subtypes(self, polymorphic_subtypes: List["ModelType"]) -> None:
|
|
171
|
+
|
|
159
172
|
is_polymorphic_subtype = (
|
|
160
173
|
self.discriminator_value and not self.discriminated_subtypes
|
|
161
174
|
)
|
|
175
|
+
if self._got_polymorphic_subtypes:
|
|
176
|
+
return
|
|
177
|
+
self._got_polymorphic_subtypes = True
|
|
162
178
|
if (
|
|
163
179
|
self.name not in (m.name for m in polymorphic_subtypes)
|
|
164
180
|
and is_polymorphic_subtype
|
|
@@ -168,6 +184,7 @@ class ModelType(BaseType): # pylint: disable=too-many-instance-attributes
|
|
|
168
184
|
discriminated_subtype.get_polymorphic_subtypes(polymorphic_subtypes)
|
|
169
185
|
for property in self.properties:
|
|
170
186
|
property.get_polymorphic_subtypes(polymorphic_subtypes)
|
|
187
|
+
self._got_polymorphic_subtypes = False
|
|
171
188
|
|
|
172
189
|
@classmethod
|
|
173
190
|
def from_yaml(
|