@autorest/python 5.18.0 → 6.0.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 (51) hide show
  1. package/ChangeLog.md +58 -13
  2. package/README.md +9 -0
  3. package/autorest/codegen/__init__.py +24 -30
  4. package/autorest/codegen/models/base_builder.py +17 -6
  5. package/autorest/codegen/models/client.py +9 -6
  6. package/autorest/codegen/models/code_model.py +20 -14
  7. package/autorest/codegen/models/imports.py +57 -2
  8. package/autorest/codegen/models/lro_operation.py +4 -6
  9. package/autorest/codegen/models/lro_paging_operation.py +3 -9
  10. package/autorest/codegen/models/model_type.py +1 -6
  11. package/autorest/codegen/models/operation.py +47 -79
  12. package/autorest/codegen/models/operation_group.py +10 -9
  13. package/autorest/codegen/models/paging_operation.py +19 -7
  14. package/autorest/codegen/models/parameter.py +3 -7
  15. package/autorest/codegen/models/parameter_list.py +20 -36
  16. package/autorest/codegen/models/request_builder.py +31 -42
  17. package/autorest/codegen/serializers/__init__.py +12 -50
  18. package/autorest/codegen/serializers/builder_serializer.py +53 -40
  19. package/autorest/codegen/serializers/client_serializer.py +22 -31
  20. package/autorest/codegen/serializers/general_serializer.py +12 -12
  21. package/autorest/codegen/serializers/import_serializer.py +11 -22
  22. package/autorest/codegen/serializers/metadata_serializer.py +0 -2
  23. package/autorest/codegen/serializers/{model_base_serializer.py → model_serializer.py} +57 -43
  24. package/autorest/codegen/serializers/operation_groups_serializer.py +3 -7
  25. package/autorest/codegen/serializers/operations_init_serializer.py +2 -23
  26. package/autorest/codegen/serializers/patch_serializer.py +1 -3
  27. package/autorest/codegen/serializers/request_builders_serializer.py +2 -5
  28. package/autorest/codegen/serializers/utils.py +1 -4
  29. package/autorest/codegen/templates/client.py.jinja2 +6 -3
  30. package/autorest/codegen/templates/config.py.jinja2 +2 -2
  31. package/autorest/codegen/templates/metadata.json.jinja2 +4 -4
  32. package/autorest/codegen/templates/model_init.py.jinja2 +5 -12
  33. package/autorest/codegen/templates/operation_group.py.jinja2 +16 -3
  34. package/autorest/codegen/templates/operation_groups_container.py.jinja2 +2 -8
  35. package/autorest/codegen/templates/operation_tools.jinja2 +3 -1
  36. package/autorest/codegen/templates/patch.py.jinja2 +1 -2
  37. package/autorest/codegen/templates/request_builder.py.jinja2 +0 -7
  38. package/autorest/codegen/templates/request_builders.py.jinja2 +1 -4
  39. package/autorest/codegen/templates/rest_init.py.jinja2 +3 -8
  40. package/autorest/codegen/templates/serialization.py.jinja2 +2006 -0
  41. package/autorest/codegen/templates/setup.py.jinja2 +4 -0
  42. package/autorest/codegen/templates/vendor.py.jinja2 +10 -0
  43. package/autorest/m4reformatter/__init__.py +9 -3
  44. package/autorest/multiapi/models/client.py +12 -2
  45. package/autorest/multiapi/serializers/__init__.py +17 -8
  46. package/autorest/multiapi/serializers/import_serializer.py +4 -8
  47. package/autorest/multiapi/templates/multiapi_operations_mixin.py.jinja2 +1 -1
  48. package/autorest/preprocess/__init__.py +24 -4
  49. package/package.json +2 -2
  50. package/autorest/codegen/serializers/model_generic_serializer.py +0 -32
  51. package/autorest/codegen/serializers/model_python3_serializer.py +0 -72
@@ -82,7 +82,11 @@ setup(
82
82
  include_package_data=True,
83
83
  {% endif %}
84
84
  install_requires=[
85
+ {% if code_model.is_legacy %}
85
86
  "{{ dependency_msrest }}",
87
+ {% else %}
88
+ "isodate<1.0.0,>=0.6.1",
89
+ {% endif %}
86
90
  {% if azure_arm %}
87
91
  "{{ dependency_azure_mgmt_core }}",
88
92
  {% else %}
@@ -34,3 +34,13 @@ class MixinABC(ABC):
34
34
  _serialize: "Serializer"
35
35
  _deserialize: "Deserializer"
36
36
  {% endif %}
37
+ {% if code_model.has_abstract_operations %}
38
+
39
+ def raise_if_not_implemented(cls, abstract_methods):
40
+ not_implemented = [f for f in abstract_methods if not callable(getattr(cls, f, None))]
41
+ if not_implemented:
42
+ raise NotImplementedError("The following methods on operation group '{}' are not implemented: '{}'."
43
+ " Please refer to https://aka.ms/azsdk/python/dpcodegen/python/customize to learn how to customize.".format(
44
+ cls.__name__, '\', \''.join(not_implemented))
45
+ )
46
+ {% endif %}
@@ -494,6 +494,13 @@ class M4Reformatter(YamlUpdatePlugin): # pylint: disable=too-many-public-method
494
494
  in_overriden = (
495
495
  body_parameter["type"]["type"] == "combined" if body_parameter else False
496
496
  )
497
+ abstract = False
498
+ if body_parameter and (
499
+ body_parameter.get("entries")
500
+ or len(body_parameter["type"].get("types", [])) > 2
501
+ ):
502
+ # this means it's formdata or urlencoded, or there are more than 2 types of body
503
+ abstract = True
497
504
  return {
498
505
  "name": yaml_data["language"]["default"]["name"],
499
506
  "description": yaml_data["language"]["default"]["description"],
@@ -520,6 +527,7 @@ class M4Reformatter(YamlUpdatePlugin): # pylint: disable=too-many-public-method
520
527
  "discriminator": "operation",
521
528
  "isOverload": is_overload,
522
529
  "apiVersions": _get_api_versions(yaml_data.get("apiVersions", [])),
530
+ "abstract": abstract,
523
531
  }
524
532
 
525
533
  def get_operation_creator(
@@ -1072,9 +1080,7 @@ class M4Reformatter(YamlUpdatePlugin): # pylint: disable=too-many-public-method
1072
1080
  "skipUrlEncoding": True,
1073
1081
  "inOverload": False,
1074
1082
  }
1075
- if self._autorestapi.get_boolean_value(
1076
- "version-tolerant"
1077
- ) or self._autorestapi.get_boolean_value("low-level-client"):
1083
+ if self.version_tolerant or self.low_level_client:
1078
1084
  parameters.append(credential)
1079
1085
  else:
1080
1086
  parameters.insert(0, credential)
@@ -5,9 +5,10 @@
5
5
  # --------------------------------------------------------------------------
6
6
  import sys
7
7
  import json
8
+ import re
8
9
  from typing import Any, Dict, List
9
10
  from pathlib import Path
10
- from .imports import FileImport
11
+ from .imports import FileImport, TypingSection, ImportType
11
12
 
12
13
 
13
14
  def _extract_version(metadata_json: Dict[str, Any], version_path: Path) -> str:
@@ -43,9 +44,18 @@ class Client:
43
44
 
44
45
  def imports(self, async_mode: bool) -> FileImport:
45
46
  imports_to_load = "async_imports" if async_mode else "sync_imports"
46
- return FileImport(
47
+ file_import = FileImport(
47
48
  json.loads(self.default_version_metadata["client"][imports_to_load])
48
49
  )
50
+ local_imports = file_import.imports.get(TypingSection.REGULAR, {}).get(
51
+ ImportType.LOCAL, {}
52
+ )
53
+ for key in local_imports:
54
+ if re.search("^\\.*_serialization$", key):
55
+ relative_path = ".." if async_mode else "."
56
+ local_imports[f"{relative_path}_serialization"] = local_imports.pop(key)
57
+ break
58
+ return file_import
49
59
 
50
60
  @property
51
61
  def parameterized_host_template_to_api_version(self) -> Dict[str, List[str]]:
@@ -57,18 +57,14 @@ class MultiAPISerializer(object):
57
57
  )
58
58
 
59
59
  # serialize service client file
60
- imports = FileImportSerializer(
61
- code_model.client.imports(async_mode), is_python3_file=async_mode
62
- )
60
+ imports = FileImportSerializer(code_model.client.imports(async_mode))
63
61
  self._autorestapi.write_file(
64
62
  _get_file_path(code_model.client.filename, async_mode),
65
63
  _render_template("client", imports=imports),
66
64
  )
67
65
 
68
66
  # serialize config file
69
- imports = FileImportSerializer(
70
- code_model.config.imports(async_mode), is_python3_file=async_mode
71
- )
67
+ imports = FileImportSerializer(code_model.config.imports(async_mode))
72
68
  self._autorestapi.write_file(
73
69
  _get_file_path("_configuration", async_mode),
74
70
  _render_template("config", imports=imports),
@@ -77,8 +73,7 @@ class MultiAPISerializer(object):
77
73
  # serialize mixins
78
74
  if code_model.operation_mixin_group.mixin_operations:
79
75
  imports = FileImportSerializer(
80
- code_model.operation_mixin_group.imports(async_mode),
81
- is_python3_file=async_mode,
76
+ code_model.operation_mixin_group.imports(async_mode)
82
77
  )
83
78
  self._autorestapi.write_file(
84
79
  _get_file_path("_operations_mixin", async_mode),
@@ -115,3 +110,17 @@ class MultiAPISerializer(object):
115
110
  )
116
111
 
117
112
  self._autorestapi.write_file(Path("py.typed"), "# Marker file for PEP 561.")
113
+
114
+ if not code_model.client.client_side_validation:
115
+ codegen_env = Environment(
116
+ loader=PackageLoader("autorest.codegen", "templates"),
117
+ keep_trailing_newline=True,
118
+ line_statement_prefix="##",
119
+ line_comment_prefix="###",
120
+ trim_blocks=True,
121
+ lstrip_blocks=True,
122
+ )
123
+ self._autorestapi.write_file(
124
+ Path("_serialization.py"),
125
+ codegen_env.get_template("serialization.py.jinja2").render(),
126
+ )
@@ -54,9 +54,8 @@ def _get_import_clauses(
54
54
 
55
55
 
56
56
  class FileImportSerializer:
57
- def __init__(self, file_import: FileImport, is_python3_file: bool) -> None:
57
+ def __init__(self, file_import: FileImport) -> None:
58
58
  self._file_import = file_import
59
- self.is_python3_file = is_python3_file
60
59
 
61
60
  def _switch_typing_section_key(self, new_key: TypingSection):
62
61
  switched_dictionary = {}
@@ -84,10 +83,7 @@ class FileImportSerializer:
84
83
  return file_import_copy.imports.get(baseline_typing_section, {})
85
84
 
86
85
  def _add_type_checking_import(self):
87
- if self._file_import.imports.get(TypingSection.TYPING) or (
88
- not self.is_python3_file
89
- and self._file_import.imports.get(TypingSection.CONDITIONAL)
90
- ):
86
+ if self._file_import.imports.get(TypingSection.TYPING):
91
87
  self._file_import.add_submodule_import(
92
88
  "typing", "TYPE_CHECKING", ImportType.STDLIB
93
89
  )
@@ -97,7 +93,7 @@ class FileImportSerializer:
97
93
  regular_imports = ""
98
94
  regular_imports_dict = self._get_imports_dict(
99
95
  baseline_typing_section=TypingSection.REGULAR,
100
- add_conditional_typing=self.is_python3_file,
96
+ add_conditional_typing=True,
101
97
  )
102
98
 
103
99
  if regular_imports_dict:
@@ -108,7 +104,7 @@ class FileImportSerializer:
108
104
  typing_imports = ""
109
105
  typing_imports_dict = self._get_imports_dict(
110
106
  baseline_typing_section=TypingSection.TYPING,
111
- add_conditional_typing=not self.is_python3_file,
107
+ add_conditional_typing=False,
112
108
  )
113
109
  if typing_imports_dict:
114
110
  typing_imports += "\n\nif TYPE_CHECKING:\n # pylint: disable=unused-import,ungrouped-imports\n "
@@ -8,7 +8,7 @@
8
8
  # Changes may cause incorrect behavior and will be lost if the code is
9
9
  # regenerated.
10
10
  # --------------------------------------------------------------------------
11
- from msrest import Serializer, Deserializer
11
+ from {{ ".." if async_mode else "." }}_serialization import Serializer, Deserializer
12
12
  {% if imports %}
13
13
  {{ imports }}
14
14
  {% endif %}
@@ -12,6 +12,16 @@ from .python_mappings import PadType
12
12
  from .. import YamlUpdatePlugin
13
13
 
14
14
 
15
+ def _remove_paging_maxpagesize(yaml_data: Dict[str, Any]) -> None:
16
+ # we don't expose maxpagesize for version tolerant generation
17
+ # users should be passing this into `by_page`
18
+ yaml_data["parameters"] = [
19
+ p
20
+ for p in yaml_data.get("parameters", [])
21
+ if p["restApiName"].lower() not in ["maxpagesize", "$maxpagesize"]
22
+ ]
23
+
24
+
15
25
  def update_description(
16
26
  description: Optional[str], default_description: str = ""
17
27
  ) -> str:
@@ -85,6 +95,10 @@ def update_paging_response(yaml_data: Dict[str, Any]) -> None:
85
95
  class PreProcessPlugin(YamlUpdatePlugin):
86
96
  """Add Python naming information."""
87
97
 
98
+ @property
99
+ def version_tolerant(self) -> bool:
100
+ return bool(self._autorestapi.get_boolean_value("version-tolerant", True))
101
+
88
102
  def get_operation_updater(
89
103
  self, yaml_data: Dict[str, Any]
90
104
  ) -> Callable[[Dict[str, Any]], None]:
@@ -144,13 +158,16 @@ class PreProcessPlugin(YamlUpdatePlugin):
144
158
  def update_lro_paging_operation(self, yaml_data: Dict[str, Any]) -> None:
145
159
  self.update_lro_operation(yaml_data)
146
160
  self.update_paging_operation(yaml_data)
161
+ yaml_data["discriminator"] = "lropaging"
147
162
  for response in yaml_data.get("responses", []):
148
163
  response["discriminator"] = "lropaging"
164
+ for overload in yaml_data.get("overloads", []):
165
+ self.update_lro_paging_operation(overload)
149
166
 
150
167
  def update_lro_operation(self, yaml_data: Dict[str, Any]) -> None:
151
168
  self.update_operation(yaml_data)
152
169
  self._update_lro_operation_helper(yaml_data)
153
- for overload in yaml_data["overloads"]:
170
+ for overload in yaml_data.get("overloads", []):
154
171
  self._update_lro_operation_helper(overload)
155
172
 
156
173
  def update_paging_operation(self, yaml_data: Dict[str, Any]) -> None:
@@ -164,15 +181,18 @@ class PreProcessPlugin(YamlUpdatePlugin):
164
181
  if yaml_data.get("nextOperation")
165
182
  else yaml_data["responses"][0]
166
183
  )
167
- # if we're in version tolerant, hide the paging model
168
- if self._autorestapi.get_boolean_value("version-tolerant"):
184
+ if self.version_tolerant:
185
+ # if we're in version tolerant, hide the paging model
169
186
  returned_response_object["type"]["isPublic"] = False
187
+ _remove_paging_maxpagesize(yaml_data)
170
188
  item_type = next(
171
189
  p["type"]["elementType"]
172
190
  for p in returned_response_object["type"]["properties"]
173
- if p["restApiName"] == yaml_data["itemName"]
191
+ if p["restApiName"] == (yaml_data.get("itemName") or "value")
174
192
  )
175
193
  if yaml_data.get("nextOperation"):
194
+ if self.version_tolerant:
195
+ _remove_paging_maxpagesize(yaml_data["nextOperation"])
176
196
  yaml_data["nextOperation"]["groupName"] = pad_reserved_words(
177
197
  yaml_data["nextOperation"]["groupName"], PadType.OPERATION_GROUP
178
198
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autorest/python",
3
- "version": "5.18.0",
3
+ "version": "6.0.0",
4
4
  "description": "The Python extension for generators in AutoRest.",
5
5
  "scripts": {
6
6
  "prepare": "node run-python3.js prepare.py",
@@ -27,7 +27,7 @@
27
27
  "@autorest/system-requirements": "~1.0.0"
28
28
  },
29
29
  "devDependencies": {
30
- "@microsoft.azure/autorest.testserver": "^3.3.28"
30
+ "@microsoft.azure/autorest.testserver": "^3.3.31"
31
31
  },
32
32
  "files": [
33
33
  "autorest/**/*.py",
@@ -1,32 +0,0 @@
1
- # -------------------------------------------------------------------------
2
- # Copyright (c) Microsoft Corporation. All rights reserved.
3
- # Licensed under the MIT License. See License.txt in the project root for
4
- # license information.
5
- # --------------------------------------------------------------------------
6
- from typing import List
7
- from jinja2 import Environment
8
- from .model_base_serializer import ModelBaseSerializer
9
- from ..models import ModelType, CodeModel, Property
10
-
11
-
12
- class ModelGenericSerializer(ModelBaseSerializer):
13
- def __init__(self, code_model: CodeModel, env: Environment) -> None:
14
- super().__init__(code_model=code_model, env=env, is_python3_file=False)
15
-
16
- def init_line(self, model: ModelType) -> List[str]:
17
- return []
18
-
19
- def properties_to_pass_to_super(self, model: ModelType) -> str:
20
- return "**kwargs"
21
-
22
- def required_property_no_default_init(self, prop: Property) -> str:
23
- return f"self.{prop.client_name} = kwargs['{prop.client_name}']"
24
-
25
- def optional_property_init(self, prop: Property) -> str:
26
- return f"self.{prop.client_name} = kwargs.get('{prop.client_name}', {prop.client_default_value_declaration})"
27
-
28
- def initialize_standard_arg(self, prop: Property) -> str:
29
- return self.initialize_standard_property(prop)
30
-
31
- def super_call_template(self, model: ModelType) -> str:
32
- return "super(" + model.name + ", self).__init__({})"
@@ -1,72 +0,0 @@
1
- # -------------------------------------------------------------------------
2
- # Copyright (c) Microsoft Corporation. All rights reserved.
3
- # Licensed under the MIT License. See License.txt in the project root for
4
- # license information.
5
- # --------------------------------------------------------------------------
6
- from typing import List
7
- from jinja2 import Environment
8
- from .model_base_serializer import ModelBaseSerializer
9
- from ..models import ModelType, CodeModel, Property
10
- from ..models.imports import FileImport
11
-
12
-
13
- class ModelPython3Serializer(ModelBaseSerializer):
14
- def __init__(self, code_model: CodeModel, env: Environment) -> None:
15
- super().__init__(code_model=code_model, env=env, is_python3_file=True)
16
-
17
- def init_line(self, model: ModelType) -> List[str]:
18
- init_properties_declaration = []
19
- init_line_parameters = [
20
- p
21
- for p in model.properties
22
- if not p.readonly and not p.is_discriminator and not p.constant
23
- ]
24
- init_line_parameters.sort(key=lambda x: x.optional)
25
- if init_line_parameters:
26
- init_properties_declaration.append("*,")
27
- for param in init_line_parameters:
28
- init_properties_declaration.append(self.initialize_standard_property(param))
29
-
30
- return init_properties_declaration
31
-
32
- def properties_to_pass_to_super(self, model: ModelType) -> str:
33
- properties_to_pass_to_super = []
34
- for parent in model.parents:
35
- for prop in model.properties:
36
- if (
37
- prop in parent.properties
38
- and not prop.is_discriminator
39
- and not prop.constant
40
- and not prop.readonly
41
- ):
42
- properties_to_pass_to_super.append(
43
- f"{prop.client_name}={prop.client_name}"
44
- )
45
- properties_to_pass_to_super.append("**kwargs")
46
- return ", ".join(properties_to_pass_to_super)
47
-
48
- def required_property_no_default_init(self, prop: Property) -> str:
49
- return f"{prop.client_name}: {prop.type_annotation()},{prop.pylint_disable}"
50
-
51
- def optional_property_init(self, prop: Property) -> str:
52
- return (
53
- f"{prop.client_name}: {prop.type_annotation()} = "
54
- f"{prop.client_default_value_declaration},{prop.pylint_disable}"
55
- )
56
-
57
- def initialize_standard_arg(self, prop: Property) -> str:
58
- return f"self.{prop.client_name} = {prop.client_name}"
59
-
60
- def super_call_template(self, model: ModelType) -> str:
61
- return "super().__init__({})"
62
-
63
- def imports(self) -> FileImport:
64
- file_import = super(ModelPython3Serializer, self).imports()
65
- for model in self.code_model.model_types:
66
- init_line_parameters = [
67
- p for p in model.properties if not p.readonly and not p.is_discriminator
68
- ]
69
- for param in init_line_parameters:
70
- file_import.merge(param.imports())
71
-
72
- return file_import