@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.
Files changed (41) hide show
  1. package/ChangeLog.md +57 -0
  2. package/README.md +22 -3
  3. package/autorest/__init__.py +52 -14
  4. package/autorest/_utils.py +75 -0
  5. package/autorest/black/__init__.py +15 -8
  6. package/autorest/cadlflags/__init__.py +128 -0
  7. package/autorest/codegen/__init__.py +14 -21
  8. package/autorest/codegen/_utils.py +12 -0
  9. package/autorest/codegen/models/__init__.py +10 -1
  10. package/autorest/codegen/models/combined_type.py +4 -0
  11. package/autorest/codegen/models/model_type.py +18 -1
  12. package/autorest/codegen/models/parameter.py +5 -1
  13. package/autorest/codegen/models/property.py +1 -8
  14. package/autorest/codegen/models/request_builder_parameter.py +1 -1
  15. package/autorest/codegen/models/response.py +7 -0
  16. package/autorest/codegen/models/utils.py +0 -3
  17. package/autorest/codegen/serializers/__init__.py +20 -15
  18. package/autorest/codegen/serializers/builder_serializer.py +5 -5
  19. package/autorest/codegen/templates/README.md.jinja2 +3 -3
  20. package/autorest/codegen/templates/setup.py.jinja2 +1 -2
  21. package/autorest/jsonrpc/server.py +7 -1
  22. package/autorest/m2r/__init__.py +11 -4
  23. package/autorest/m4reformatter/__init__.py +13 -37
  24. package/autorest/multiapi/__init__.py +5 -6
  25. package/autorest/multiapi/serializers/__init__.py +5 -3
  26. package/autorest/multiapi/templates/multiapi_init.py.jinja2 +4 -0
  27. package/autorest/multiclient/__init__.py +50 -0
  28. package/autorest/multiclient/templates/init.py.jinja2 +8 -0
  29. package/autorest/multiclient/templates/version.py.jinja2 +8 -0
  30. package/autorest/postprocess/__init__.py +1 -1
  31. package/autorest/preprocess/__init__.py +138 -17
  32. package/autorest/preprocess/helpers.py +0 -30
  33. package/install.py +3 -3
  34. package/package.json +3 -2
  35. package/prepare.py +2 -2
  36. package/requirements.txt +8 -9
  37. package/run-python3.js +2 -2
  38. package/run_cadl.py +27 -0
  39. package/setup.py +2 -3
  40. package/start.py +2 -2
  41. 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 v5.0.0
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
@@ -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
- pass
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
- raise NotImplementedError("Haven't plugged in Cadl yet")
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
- raise NotImplementedError("Haven't plugged in Cadl yet")
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__(self, *, autorestapi: AutorestAPI) -> None:
36
- super().__init__()
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__(self, autorestapi: AutorestAPI) -> None:
71
- super().__init__(autorestapi=autorestapi)
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 = yaml.safe_load(self.read_file("code-model-v4-no-tags.yaml"))
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.write_file("code-model-v4-no-tags.yaml", yaml_string)
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
- _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()
@@ -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["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,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
- 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
 
@@ -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
- return representation
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(