@autorest/python 5.15.0 → 5.18.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 (118) hide show
  1. package/ChangeLog.md +98 -4
  2. package/README.md +30 -4
  3. package/autorest/__init__.py +2 -3
  4. package/autorest/black/__init__.py +12 -5
  5. package/autorest/codegen/__init__.py +122 -211
  6. package/autorest/codegen/models/__init__.py +122 -78
  7. package/autorest/codegen/models/base_builder.py +70 -72
  8. package/autorest/codegen/models/base_model.py +7 -5
  9. package/autorest/codegen/models/{base_schema.py → base_type.py} +68 -45
  10. package/autorest/codegen/models/client.py +193 -40
  11. package/autorest/codegen/models/code_model.py +145 -245
  12. package/autorest/codegen/models/combined_type.py +107 -0
  13. package/autorest/codegen/models/constant_type.py +122 -0
  14. package/autorest/codegen/models/credential_types.py +224 -0
  15. package/autorest/codegen/models/dictionary_type.py +131 -0
  16. package/autorest/codegen/models/enum_type.py +195 -0
  17. package/autorest/codegen/models/imports.py +93 -41
  18. package/autorest/codegen/models/list_type.py +149 -0
  19. package/autorest/codegen/models/lro_operation.py +90 -133
  20. package/autorest/codegen/models/lro_paging_operation.py +28 -12
  21. package/autorest/codegen/models/model_type.py +262 -0
  22. package/autorest/codegen/models/operation.py +412 -259
  23. package/autorest/codegen/models/operation_group.py +80 -91
  24. package/autorest/codegen/models/paging_operation.py +101 -117
  25. package/autorest/codegen/models/parameter.py +302 -341
  26. package/autorest/codegen/models/parameter_list.py +373 -357
  27. package/autorest/codegen/models/primitive_types.py +544 -0
  28. package/autorest/codegen/models/property.py +136 -134
  29. package/autorest/codegen/models/request_builder.py +138 -86
  30. package/autorest/codegen/models/request_builder_parameter.py +122 -86
  31. package/autorest/codegen/models/response.py +325 -0
  32. package/autorest/codegen/models/utils.py +13 -17
  33. package/autorest/codegen/serializers/__init__.py +212 -112
  34. package/autorest/codegen/serializers/builder_serializer.py +931 -1040
  35. package/autorest/codegen/serializers/client_serializer.py +140 -84
  36. package/autorest/codegen/serializers/general_serializer.py +26 -50
  37. package/autorest/codegen/serializers/import_serializer.py +96 -31
  38. package/autorest/codegen/serializers/metadata_serializer.py +39 -79
  39. package/autorest/codegen/serializers/model_base_serializer.py +62 -34
  40. package/autorest/codegen/serializers/model_generic_serializer.py +9 -10
  41. package/autorest/codegen/serializers/model_init_serializer.py +4 -2
  42. package/autorest/codegen/serializers/model_python3_serializer.py +29 -22
  43. package/autorest/codegen/serializers/operation_groups_serializer.py +21 -19
  44. package/autorest/codegen/serializers/operations_init_serializer.py +23 -11
  45. package/autorest/codegen/serializers/parameter_serializer.py +174 -0
  46. package/autorest/codegen/serializers/patch_serializer.py +4 -1
  47. package/autorest/codegen/serializers/request_builders_serializer.py +57 -0
  48. package/autorest/codegen/serializers/utils.py +0 -126
  49. package/autorest/codegen/templates/MANIFEST.in.jinja2 +1 -0
  50. package/autorest/codegen/templates/{service_client.py.jinja2 → client.py.jinja2} +7 -7
  51. package/autorest/codegen/templates/config.py.jinja2 +13 -13
  52. package/autorest/codegen/templates/enum.py.jinja2 +4 -4
  53. package/autorest/codegen/templates/enum_container.py.jinja2 +1 -1
  54. package/autorest/codegen/templates/init.py.jinja2 +3 -3
  55. package/autorest/codegen/templates/lro_operation.py.jinja2 +6 -5
  56. package/autorest/codegen/templates/lro_paging_operation.py.jinja2 +6 -5
  57. package/autorest/codegen/templates/metadata.json.jinja2 +36 -35
  58. package/autorest/codegen/templates/model.py.jinja2 +23 -24
  59. package/autorest/codegen/templates/model_container.py.jinja2 +2 -1
  60. package/autorest/codegen/templates/model_init.py.jinja2 +3 -5
  61. package/autorest/codegen/templates/operation.py.jinja2 +10 -14
  62. package/autorest/codegen/templates/operation_group.py.jinja2 +9 -15
  63. package/autorest/codegen/templates/operation_groups_container.py.jinja2 +1 -1
  64. package/autorest/codegen/templates/operation_tools.jinja2 +8 -2
  65. package/autorest/codegen/templates/paging_operation.py.jinja2 +7 -8
  66. package/autorest/codegen/templates/request_builder.py.jinja2 +19 -10
  67. package/autorest/codegen/templates/setup.py.jinja2 +9 -3
  68. package/autorest/codegen/templates/vendor.py.jinja2 +1 -1
  69. package/autorest/jsonrpc/__init__.py +7 -12
  70. package/autorest/jsonrpc/localapi.py +4 -3
  71. package/autorest/jsonrpc/server.py +28 -9
  72. package/autorest/jsonrpc/stdstream.py +13 -6
  73. package/autorest/m2r/__init__.py +5 -8
  74. package/autorest/m4reformatter/__init__.py +1126 -0
  75. package/autorest/multiapi/__init__.py +24 -14
  76. package/autorest/multiapi/models/client.py +21 -11
  77. package/autorest/multiapi/models/code_model.py +23 -10
  78. package/autorest/multiapi/models/config.py +4 -1
  79. package/autorest/multiapi/models/constant_global_parameter.py +1 -0
  80. package/autorest/multiapi/models/global_parameter.py +2 -1
  81. package/autorest/multiapi/models/global_parameters.py +14 -8
  82. package/autorest/multiapi/models/imports.py +24 -17
  83. package/autorest/multiapi/models/mixin_operation.py +5 -5
  84. package/autorest/multiapi/models/operation_group.py +2 -1
  85. package/autorest/multiapi/models/operation_mixin_group.py +21 -10
  86. package/autorest/multiapi/serializers/__init__.py +20 -25
  87. package/autorest/multiapi/serializers/import_serializer.py +47 -17
  88. package/autorest/multiapi/serializers/multiapi_serializer.py +17 -17
  89. package/autorest/multiapi/templates/multiapi_config.py.jinja2 +3 -3
  90. package/autorest/multiapi/templates/multiapi_init.py.jinja2 +2 -2
  91. package/autorest/multiapi/templates/multiapi_operations_mixin.py.jinja2 +4 -4
  92. package/autorest/multiapi/templates/multiapi_service_client.py.jinja2 +9 -9
  93. package/autorest/multiapi/utils.py +3 -3
  94. package/autorest/postprocess/__init__.py +202 -0
  95. package/autorest/postprocess/get_all.py +19 -0
  96. package/autorest/postprocess/venvtools.py +73 -0
  97. package/autorest/preprocess/__init__.py +210 -0
  98. package/autorest/preprocess/helpers.py +54 -0
  99. package/autorest/{namer → preprocess}/python_mappings.py +25 -32
  100. package/package.json +3 -3
  101. package/run-python3.js +2 -3
  102. package/venvtools.py +1 -1
  103. package/autorest/codegen/models/constant_schema.py +0 -101
  104. package/autorest/codegen/models/credential_model.py +0 -47
  105. package/autorest/codegen/models/credential_schema.py +0 -91
  106. package/autorest/codegen/models/credential_schema_policy.py +0 -77
  107. package/autorest/codegen/models/dictionary_schema.py +0 -103
  108. package/autorest/codegen/models/enum_schema.py +0 -215
  109. package/autorest/codegen/models/list_schema.py +0 -123
  110. package/autorest/codegen/models/object_schema.py +0 -253
  111. package/autorest/codegen/models/primitive_schemas.py +0 -466
  112. package/autorest/codegen/models/request_builder_parameter_list.py +0 -280
  113. package/autorest/codegen/models/rest.py +0 -42
  114. package/autorest/codegen/models/schema_request.py +0 -45
  115. package/autorest/codegen/models/schema_response.py +0 -136
  116. package/autorest/codegen/serializers/rest_serializer.py +0 -57
  117. package/autorest/namer/__init__.py +0 -25
  118. package/autorest/namer/name_converter.py +0 -412
@@ -3,154 +3,85 @@
3
3
  # Licensed under the MIT License. See License.txt in the project root for
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
- import logging
7
- from enum import Enum
8
-
9
- from typing import Dict, Optional, List, Any, Union, Tuple, cast, TYPE_CHECKING
10
-
11
- from .imports import FileImport, ImportType, TypingSection
6
+ import abc
7
+ from enum import Enum, auto
8
+
9
+ from typing import (
10
+ Dict,
11
+ Any,
12
+ TYPE_CHECKING,
13
+ List,
14
+ Optional,
15
+ TypeVar,
16
+ Union,
17
+ Generic,
18
+ )
19
+
20
+ from .imports import FileImport, ImportType
12
21
  from .base_model import BaseModel
13
- from .base_schema import BaseSchema
14
- from .constant_schema import ConstantSchema
15
- from .object_schema import ObjectSchema
16
- from .property import Property
17
- from .primitive_schemas import IOSchema
18
- from .utils import get_schema
22
+ from .base_type import BaseType
23
+ from .constant_type import ConstantType
24
+ from .utils import add_to_description
19
25
 
20
26
  if TYPE_CHECKING:
21
27
  from .code_model import CodeModel
28
+ from .request_builder_parameter import RequestBuilderBodyParameter
22
29
 
23
30
 
24
- _LOGGER = logging.getLogger(__name__)
25
-
26
- _HIDDEN_KWARGS = ["content_type"]
31
+ class ParameterLocation(str, Enum):
32
+ HEADER = "header"
33
+ PATH = "path"
34
+ ENDPOINT_PATH = "endpointPath"
35
+ QUERY = "query"
36
+ BODY = "body"
37
+ OTHER = "other"
27
38
 
28
39
 
29
- class ParameterLocation(Enum):
30
- Path = "path"
31
- Body = "body"
32
- Query = "query"
33
- Header = "header"
34
- Uri = "uri"
35
- Other = "other"
40
+ class ParameterMethodLocation(Enum):
41
+ POSITIONAL = auto()
42
+ KEYWORD_ONLY = auto()
43
+ KWARG = auto()
36
44
 
37
45
 
38
- class ParameterStyle(Enum):
39
- simple = "simple"
40
- label = "label"
41
- matrix = "matrix"
42
- form = "form"
43
- spaceDelimited = "spaceDelimited"
44
- pipeDelimited = "pipeDelimited"
45
- deepObject = "deepObject"
46
- tabDelimited = "tabDelimited"
47
- json = "json"
48
- binary = "binary"
49
- xml = "xml"
50
- multipart = "multipart"
46
+ class ParameterDelimeter(str, Enum):
47
+ SPACE = "space"
48
+ PIPE = "pipe"
49
+ TAB = "tab"
50
+ COMMA = "comma"
51
51
 
52
52
 
53
+ class _ParameterBase(
54
+ BaseModel, abc.ABC
55
+ ): # pylint: disable=too-many-instance-attributes
56
+ """Base class for all parameters"""
53
57
 
54
- def get_target_property_name(code_model: "CodeModel", target_property_id: int) -> str:
55
- for obj in code_model.schemas.values():
56
- for prop in obj.properties:
57
- if prop.id == target_property_id:
58
- return prop.name
59
- raise KeyError("Didn't find the target property")
60
-
61
-
62
- class Parameter(BaseModel): # pylint: disable=too-many-instance-attributes, too-many-public-methods
63
58
  def __init__(
64
59
  self,
65
- code_model,
66
60
  yaml_data: Dict[str, Any],
67
- schema: BaseSchema,
68
- rest_api_name: str,
69
- serialized_name: str,
70
- description: str,
71
- implementation: str,
72
- required: bool,
73
- location: ParameterLocation,
74
- skip_url_encoding: bool,
75
- constraints: List[Any],
76
- target_property_name: Optional[Union[int, str]] = None, # first uses id as placeholder
77
- style: Optional[ParameterStyle] = None,
78
- explode: Optional[bool] = False,
79
- *,
80
- flattened: bool = False,
81
- grouped_by: Optional["Parameter"] = None,
82
- original_parameter: Optional["Parameter"] = None,
83
- client_default_value: Optional[Any] = None,
84
- keyword_only: Optional[bool] = None,
85
- content_types: Optional[List[str]] = None,
61
+ code_model: "CodeModel",
62
+ type: BaseType,
86
63
  ) -> None:
87
- super().__init__(yaml_data)
88
- self.code_model = code_model
89
- self.schema = schema
90
- self.rest_api_name = rest_api_name
91
- self.serialized_name = serialized_name
92
- self._description = description
93
- self._implementation = implementation
94
- self.required = required
95
- self.location = location
96
- self.skip_url_encoding = skip_url_encoding
97
- self.constraints = constraints
98
- self.target_property_name = target_property_name
99
- self.style = style
100
- self.explode = explode
101
- self.flattened = flattened
102
- self.grouped_by = grouped_by
103
- self.original_parameter = original_parameter
104
- self.client_default_value = client_default_value
105
- self.has_multiple_content_types: bool = False
106
- self.multiple_content_types_type_annot: Optional[str] = None
107
- self.multiple_content_types_docstring_type: Optional[str] = None
108
- self._keyword_only = keyword_only
109
- self.is_multipart = yaml_data.get("language", {}).get("python", {}).get("multipart", False)
110
- self.is_data_input = yaml_data.get("isPartialBody", False) and not self.is_multipart
111
- self.content_types = content_types or []
112
- self.body_kwargs: List[Parameter] = []
113
- self.is_body_kwarg = False
114
- self.need_import = True
115
- self.is_kwarg = (self.rest_api_name == "Content-Type" or (self.constant and self.inputtable_by_user))
116
-
117
- def __hash__(self) -> int:
118
- return hash(self.serialized_name)
119
-
120
- @property
121
- def description(self):
122
- try:
123
- description = self._description
124
- if self.schema.extra_description_information:
125
- if description:
126
- description += " "
127
- description += f"{self.schema.extra_description_information}"
128
- if isinstance(self.schema, ConstantSchema) and not self.constant:
129
- if description:
130
- description += " "
131
- description += f"Known values are {self.schema.get_declaration(self.schema.value)} or {None}."
132
- if self.has_default_value and not any(
133
- l for l in ["default value is", "default is"] if l in description.lower()
134
- ):
135
- description += f" Default value is {self.default_value_declaration}."
136
- if self.constant:
137
- description += " Note that overriding this default value may result in unsupported behavior."
138
- return description
139
- except AttributeError:
140
- pass
141
- return self._description
142
-
143
- @description.setter
144
- def description(self, val: str):
145
- self._description = val
146
-
147
- @property
148
- def is_json_parameter(self) -> bool:
149
- if self.is_multipart or self.is_data_input:
150
- return False
151
- if self.style == ParameterStyle.xml:
152
- return False
153
- return True
64
+ super().__init__(yaml_data, code_model)
65
+ self.rest_api_name: str = yaml_data["restApiName"]
66
+ self.client_name: str = self.yaml_data["clientName"]
67
+ self.optional: bool = self.yaml_data["optional"]
68
+ self.location: ParameterLocation = self.yaml_data["location"]
69
+ self.client_default_value = self.yaml_data.get("clientDefaultValue", None)
70
+ self.in_docstring = self.yaml_data.get("inDocstring", True)
71
+ self.type = type
72
+ if self.client_default_value is None:
73
+ self.client_default_value = self.type.client_default_value
74
+ # name of grouper if it is grouped by another parameter
75
+ self.grouped_by: Optional[str] = self.yaml_data.get("groupedBy")
76
+ # property matching property name to parameter name for grouping params
77
+ # and flattened body params
78
+ self.property_to_parameter_name: Optional[Dict[str, str]] = self.yaml_data.get(
79
+ "propertyToParameterName"
80
+ )
81
+ self.flattened: bool = self.yaml_data.get("flattened", False)
82
+ self.in_flattened_body: bool = self.yaml_data.get("inFlattenedBody", False)
83
+ self.grouper: bool = self.yaml_data.get("grouper", False)
84
+ self.check_client_input: bool = self.yaml_data.get("checkClientInput", False)
154
85
 
155
86
  @property
156
87
  def constant(self) -> bool:
@@ -158,271 +89,301 @@ class Parameter(BaseModel): # pylint: disable=too-many-instance-attributes, too
158
89
  Checking to see if it's required, because if not, we don't consider it
159
90
  a constant because it can have a value of None.
160
91
  """
161
- if isinstance(self.schema, dict):
162
- if not self.schema.get("type") == "constant":
163
- return False
164
- else:
165
- if not isinstance(self.schema, ConstantSchema):
166
- return False
167
- return self.required
92
+ return not self.optional and isinstance(self.type, ConstantType)
168
93
 
169
94
  @property
170
- def constant_declaration(self) -> str:
171
- if self.schema:
172
- if isinstance(self.schema, ConstantSchema):
173
- return self.schema.get_declaration(self.schema.value)
174
- raise ValueError(
175
- "Trying to get constant declaration for a schema that is not ConstantSchema"
176
- )
177
- raise ValueError("Trying to get a declaration for a schema that doesn't exist")
95
+ def description(self) -> str:
96
+ base_description = self.yaml_data["description"]
97
+ type_description = self.type.description(is_operation_file=True)
98
+ if type_description:
99
+ base_description = add_to_description(base_description, type_description)
100
+ if self.optional and isinstance(self.type, ConstantType):
101
+ base_description = add_to_description(
102
+ base_description,
103
+ f"Known values are {self.type.get_declaration()} and None.",
104
+ )
105
+ if not (self.optional or self.client_default_value):
106
+ base_description = add_to_description(base_description, "Required.")
107
+ if self.client_default_value is not None:
108
+ base_description = add_to_description(
109
+ base_description,
110
+ f"Default value is {self.client_default_value_declaration}.",
111
+ )
112
+ if self.optional and self.client_default_value is None:
113
+ base_description = add_to_description(
114
+ base_description,
115
+ f"Default value is {self.client_default_value_declaration}.",
116
+ )
117
+ if self.constant:
118
+ base_description = add_to_description(
119
+ base_description,
120
+ "Note that overriding this default value may result in unsupported behavior.",
121
+ )
122
+ return base_description
178
123
 
179
124
  @property
180
- def serialization_formats(self) -> List[str]:
181
- return self.yaml_data.get('serializationFormats', [])
125
+ def client_default_value_declaration(self):
126
+ """Declaration of parameter's client default value"""
127
+ if self.client_default_value is None:
128
+ return None
129
+ return self.type.get_declaration(self.client_default_value)
182
130
 
183
- @property
184
- def xml_serialization_ctxt(self) -> str:
185
- return self.schema.xml_serialization_ctxt() or ""
131
+ def type_annotation(self, **kwargs: Any) -> str:
132
+ kwargs["is_operation_file"] = True
133
+ type_annot = self.type.type_annotation(**kwargs)
134
+ if self.optional and self.client_default_value is None:
135
+ return f"Optional[{type_annot}]"
136
+ return type_annot
186
137
 
187
- @property
188
- def is_body(self) -> bool:
189
- return self.location == ParameterLocation.Body
138
+ def docstring_text(self, **kwargs: Any) -> str:
139
+ return self.type.docstring_text(**kwargs)
190
140
 
191
- @property
192
- def inputtable_by_user(self) -> bool:
193
- return self.rest_api_name != "Accept"
141
+ def docstring_type(self, **kwargs: Any) -> str:
142
+ return self.type.docstring_type(**kwargs)
194
143
 
195
144
  @property
196
- def pre_semicolon_content_types(self) -> List[str]:
197
- """Splits on semicolon of media types and returns the first half.
198
- I.e. ["text/plain; charset=UTF-8"] -> ["text/plain"]
199
- """
200
- return [content_type.split(";")[0] for content_type in self.content_types]
145
+ def serialization_type(self) -> str:
146
+ return self.type.serialization_type
201
147
 
202
- @property
203
- def in_method_signature(self) -> bool:
204
- return not(
205
- # if not inputtable, don't put in signature
206
- not self.inputtable_by_user
207
- # If i'm not in the method code, no point in being in signature
208
- or not self.in_method_code
209
- # If I'm grouped, my grouper will be on signature, not me
210
- or self.grouped_by
211
- # If I'm body and it's flattened, I'm not either
212
- or (self.is_body and self.flattened)
148
+ def imports(self, async_mode: bool, **kwargs: Any) -> FileImport:
149
+ file_import = FileImport()
150
+ file_import.merge(
151
+ self.type.imports(is_operation_file=True, async_mode=async_mode, **kwargs)
213
152
  )
153
+ if self.optional and self.client_default_value is None:
154
+ file_import.add_submodule_import("typing", "Optional", ImportType.STDLIB)
155
+ return file_import
214
156
 
215
157
  @property
216
- def corresponding_grouped_property(self) -> Property:
217
- if not self.grouped_by:
218
- raise ValueError("Should only be calling if your parameter is grouped")
219
- try:
220
- return next(
221
- p for p in cast(ObjectSchema, self.grouped_by.schema).properties
222
- if any(op for op in p.yaml_data['originalParameter'] if id(op) == self.id)
223
- )
224
- except StopIteration:
225
- raise ValueError("There is not a corresponding grouped property for your parameter.")
158
+ def method_location(self) -> ParameterMethodLocation:
159
+ raise NotImplementedError("Please implement in children")
226
160
 
227
161
  @property
228
- def in_method_code(self) -> bool:
229
- return self.rest_api_name != '$host'
162
+ def description_keyword(self) -> str:
163
+ return (
164
+ "param"
165
+ if self.method_location == ParameterMethodLocation.POSITIONAL
166
+ else "keyword"
167
+ )
230
168
 
231
169
  @property
232
- def implementation(self) -> str:
233
- # https://github.com/Azure/autorest.modelerfour/issues/81
234
- if self.serialized_name == "api_version":
235
- return "Method"
236
- return self._implementation
170
+ def docstring_type_keyword(self) -> str:
171
+ return (
172
+ "type"
173
+ if self.method_location == ParameterMethodLocation.POSITIONAL
174
+ else "paramtype"
175
+ )
237
176
 
238
177
  @property
239
- def _is_io_json(self):
240
- return any(
241
- k for k in self.body_kwargs if k.serialized_name == "json"
242
- ) and isinstance(self.schema, IOSchema)
243
-
244
- def _default_value(self) -> Tuple[Optional[Any], str, str]:
245
- type_annot = self.multiple_content_types_type_annot or self.schema.type_annotation(is_operation_file=True)
246
- if self._is_io_json:
247
- type_annot = f"Union[{type_annot}, JSONType]"
248
- any_types = ["Any", "JSONType"]
249
- if not self.required and type_annot not in any_types and not self._is_io_json:
250
- type_annot = f"Optional[{type_annot}]"
178
+ @abc.abstractmethod
179
+ def in_method_signature(self) -> bool:
180
+ ...
251
181
 
252
- if self.client_default_value is not None:
253
- return self.client_default_value, self.schema.get_declaration(self.client_default_value), type_annot
254
-
255
- if self.multiple_content_types_type_annot:
256
- # means this parameter has multiple media types. We force default value to be None.
257
- default_value = None
258
- default_value_declaration = "None"
259
- else:
260
- if isinstance(self.schema, ConstantSchema):
261
- if (self.required or
262
- self.is_content_type or
263
- not self.code_model.options["default_optional_constants_to_none"]):
264
- default_value = self.schema.get_declaration(self.schema.value)
265
- else:
266
- default_value = None
267
- default_value_declaration = default_value
268
- else:
269
- default_value = self.schema.default_value
270
- default_value_declaration = self.schema.default_value_declaration
271
- if default_value is not None and self.required:
272
- _LOGGER.warning(
273
- "Parameter '%s' is required and has a default value, this combination is not recommended",
274
- self.rest_api_name
275
- )
182
+ def method_signature(self, is_python3_file: bool, async_mode: bool) -> str:
183
+ type_annot = self.type_annotation(async_mode=async_mode)
184
+ if is_python3_file:
185
+ if self.client_default_value is not None or self.optional:
186
+ return f"{self.client_name}: {type_annot} = {self.client_default_value_declaration},"
187
+ return f"{self.client_name}: {type_annot},"
188
+ if self.client_default_value is not None or self.optional:
189
+ return f"{self.client_name}={self.client_default_value_declaration}, # type: {type_annot}"
190
+ return f"{self.client_name}, # type: {type_annot}"
276
191
 
277
- return default_value, default_value_declaration, type_annot
278
192
 
279
- @property
280
- def description_keyword(self) -> str:
281
- return "keyword" if self.is_kwarg or self.is_keyword_only else "param"
193
+ class _BodyParameterBase(_ParameterBase):
194
+ """Base class for body parameters"""
282
195
 
283
196
  @property
284
- def docstring_type_keyword(self) -> str:
285
- return "paramtype" if self.is_kwarg or self.is_keyword_only else "type"
197
+ def is_partial_body(self) -> bool:
198
+ """Whether it's part of a bigger body parameter, i.e. a MultipartBodyParameter"""
199
+ return self.yaml_data.get("isPartialBody", False)
286
200
 
287
201
  @property
288
- def default_value(self) -> Optional[Any]:
289
- # exposing default_value because client_default_value doesn't get updated with
290
- # default values we bubble up from the schema
291
- return self._default_value()[0]
202
+ def method_location(self) -> ParameterMethodLocation:
203
+ return (
204
+ ParameterMethodLocation.KWARG
205
+ if self.constant
206
+ else ParameterMethodLocation.POSITIONAL
207
+ )
292
208
 
293
209
  @property
294
- def default_value_declaration(self) -> Optional[Any]:
295
- return self._default_value()[1]
210
+ def in_method_signature(self) -> bool:
211
+ return not (self.flattened or self.grouped_by)
296
212
 
297
- def type_annotation(self, *, is_operation_file: bool = False) -> str: # pylint: disable=unused-argument
298
- return self._default_value()[2]
299
213
 
300
- @property
301
- def serialization_type(self) -> str:
302
- return self.schema.serialization_type
214
+ class BodyParameter(_BodyParameterBase):
215
+ """Body parameter."""
303
216
 
304
217
  @property
305
- def docstring_type(self) -> str:
306
- retval = self.multiple_content_types_docstring_type or self.schema.docstring_type
307
- if self._is_io_json:
308
- retval += " or JSONType"
309
- return retval
218
+ def content_types(self) -> List[str]:
219
+ return self.yaml_data["contentTypes"]
310
220
 
311
221
  @property
312
- def has_default_value(self):
313
- return self.default_value is not None or not self.required
222
+ def default_content_type(self) -> str:
223
+ return self.yaml_data["defaultContentType"]
314
224
 
315
- def method_signature(self, is_python3_file: bool) -> str:
316
- type_annot = self.type_annotation(is_operation_file=True)
317
- if is_python3_file:
318
- if self.has_default_value:
319
- return f"{self.serialized_name}: {type_annot} = {self.default_value_declaration},"
320
- return f"{self.serialized_name}: {type_annot},"
321
- if self.has_default_value:
322
- return f"{self.serialized_name}={self.default_value_declaration}, # type: {type_annot}"
323
- return f"{self.serialized_name}, # type: {type_annot}"
225
+ @classmethod
226
+ def from_yaml(
227
+ cls, yaml_data: Dict[str, Any], code_model: "CodeModel"
228
+ ) -> "BodyParameter":
229
+ return cls(
230
+ yaml_data=yaml_data,
231
+ code_model=code_model,
232
+ type=code_model.lookup_type(id(yaml_data["type"])),
233
+ )
324
234
 
325
- @property
326
- def full_serialized_name(self) -> str:
327
- origin_name = self.serialized_name
328
- if self.implementation == "Client":
329
- origin_name = f"self._config.{self.serialized_name}"
330
- return origin_name
331
235
 
332
- @property
333
- def is_keyword_only(self) -> bool:
334
- # this means in async mode, I am documented like def hello(positional_1, *, me!)
335
- return self._keyword_only or False
236
+ EntryBodyParameterType = TypeVar(
237
+ "EntryBodyParameterType", bound=Union[BodyParameter, "RequestBuilderBodyParameter"]
238
+ )
336
239
 
337
- @is_keyword_only.setter
338
- def is_keyword_only(self, val: bool) -> None:
339
- self._keyword_only = val
340
- self.is_kwarg = False
341
240
 
342
- @property
343
- def is_hidden(self) -> bool:
344
- return self.serialized_name in _HIDDEN_KWARGS and self.is_kwarg or (
345
- self.yaml_data["implementation"] == "Client" and self.constant
346
- )
241
+ class _MultipartBodyParameter(Generic[EntryBodyParameterType], BodyParameter):
242
+ """Base class for MultipartBodyParameter and RequestBuilderMultipartBodyParameter"""
347
243
 
348
- @property
349
- def is_content_type(self) -> bool:
350
- return self.rest_api_name == "Content-Type" and self.location == ParameterLocation.Header
244
+ def __init__(
245
+ self,
246
+ yaml_data: Dict[str, Any],
247
+ code_model: "CodeModel",
248
+ type: BaseType,
249
+ entries: List[EntryBodyParameterType],
250
+ ) -> None:
251
+ super().__init__(yaml_data, code_model, type)
252
+ self.entries = entries
351
253
 
352
254
  @property
353
- def is_positional(self) -> bool:
354
- return self.in_method_signature and not (self.is_keyword_only or self.is_kwarg)
255
+ def in_method_signature(self) -> bool:
256
+ # Right now, only legacy generates with multipart bodies
257
+ # and legacy generates with the multipart body arguments splatted out
258
+ return False
259
+
260
+
261
+ class MultipartBodyParameter(
262
+ _MultipartBodyParameter[BodyParameter] # pylint: disable=unsubscriptable-object
263
+ ):
264
+ """Multipart body parameter for Operation. Used for files and data input."""
355
265
 
356
266
  @classmethod
357
267
  def from_yaml(
358
- cls,
268
+ cls, yaml_data: Dict[str, Any], code_model: "CodeModel"
269
+ ) -> "MultipartBodyParameter":
270
+ return cls(
271
+ yaml_data=yaml_data,
272
+ code_model=code_model,
273
+ type=code_model.lookup_type(id(yaml_data["type"])),
274
+ entries=[
275
+ BodyParameter.from_yaml(entry, code_model)
276
+ for entry in yaml_data["entries"]
277
+ ],
278
+ )
279
+
280
+
281
+ class Parameter(_ParameterBase):
282
+ """Basic Parameter class"""
283
+
284
+ def __init__(
285
+ self,
359
286
  yaml_data: Dict[str, Any],
360
- *,
361
- code_model,
362
- content_types: Optional[List[str]] = None
363
- ) -> "Parameter":
364
- http_protocol = yaml_data["protocol"].get("http", {"in": ParameterLocation.Other})
365
- serialized_name = yaml_data["language"]["python"]["name"]
366
- schema = get_schema(
367
- code_model, yaml_data.get("schema"), serialized_name
287
+ code_model: "CodeModel",
288
+ type: BaseType,
289
+ ) -> None:
290
+ super().__init__(yaml_data, code_model, type=type)
291
+
292
+ self.implementation: str = yaml_data["implementation"]
293
+ self.skip_url_encoding: bool = self.yaml_data.get("skipUrlEncoding", False)
294
+ self.explode: bool = self.yaml_data.get("explode", False)
295
+ self.in_overload: bool = self.yaml_data["inOverload"]
296
+ self.in_overriden: bool = self.yaml_data.get("inOverriden", False)
297
+ self.delimiter: Optional[ParameterDelimeter] = self.yaml_data.get("delimiter")
298
+
299
+ @property
300
+ def in_method_signature(self) -> bool:
301
+ return not (self.rest_api_name == "Accept" or self.grouped_by or self.flattened)
302
+
303
+ @property
304
+ def full_client_name(self) -> str:
305
+ if self.implementation == "Client":
306
+ return f"self._config.{self.client_name}"
307
+ return self.client_name
308
+
309
+ @property
310
+ def xml_serialization_ctxt(self) -> str:
311
+ return self.type.xml_serialization_ctxt or ""
312
+
313
+ @property
314
+ def method_location(self) -> ParameterMethodLocation:
315
+ if not self.in_method_signature:
316
+ raise ValueError(f"Parameter '{self.client_name}' is not in the method.")
317
+ if self.grouper:
318
+ return ParameterMethodLocation.POSITIONAL
319
+ if self.constant:
320
+ return ParameterMethodLocation.KWARG
321
+ if self.rest_api_name == "Content-Type":
322
+ if self.in_overload:
323
+ return ParameterMethodLocation.KEYWORD_ONLY
324
+ return ParameterMethodLocation.KWARG
325
+ query_or_header = self.location in (
326
+ ParameterLocation.HEADER,
327
+ ParameterLocation.QUERY,
368
328
  )
369
- target_property = yaml_data.get("targetProperty")
370
- target_property_name = get_target_property_name(code_model, id(target_property)) if target_property else None
329
+ if (
330
+ self.code_model.options["only_path_and_body_params_positional"]
331
+ and query_or_header
332
+ ):
333
+ return ParameterMethodLocation.KEYWORD_ONLY
334
+ return ParameterMethodLocation.POSITIONAL
371
335
 
336
+ @classmethod
337
+ def from_yaml(cls, yaml_data: Dict[str, Any], code_model: "CodeModel"):
372
338
  return cls(
373
- code_model=code_model,
374
339
  yaml_data=yaml_data,
375
- schema=schema, # FIXME replace by operation model
376
- # See also https://github.com/Azure/autorest.modelerfour/issues/80
377
- rest_api_name=yaml_data["language"]["default"].get(
378
- "serializedName", yaml_data["language"]["default"]["name"]
379
- ),
380
- serialized_name=serialized_name,
381
- description=yaml_data["language"]["python"]["description"],
382
- implementation=yaml_data["implementation"],
383
- required=yaml_data.get("required", False),
384
- location=ParameterLocation(http_protocol["in"]),
385
- skip_url_encoding=yaml_data.get("extensions", {}).get("x-ms-skip-url-encoding", False),
386
- constraints=[], # FIXME constraints
387
- target_property_name=target_property_name,
388
- style=ParameterStyle(http_protocol["style"]) if "style" in http_protocol else None,
389
- explode=http_protocol.get("explode", False),
390
- grouped_by=yaml_data.get("groupedBy", None),
391
- original_parameter=yaml_data.get("originalParameter", None),
392
- flattened=yaml_data.get("flattened", False),
393
- client_default_value=yaml_data.get("clientDefaultValue"),
394
- content_types=content_types
340
+ code_model=code_model,
341
+ type=code_model.lookup_type(id(yaml_data["type"])),
395
342
  )
396
343
 
397
- def imports(self) -> FileImport:
398
- file_import = self.schema.imports()
399
- if not self.required:
400
- file_import.add_submodule_import("typing", "Optional", ImportType.STDLIB, TypingSection.CONDITIONAL)
401
- if self.has_multiple_content_types or self._is_io_json:
402
- file_import.add_submodule_import("typing", "Union", ImportType.STDLIB, TypingSection.CONDITIONAL)
403
344
 
404
- return file_import
345
+ class ClientParameter(Parameter):
346
+ """Client parameter"""
405
347
 
406
- class ParameterOnlyPathAndBodyPositional(Parameter):
348
+ @property
349
+ def is_host(self) -> bool:
350
+ return self.rest_api_name == "$host"
407
351
 
408
352
  @property
409
- def is_keyword_only(self) -> bool:
410
- if self._keyword_only is not None:
411
- return self._keyword_only
412
- return self.in_method_signature and not (
413
- self.is_hidden or
414
- self.location == ParameterLocation.Path or
415
- self.location == ParameterLocation.Uri or
416
- self.location == ParameterLocation.Body or
417
- self.is_kwarg
418
- )
353
+ def method_location(self) -> ParameterMethodLocation:
354
+ if self.constant:
355
+ return ParameterMethodLocation.KWARG
356
+ if self.is_host and (
357
+ self.code_model.options["version_tolerant"]
358
+ or self.code_model.options["low_level_client"]
359
+ ):
360
+ # this means i am the base url
361
+ return ParameterMethodLocation.KEYWORD_ONLY
362
+ return ParameterMethodLocation.POSITIONAL
363
+
419
364
 
420
- @is_keyword_only.setter
421
- def is_keyword_only(self, val: bool) -> None:
422
- self._keyword_only = val
423
- self.is_kwarg = False
365
+ class ConfigParameter(Parameter):
366
+ """Config Parameter"""
424
367
 
425
- def get_parameter(code_model):
426
- if code_model.options["only_path_and_body_params_positional"]:
427
- return ParameterOnlyPathAndBodyPositional
428
- return Parameter
368
+ @property
369
+ def in_method_signature(self) -> bool:
370
+ return not self.is_host
371
+
372
+ @property
373
+ def is_host(self) -> bool:
374
+ return self.rest_api_name == "$host"
375
+
376
+ @property
377
+ def method_location(self) -> ParameterMethodLocation:
378
+ if self.constant:
379
+ return ParameterMethodLocation.KWARG
380
+ return ParameterMethodLocation.POSITIONAL
381
+
382
+
383
+ def get_body_parameter(
384
+ yaml_data: Dict[str, Any], code_model: "CodeModel"
385
+ ) -> Union[BodyParameter, MultipartBodyParameter]:
386
+ """Creates a regular body parameter or Multipart body parameter"""
387
+ if yaml_data.get("entries"):
388
+ return MultipartBodyParameter.from_yaml(yaml_data, code_model)
389
+ return BodyParameter.from_yaml(yaml_data, code_model)