@autorest/python 5.16.0 → 5.17.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.
Files changed (92) hide show
  1. package/ChangeLog.md +43 -3
  2. package/README.md +30 -4
  3. package/autorest/__init__.py +1 -1
  4. package/autorest/codegen/__init__.py +48 -209
  5. package/autorest/codegen/models/__init__.py +116 -83
  6. package/autorest/codegen/models/base_builder.py +49 -88
  7. package/autorest/codegen/models/base_model.py +1 -1
  8. package/autorest/codegen/models/{base_schema.py → base_type.py} +56 -40
  9. package/autorest/codegen/models/client.py +157 -48
  10. package/autorest/codegen/models/code_model.py +108 -254
  11. package/autorest/codegen/models/combined_type.py +107 -0
  12. package/autorest/codegen/models/{constant_schema.py → constant_type.py} +49 -40
  13. package/autorest/codegen/models/credential_types.py +224 -0
  14. package/autorest/codegen/models/{dictionary_schema.py → dictionary_type.py} +41 -31
  15. package/autorest/codegen/models/enum_type.py +195 -0
  16. package/autorest/codegen/models/imports.py +23 -0
  17. package/autorest/codegen/models/list_type.py +134 -0
  18. package/autorest/codegen/models/lro_operation.py +77 -156
  19. package/autorest/codegen/models/lro_paging_operation.py +28 -11
  20. package/autorest/codegen/models/model_type.py +239 -0
  21. package/autorest/codegen/models/operation.py +303 -269
  22. package/autorest/codegen/models/operation_group.py +48 -89
  23. package/autorest/codegen/models/paging_operation.py +80 -123
  24. package/autorest/codegen/models/parameter.py +289 -396
  25. package/autorest/codegen/models/parameter_list.py +348 -360
  26. package/autorest/codegen/models/primitive_types.py +544 -0
  27. package/autorest/codegen/models/property.py +109 -139
  28. package/autorest/codegen/models/request_builder.py +105 -88
  29. package/autorest/codegen/models/request_builder_parameter.py +112 -100
  30. package/autorest/codegen/models/response.py +325 -0
  31. package/autorest/codegen/models/utils.py +12 -19
  32. package/autorest/codegen/serializers/__init__.py +46 -37
  33. package/autorest/codegen/serializers/builder_serializer.py +604 -1146
  34. package/autorest/codegen/serializers/client_serializer.py +83 -88
  35. package/autorest/codegen/serializers/general_serializer.py +5 -64
  36. package/autorest/codegen/serializers/import_serializer.py +7 -4
  37. package/autorest/codegen/serializers/metadata_serializer.py +15 -104
  38. package/autorest/codegen/serializers/model_base_serializer.py +40 -32
  39. package/autorest/codegen/serializers/model_generic_serializer.py +8 -6
  40. package/autorest/codegen/serializers/model_init_serializer.py +2 -4
  41. package/autorest/codegen/serializers/model_python3_serializer.py +22 -16
  42. package/autorest/codegen/serializers/operation_groups_serializer.py +4 -13
  43. package/autorest/codegen/serializers/parameter_serializer.py +174 -0
  44. package/autorest/codegen/serializers/request_builders_serializer.py +12 -29
  45. package/autorest/codegen/serializers/utils.py +0 -142
  46. package/autorest/codegen/templates/MANIFEST.in.jinja2 +1 -0
  47. package/autorest/codegen/templates/{service_client.py.jinja2 → client.py.jinja2} +7 -7
  48. package/autorest/codegen/templates/config.py.jinja2 +13 -13
  49. package/autorest/codegen/templates/enum.py.jinja2 +4 -4
  50. package/autorest/codegen/templates/enum_container.py.jinja2 +1 -1
  51. package/autorest/codegen/templates/init.py.jinja2 +2 -2
  52. package/autorest/codegen/templates/lro_operation.py.jinja2 +4 -1
  53. package/autorest/codegen/templates/lro_paging_operation.py.jinja2 +4 -1
  54. package/autorest/codegen/templates/metadata.json.jinja2 +33 -33
  55. package/autorest/codegen/templates/model.py.jinja2 +23 -24
  56. package/autorest/codegen/templates/model_container.py.jinja2 +2 -1
  57. package/autorest/codegen/templates/model_init.py.jinja2 +3 -5
  58. package/autorest/codegen/templates/operation.py.jinja2 +6 -8
  59. package/autorest/codegen/templates/operation_group.py.jinja2 +7 -7
  60. package/autorest/codegen/templates/operation_groups_container.py.jinja2 +1 -1
  61. package/autorest/codegen/templates/operation_tools.jinja2 +8 -2
  62. package/autorest/codegen/templates/paging_operation.py.jinja2 +2 -2
  63. package/autorest/codegen/templates/request_builder.py.jinja2 +13 -11
  64. package/autorest/codegen/templates/setup.py.jinja2 +9 -3
  65. package/autorest/codegen/templates/vendor.py.jinja2 +1 -1
  66. package/autorest/jsonrpc/server.py +15 -3
  67. package/autorest/m4reformatter/__init__.py +1108 -0
  68. package/autorest/multiapi/models/code_model.py +1 -1
  69. package/autorest/multiapi/serializers/__init__.py +4 -4
  70. package/autorest/multiapi/templates/multiapi_config.py.jinja2 +3 -3
  71. package/autorest/multiapi/templates/multiapi_init.py.jinja2 +2 -2
  72. package/autorest/multiapi/templates/multiapi_operations_mixin.py.jinja2 +3 -3
  73. package/autorest/multiapi/templates/multiapi_service_client.py.jinja2 +9 -9
  74. package/autorest/postprocess/__init__.py +202 -0
  75. package/autorest/postprocess/get_all.py +19 -0
  76. package/autorest/postprocess/venvtools.py +73 -0
  77. package/autorest/preprocess/__init__.py +209 -0
  78. package/autorest/preprocess/helpers.py +54 -0
  79. package/autorest/{namer → preprocess}/python_mappings.py +21 -16
  80. package/package.json +2 -2
  81. package/autorest/codegen/models/credential_model.py +0 -55
  82. package/autorest/codegen/models/credential_schema.py +0 -95
  83. package/autorest/codegen/models/credential_schema_policy.py +0 -73
  84. package/autorest/codegen/models/enum_schema.py +0 -225
  85. package/autorest/codegen/models/list_schema.py +0 -135
  86. package/autorest/codegen/models/object_schema.py +0 -303
  87. package/autorest/codegen/models/primitive_schemas.py +0 -495
  88. package/autorest/codegen/models/request_builder_parameter_list.py +0 -249
  89. package/autorest/codegen/models/schema_request.py +0 -55
  90. package/autorest/codegen/models/schema_response.py +0 -141
  91. package/autorest/namer/__init__.py +0 -23
  92. package/autorest/namer/name_converter.py +0 -509
@@ -34,7 +34,7 @@ class CodeModel: # pylint: disable=too-many-instance-attributes
34
34
  self.azure_arm = default_version_metadata["client"]["azure_arm"]
35
35
  self.default_version_metadata = default_version_metadata
36
36
  self.version_path_to_metadata = version_path_to_metadata
37
- self.service_client = Client(
37
+ self.client = Client(
38
38
  self.azure_arm, default_version_metadata, version_path_to_metadata
39
39
  )
40
40
  self.config = Config(default_version_metadata)
@@ -18,7 +18,7 @@ __all__ = [
18
18
 
19
19
  _FILE_TO_TEMPLATE = {
20
20
  "init": "multiapi_init.py.jinja2",
21
- "service_client": "multiapi_service_client.py.jinja2",
21
+ "client": "multiapi_service_client.py.jinja2",
22
22
  "config": "multiapi_config.py.jinja2",
23
23
  "models": "multiapi_models.py.jinja2",
24
24
  "operations_mixin": "multiapi_operations_mixin.py.jinja2",
@@ -58,11 +58,11 @@ class MultiAPISerializer(object):
58
58
 
59
59
  # serialize service client file
60
60
  imports = FileImportSerializer(
61
- code_model.service_client.imports(async_mode), is_python3_file=async_mode
61
+ code_model.client.imports(async_mode), is_python3_file=async_mode
62
62
  )
63
63
  self._autorestapi.write_file(
64
- _get_file_path(code_model.service_client.filename, async_mode),
65
- _render_template("service_client", imports=imports),
64
+ _get_file_path(code_model.client.filename, async_mode),
65
+ _render_template("client", imports=imports),
66
66
  )
67
67
 
68
68
  # serialize config file
@@ -28,8 +28,8 @@ def __init__(
28
28
  # --------------------------------------------------------------------------
29
29
  {{ imports }}
30
30
 
31
- class {{ code_model.service_client.name }}Configuration(Configuration):
32
- """Configuration for {{ code_model.service_client.name }}.
31
+ class {{ code_model.client.name }}Configuration(Configuration):
32
+ """Configuration for {{ code_model.client.name }}.
33
33
 
34
34
  Note that all parameters used to create this instance are saved as instance
35
35
  attributes.
@@ -52,7 +52,7 @@ class {{ code_model.service_client.name }}Configuration(Configuration):
52
52
  raise ValueError("Parameter '{{ parameter.name }}' must not be None.")
53
53
  {% endif %}
54
54
  {% endfor %}
55
- super({{ code_model.service_client.name }}Configuration, self).__init__(**kwargs)
55
+ super({{ code_model.client.name }}Configuration, self).__init__(**kwargs)
56
56
 
57
57
  {% for parameter in code_model.global_parameters.parameters %}
58
58
  self.{{ parameter.name }} = {{ parameter.name }}
@@ -6,8 +6,8 @@
6
6
  # Changes may cause incorrect behavior and will be lost if the code is regenerated.
7
7
  # --------------------------------------------------------------------------
8
8
 
9
- from .{{ code_model.service_client.filename }} import {{ code_model.service_client.name }}
10
- __all__ = ['{{ code_model.service_client.name }}']
9
+ from .{{ code_model.client.filename }} import {{ code_model.client.name }}
10
+ __all__ = ['{{ code_model.client.name }}']
11
11
  {% if not async_mode %}
12
12
 
13
13
  try:
@@ -14,7 +14,7 @@ from msrest import Serializer, Deserializer
14
14
  {% endif %}
15
15
 
16
16
 
17
- class {{ code_model.service_client.name }}OperationsMixin(object):
17
+ class {{ code_model.client.name }}OperationsMixin(object):
18
18
  {% for mixin_operation in code_model.operation_mixin_group.mixin_operations %}
19
19
 
20
20
  {{ mixin_operation.signature(async_mode) | indent }} {{ mixin_operation.description(async_mode) | indent(8) }}
@@ -22,7 +22,7 @@ class {{ code_model.service_client.name }}OperationsMixin(object):
22
22
  {% for api in mixin_operation.available_apis|sort %}
23
23
  {% set if_statement = "if" if loop.first else "elif" %}
24
24
  {{ if_statement }} api_version == '{{ code_model.mod_to_api_version[api] }}':
25
- from {{ ".." if async_mode else "." }}{{ api }}{{ ".aio" if async_mode else "" }}.operations import {{ code_model.service_client.name }}OperationsMixin as OperationClass
25
+ from {{ ".." if async_mode else "." }}{{ api }}{{ ".aio" if async_mode else "" }}.operations import {{ code_model.client.name }}OperationsMixin as OperationClass
26
26
  {% endfor %}
27
27
  else:
28
28
  raise ValueError("API version {} does not have operation '{{ mixin_operation.name }}'".format(api_version))
@@ -30,7 +30,7 @@ class {{ code_model.service_client.name }}OperationsMixin(object):
30
30
  mixin_instance._client = self._client
31
31
  mixin_instance._config = self._config
32
32
  mixin_instance._serialize = Serializer(self._models_dict(api_version))
33
- {% if not code_model.service_client.client_side_validation %}
33
+ {% if not code_model.client.client_side_validation %}
34
34
  mixin_instance._serialize.client_side_validation = False
35
35
  {% endif %}
36
36
  mixin_instance._deserialize = Deserializer(self._models_dict(api_version))
@@ -43,8 +43,8 @@ class _SDKClient(object):
43
43
  """
44
44
  pass
45
45
 
46
- class {{ code_model.service_client.name }}({% if code_model.operation_mixin_group.mixin_operations %}{{ code_model.service_client.name }}OperationsMixin, {% endif %}MultiApiClientMixin, _SDKClient):
47
- """{{ code_model.service_client.description }}
46
+ class {{ code_model.client.name }}({% if code_model.operation_mixin_group.mixin_operations %}{{ code_model.client.name }}OperationsMixin, {% endif %}MultiApiClientMixin, _SDKClient):
47
+ """{{ code_model.client.description }}
48
48
 
49
49
  This ready contains multiple API versions, to help you deal with all of the Azure clouds
50
50
  (Azure Stack, Azure Government, Azure China, etc.).
@@ -64,13 +64,13 @@ class {{ code_model.service_client.name }}({% if code_model.operation_mixin_grou
64
64
  :param {{ parameter.name }}: {{ parameter.description(async_mode) }}
65
65
  :type {{ parameter.name }}: {{ parameter.docstring_type(async_mode) }}
66
66
  {% endfor %}
67
- {% if code_model.service_client.has_lro_operations %}
67
+ {% if code_model.client.has_lro_operations %}
68
68
  :keyword int polling_interval: Default waiting time between two polls for LRO operations if no Retry-After header is present.
69
69
  {% endif %}
70
70
  """
71
71
 
72
72
  DEFAULT_API_VERSION = '{{ code_model.mod_to_api_version[code_model.default_api_version] }}'
73
- _PROFILE_TAG = "{{ code_model.module_name }}.{{ code_model.service_client.name }}"
73
+ _PROFILE_TAG = "{{ code_model.module_name }}.{{ code_model.client.name }}"
74
74
  LATEST_PROFILE = ProfileDefinition({
75
75
  _PROFILE_TAG: {
76
76
  None: DEFAULT_API_VERSION,
@@ -82,8 +82,8 @@ class {{ code_model.service_client.name }}({% if code_model.operation_mixin_grou
82
82
  )
83
83
 
84
84
  {{ method_signature()|indent }}
85
- {% if not code_model.service_client.host_value %}
86
- {% for parameterized_host_template, api_versions in code_model.service_client.parameterized_host_template_to_api_version|dictsort %}
85
+ {% if not code_model.client.host_value %}
86
+ {% for parameterized_host_template, api_versions in code_model.client.parameterized_host_template_to_api_version|dictsort %}
87
87
  {% set if_statement = "if" if loop.first else "elif" %}
88
88
  {{ if_statement ~ " api_version == '" ~ api_versions|join("' or api_version == '") ~ "'" }}:
89
89
  base_url = {{ parameterized_host_template }}
@@ -91,9 +91,9 @@ class {{ code_model.service_client.name }}({% if code_model.operation_mixin_grou
91
91
  else:
92
92
  raise ValueError("API version {} is not available".format(api_version))
93
93
  {% endif %}
94
- self._config = {{ code_model.service_client.name }}Configuration({{ code_model.global_parameters.call }}{{ ", " if code_model.global_parameters.call }}**kwargs)
95
- self._client = {{ async_prefix }}{{ code_model.service_client.pipeline_client }}(base_url=base_url, config=self._config, **kwargs)
96
- super({{ code_model.service_client.name }}, self).__init__(
94
+ self._config = {{ code_model.client.name }}Configuration({{ code_model.global_parameters.call }}{{ ", " if code_model.global_parameters.call }}**kwargs)
95
+ self._client = {{ async_prefix }}{{ code_model.client.pipeline_client }}(base_url=base_url, config=self._config, **kwargs)
96
+ super({{ code_model.client.name }}, self).__init__(
97
97
  api_version=api_version,
98
98
  profile=profile
99
99
  )
@@ -0,0 +1,202 @@
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 Tuple
7
+ from pathlib import Path
8
+ import os
9
+ import shutil
10
+ from venv import EnvBuilder
11
+ import black
12
+ from .venvtools import ExtendedEnvBuilder, python_run
13
+
14
+ from .. import Plugin
15
+
16
+ _BLACK_MODE = black.Mode()
17
+ _BLACK_MODE.line_length = 120
18
+
19
+
20
+ def format_file(file: Path, file_content: str) -> str:
21
+ if not file.suffix == ".py":
22
+ return file_content
23
+ try:
24
+ file_content = black.format_file_contents(
25
+ file_content, fast=True, mode=_BLACK_MODE
26
+ )
27
+ except black.NothingChanged:
28
+ pass
29
+ return file_content
30
+
31
+
32
+ class PostProcessPlugin(Plugin):
33
+ def __init__(self, autorestapi):
34
+ super().__init__(autorestapi)
35
+ output_folder_uri = self._autorestapi.get_value("outputFolderUri")
36
+ if output_folder_uri.startswith("file:"):
37
+ output_folder_uri = output_folder_uri[5:]
38
+ if os.name == "nt" and output_folder_uri.startswith("///"):
39
+ output_folder_uri = output_folder_uri[3:]
40
+ self.output_folder = Path(output_folder_uri) # path to where the setup.py is
41
+ self.setup_venv()
42
+
43
+ # set up the venv
44
+ # base folder is where the code starts, i.e. where we
45
+ self.base_folder, self.namespace = self.get_namespace(self.output_folder, "")
46
+
47
+ def setup_venv(self):
48
+ venv_path = self.output_folder / Path(".temp_folder") / Path("temp_venv")
49
+
50
+ if venv_path.exists():
51
+ env_builder = EnvBuilder(with_pip=True)
52
+ self.venv_context = env_builder.ensure_directories(venv_path)
53
+ else:
54
+ env_builder = ExtendedEnvBuilder(with_pip=True)
55
+ env_builder.create(venv_path)
56
+ self.venv_context = env_builder.context
57
+ python_run(
58
+ self.venv_context,
59
+ "pip",
60
+ ["install", "-e", str(self.output_folder)],
61
+ directory=self.output_folder,
62
+ )
63
+
64
+ def get_namespace(self, dir: Path, namespace: str) -> Tuple[Path, str]:
65
+ try:
66
+ init_file = next(d for d in dir.iterdir() if d.name == "__init__.py")
67
+ # we don't care about pkgutil inits, we skip over them
68
+ file_content = self._autorestapi.read_file(
69
+ init_file.relative_to(self.output_folder)
70
+ )
71
+ if not "pkgutil" in file_content:
72
+ return dir, namespace
73
+ except StopIteration:
74
+ pass
75
+
76
+ try:
77
+ # first, see if we can get a folder that has the same name as the current output folder
78
+ start = self.output_folder.stem.split("-")[0]
79
+ next_dir = next(d for d in dir.iterdir() if d.is_dir() and d.name == start)
80
+ except StopIteration:
81
+ invalid_start_chars = [".", "_"]
82
+ invalid_dirs = [
83
+ "swagger",
84
+ "out",
85
+ "tests",
86
+ "samples",
87
+ ]
88
+
89
+ next_dir = next(
90
+ d
91
+ for d in dir.iterdir()
92
+ if d.is_dir()
93
+ and not str(d).endswith("egg-info")
94
+ and d.name[0] not in invalid_start_chars
95
+ and d.name not in invalid_dirs
96
+ )
97
+
98
+ namespace = f"{namespace}.{next_dir.name}" if namespace else next_dir.name
99
+ return self.get_namespace(next_dir, namespace)
100
+
101
+ def process(self) -> bool:
102
+ folders = [
103
+ f
104
+ for f in self.base_folder.glob("**/*")
105
+ if f.is_dir() and not f.stem.startswith("__")
106
+ ]
107
+ # will always have the root
108
+ self.fix_imports_in_init(
109
+ generated_file_name="_client",
110
+ folder_path=self.base_folder,
111
+ namespace=self.namespace,
112
+ )
113
+ try:
114
+ aio_folder = next(f for f in folders if f.stem == "aio")
115
+ self.fix_imports_in_init(
116
+ generated_file_name="_client",
117
+ folder_path=aio_folder,
118
+ namespace=f"{self.namespace}.aio",
119
+ )
120
+ except StopIteration:
121
+ pass
122
+
123
+ try:
124
+ models_folder = next(f for f in folders if f.stem == "models")
125
+ self.fix_imports_in_init(
126
+ generated_file_name="_models",
127
+ folder_path=models_folder,
128
+ namespace=f"{self.namespace}.models",
129
+ )
130
+ except StopIteration:
131
+ pass
132
+ operations_folders = [
133
+ f for f in folders if f.stem in ["operations", "_operations"]
134
+ ]
135
+ for operations_folder in operations_folders:
136
+ aio = ".aio" if operations_folder.parent.stem == "aio" else ""
137
+ self.fix_imports_in_init(
138
+ generated_file_name="_operations",
139
+ folder_path=operations_folder,
140
+ namespace=f"{self.namespace}{aio}.{operations_folder.stem}",
141
+ )
142
+ shutil.rmtree(f"{str(self.output_folder)}/.temp_folder")
143
+ return True
144
+
145
+ def fix_imports_in_init(
146
+ self, generated_file_name: str, folder_path: Path, namespace: str
147
+ ) -> None:
148
+ customized_objects_str = python_run(
149
+ self.venv_context,
150
+ command=[namespace, str(self.output_folder)],
151
+ module="get_all",
152
+ )
153
+
154
+ if not customized_objects_str:
155
+ return
156
+ customized_objects = {
157
+ k: None for k in customized_objects_str.split(",")
158
+ }.keys() # filter out duplicates
159
+ file = (folder_path / "__init__.py").relative_to(self.output_folder)
160
+ file_content = self._autorestapi.read_file(file)
161
+ added_objs = []
162
+ for obj in customized_objects:
163
+ if f" import {obj}\n" in file_content:
164
+ # means we're overriding a generated model
165
+ file_content = file_content.replace(
166
+ f"from .{generated_file_name} import {obj}\n",
167
+ f"from ._patch import {obj}\n",
168
+ )
169
+ else:
170
+ added_objs.append(obj)
171
+ file_content = file_content.replace(
172
+ "try:\n from ._patch import __all__ as _patch_all\n "
173
+ "from ._patch import * # type: ignore # pylint: disable=unused-wildcard-import"
174
+ "\nexcept ImportError:\n _patch_all = []",
175
+ "",
176
+ )
177
+ file_content = file_content.replace(
178
+ "from ._patch import __all__ as _patch_all", ""
179
+ )
180
+ file_content = file_content.replace(
181
+ "from ._patch import * # type: ignore # pylint: disable=unused-wildcard-import\n",
182
+ "",
183
+ )
184
+ file_content = file_content.replace(
185
+ "__all__.extend([p for p in _patch_all if p not in __all__])", ""
186
+ )
187
+ if added_objs:
188
+ # add import
189
+ patch_sdk_import = "from ._patch import patch_sdk as _patch_sdk"
190
+ imports = "\n".join([f"from ._patch import {obj}" for obj in added_objs])
191
+ if imports:
192
+ replacement = f"{imports}\n{patch_sdk_import}"
193
+ else:
194
+ replacement = patch_sdk_import
195
+ file_content = file_content.replace(patch_sdk_import, replacement)
196
+ # add to __all__
197
+ added_objs_all = "\n".join([f' "{obj}",' for obj in added_objs]) + "\n"
198
+ file_content = file_content.replace(
199
+ "__all__ = [", f"__all__ = [\n{added_objs_all}", 1
200
+ )
201
+ formatted_file = format_file(file, file_content)
202
+ self._autorestapi.write_file(file, formatted_file)
@@ -0,0 +1,19 @@
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 sys
7
+ import importlib
8
+
9
+
10
+ def main(namespace):
11
+ sdk = importlib.import_module(namespace)
12
+ return sdk._patch.__all__ # pylint: disable=protected-access
13
+
14
+
15
+ if __name__ == "__main__":
16
+ patched = ",".join(main(sys.argv[1]))
17
+ output_folder = sys.argv[2]
18
+ with open(f"{output_folder}/.temp_folder/patched.txt", "w") as f:
19
+ f.write(patched)
@@ -0,0 +1,73 @@
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 Optional
7
+ import subprocess
8
+ import venv
9
+ import sys
10
+ from pathlib import Path
11
+
12
+
13
+ _ROOT_DIR = Path(__file__).parent
14
+
15
+
16
+ class ExtendedEnvBuilder(venv.EnvBuilder):
17
+ """An extended env builder which saves the context, to have access
18
+ easily to bin path and such.
19
+ """
20
+
21
+ def __init__(self, *args, **kwargs):
22
+ self.context = None
23
+ super(ExtendedEnvBuilder, self).__init__(*args, **kwargs)
24
+
25
+ def ensure_directories(self, env_dir):
26
+ self.context = super(ExtendedEnvBuilder, self).ensure_directories(env_dir)
27
+ return self.context
28
+
29
+
30
+ def create(
31
+ env_dir,
32
+ system_site_packages=False,
33
+ clear=False,
34
+ symlinks=False,
35
+ with_pip=False,
36
+ prompt=None,
37
+ ):
38
+ """Create a virtual environment in a directory."""
39
+ builder = ExtendedEnvBuilder(
40
+ system_site_packages=system_site_packages,
41
+ clear=clear,
42
+ symlinks=symlinks,
43
+ with_pip=with_pip,
44
+ prompt=prompt,
45
+ )
46
+ builder.create(env_dir)
47
+ return builder.context
48
+
49
+
50
+ def python_run( # pylint: disable=inconsistent-return-statements
51
+ venv_context, module, command, directory=_ROOT_DIR, *, error_ok=False
52
+ ) -> Optional[str]:
53
+ try:
54
+ cmd_line = [
55
+ venv_context.env_exe,
56
+ "-m",
57
+ module,
58
+ ] + command
59
+ print("Executing: {}".format(" ".join(cmd_line)))
60
+ subprocess.run(
61
+ cmd_line,
62
+ cwd=directory,
63
+ check=True,
64
+ stdout=False,
65
+ )
66
+ if module == "get_all":
67
+ with open(f"{command[1]}/.temp_folder/patched.txt", "r") as f:
68
+ return f.read()
69
+ except subprocess.CalledProcessError as err:
70
+ print(err)
71
+ if not error_ok:
72
+ sys.exit(1)
73
+ return None
@@ -0,0 +1,209 @@
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
+ """The preprocessing autorest plugin.
7
+ """
8
+ from typing import Callable, Dict, Any, List, Optional
9
+ from .helpers import to_snake_case, pad_reserved_words, add_redefined_builtin_info
10
+ from .python_mappings import PadType
11
+
12
+ from .. import YamlUpdatePlugin
13
+
14
+
15
+ def update_description(
16
+ description: Optional[str], default_description: str = ""
17
+ ) -> str:
18
+ if not description:
19
+ description = default_description
20
+ description.rstrip(" ")
21
+ if description and description[-1] != ".":
22
+ description += "."
23
+ return description
24
+
25
+
26
+ def update_operation_group_class_name(
27
+ yaml_data: Dict[str, Any], class_name: str
28
+ ) -> str:
29
+ if class_name == "":
30
+ return yaml_data["client"]["name"] + "OperationsMixin"
31
+ if class_name == "Operations":
32
+ return "Operations"
33
+ return class_name + "Operations"
34
+
35
+
36
+ def update_parameter(yaml_data: Dict[str, Any]) -> None:
37
+ yaml_data["description"] = update_description(yaml_data["description"])
38
+ if not (
39
+ yaml_data["location"] == "header"
40
+ and yaml_data["clientName"] in ("content_type", "accept")
41
+ ):
42
+ yaml_data["clientName"] = pad_reserved_words(
43
+ yaml_data["clientName"].lower(), PadType.PARAMETER
44
+ )
45
+ if yaml_data.get("propertyToParameterName"):
46
+ # need to create a new one with padded keys and values
47
+ yaml_data["propertyToParameterName"] = {
48
+ pad_reserved_words(prop, PadType.PROPERTY)
49
+ .lower(): pad_reserved_words(param_name, PadType.PARAMETER)
50
+ .lower()
51
+ for prop, param_name in yaml_data["propertyToParameterName"].items()
52
+ }
53
+
54
+
55
+ def update_types(yaml_data: List[Dict[str, Any]]) -> None:
56
+ for type in yaml_data:
57
+ for property in type.get("properties", []):
58
+ property["description"] = update_description(property["description"])
59
+ property["clientName"] = pad_reserved_words(
60
+ property["clientName"].lower(), PadType.PROPERTY
61
+ )
62
+ add_redefined_builtin_info(property["clientName"], property)
63
+ if type.get("name"):
64
+ type["description"] = update_description(type["description"], type["name"])
65
+
66
+
67
+ def update_client(yaml_data: Dict[str, Any]) -> None:
68
+ yaml_data["description"] = update_description(
69
+ yaml_data["description"], default_description=yaml_data["name"]
70
+ )
71
+ yaml_data["moduleName"] = to_snake_case(yaml_data["name"].replace(" ", "_"))
72
+ for parameter in yaml_data["parameters"]:
73
+ update_parameter(parameter)
74
+
75
+
76
+ def update_paging_response(yaml_data: Dict[str, Any]) -> None:
77
+ yaml_data["discriminator"] = "paging"
78
+ yaml_data["pagerSync"] = yaml_data.get("pagerSync") or "azure.core.paging.ItemPaged"
79
+ yaml_data["pagerAsync"] = (
80
+ yaml_data.get("pagerAsync") or "azure.core.async_paging.AsyncItemPaged"
81
+ )
82
+
83
+
84
+ class PreProcessPlugin(YamlUpdatePlugin):
85
+ """Add Python naming information."""
86
+
87
+ def get_operation_updater(
88
+ self, yaml_data: Dict[str, Any]
89
+ ) -> Callable[[Dict[str, Any]], None]:
90
+ if yaml_data["discriminator"] == "lropaging":
91
+ return self.update_lro_paging_operation
92
+ if yaml_data["discriminator"] == "lro":
93
+ return self.update_lro_operation
94
+ if yaml_data["discriminator"] == "paging":
95
+ return self.update_paging_operation
96
+ return self.update_operation
97
+
98
+ def update_operation(self, yaml_data: Dict[str, Any]) -> None:
99
+ yaml_data["groupName"] = pad_reserved_words(
100
+ yaml_data["groupName"], PadType.OPERATION_GROUP
101
+ )
102
+ yaml_data["groupName"] = to_snake_case(yaml_data["groupName"])
103
+ yaml_data["name"] = yaml_data["name"].lower()
104
+ yaml_data["name"] = pad_reserved_words(yaml_data["name"], PadType.METHOD)
105
+ yaml_data["description"] = update_description(
106
+ yaml_data["description"], yaml_data["name"]
107
+ )
108
+ yaml_data["summary"] = update_description(yaml_data.get("summary", ""))
109
+ for parameter in yaml_data["parameters"]:
110
+ update_parameter(parameter)
111
+ if yaml_data.get("bodyParameter"):
112
+ update_parameter(yaml_data["bodyParameter"])
113
+ for entry in yaml_data["bodyParameter"].get("entries", []):
114
+ update_parameter(entry)
115
+ for overload in yaml_data.get("overloads", []):
116
+ self.update_operation(overload)
117
+ for response in yaml_data.get("responses", []):
118
+ response["discriminator"] = "operation"
119
+
120
+ def _update_lro_operation_helper(self, yaml_data: Dict[str, Any]) -> None:
121
+ azure_arm = self._autorestapi.get_boolean_value("azure-arm", False)
122
+ for response in yaml_data.get("responses", []):
123
+ response["discriminator"] = "lro"
124
+ response["pollerSync"] = (
125
+ response.get("pollerSync") or "azure.core.polling.LROPoller"
126
+ )
127
+ response["pollerAsync"] = (
128
+ response.get("pollerAsync") or "azure.core.polling.AsyncLROPoller"
129
+ )
130
+ if not response.get("pollingMethodSync"):
131
+ response["pollingMethodSync"] = (
132
+ "azure.mgmt.core.polling.arm_polling.ARMPolling"
133
+ if azure_arm
134
+ else "azure.core.polling.base_polling.LROBasePolling"
135
+ )
136
+ if not response.get("pollingMethodAsync"):
137
+ response["pollingMethodAsync"] = (
138
+ "azure.mgmt.core.polling.async_arm_polling.AsyncARMPolling"
139
+ if azure_arm
140
+ else "azure.core.polling.async_base_polling.AsyncLROBasePolling"
141
+ )
142
+
143
+ def update_lro_paging_operation(self, yaml_data: Dict[str, Any]) -> None:
144
+ self.update_lro_operation(yaml_data)
145
+ self.update_paging_operation(yaml_data)
146
+ for response in yaml_data.get("responses", []):
147
+ response["discriminator"] = "lropaging"
148
+
149
+ def update_lro_operation(self, yaml_data: Dict[str, Any]) -> None:
150
+ self.update_operation(yaml_data)
151
+ self._update_lro_operation_helper(yaml_data)
152
+ for overload in yaml_data["overloads"]:
153
+ self._update_lro_operation_helper(overload)
154
+
155
+ def update_paging_operation(self, yaml_data: Dict[str, Any]) -> None:
156
+ self.update_operation(yaml_data)
157
+ if not yaml_data.get("pagerSync"):
158
+ yaml_data["pagerSync"] = "azure.core.paging.ItemPaged"
159
+ if not yaml_data.get("pagerAsync"):
160
+ yaml_data["pagerAsync"] = "azure.core.async_paging.AsyncItemPaged"
161
+ returned_response_object = (
162
+ yaml_data["nextOperation"]["responses"][0]
163
+ if yaml_data.get("nextOperation")
164
+ else yaml_data["responses"][0]
165
+ )
166
+ # if we're in version tolerant, hide the paging model
167
+ if self._autorestapi.get_boolean_value("version-tolerant"):
168
+ returned_response_object["type"]["isPublic"] = False
169
+ item_type = next(
170
+ p["type"]["elementType"]
171
+ for p in returned_response_object["type"]["properties"]
172
+ if p["restApiName"] == yaml_data["itemName"]
173
+ )
174
+ if yaml_data.get("nextOperation"):
175
+ yaml_data["nextOperation"]["groupName"] = pad_reserved_words(
176
+ yaml_data["nextOperation"]["groupName"], PadType.OPERATION_GROUP
177
+ )
178
+ yaml_data["nextOperation"]["groupName"] = to_snake_case(
179
+ yaml_data["nextOperation"]["groupName"]
180
+ )
181
+ for response in yaml_data["nextOperation"].get("responses", []):
182
+ update_paging_response(response)
183
+ response["itemType"] = item_type
184
+ for response in yaml_data.get("responses", []):
185
+ update_paging_response(response)
186
+ response["itemType"] = item_type
187
+ for overload in yaml_data.get("overloads", []):
188
+ self.update_paging_operation(overload)
189
+
190
+ def update_operation_groups(self, yaml_data: Dict[str, Any]) -> None:
191
+ operation_groups_yaml_data = yaml_data["operationGroups"]
192
+ for operation_group in operation_groups_yaml_data:
193
+ operation_group["propertyName"] = pad_reserved_words(
194
+ operation_group["propertyName"], PadType.OPERATION_GROUP
195
+ )
196
+ operation_group["propertyName"] = to_snake_case(
197
+ operation_group["propertyName"]
198
+ )
199
+ operation_group["className"] = update_operation_group_class_name(
200
+ yaml_data, operation_group["className"]
201
+ )
202
+ for operation in operation_group["operations"]:
203
+ self.get_operation_updater(operation)(operation)
204
+
205
+ def update_yaml(self, yaml_data: Dict[str, Any]) -> None:
206
+ """Convert in place the YAML str."""
207
+ update_client(yaml_data["client"])
208
+ update_types(yaml_data["types"])
209
+ self.update_operation_groups(yaml_data)