@autorest/python 6.12.3 → 6.12.4

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.
@@ -73,7 +73,7 @@ def parse_args(
73
73
  dest="debug",
74
74
  help="Debug mode",
75
75
  required=False,
76
- action="store_true",
76
+ action="store",
77
77
  )
78
78
  args, unknown_args = parser.parse_known_args()
79
79
 
@@ -43,7 +43,6 @@ from .parameter import (
43
43
  ParameterLocation,
44
44
  BodyParameter,
45
45
  ParameterDelimeter,
46
- MultipartBodyParameter,
47
46
  ClientParameter,
48
47
  ConfigParameter,
49
48
  )
@@ -115,7 +114,6 @@ __all__ = [
115
114
  "BodyParameter",
116
115
  "RequestBuilderBodyParameter",
117
116
  "ParameterDelimeter",
118
- "MultipartBodyParameter",
119
117
  "CredentialType",
120
118
  "ClientParameter",
121
119
  "ConfigParameter",
@@ -186,3 +186,7 @@ class BaseType(BaseModel, ABC): # pylint: disable=too-many-public-methods
186
186
  @property
187
187
  def type_description(self) -> str:
188
188
  return self.type_annotation()
189
+
190
+ @property
191
+ def is_form_data(self) -> bool:
192
+ return False
@@ -79,6 +79,10 @@ class CombinedType(BaseType):
79
79
  pattern = re.compile(r"Union\[.*\]")
80
80
  return f'Union[{", ".join(map(lambda x: x[6: -1] if pattern.match(x) else x, inside_types))}]'
81
81
 
82
+ @property
83
+ def is_form_data(self) -> bool:
84
+ return any(t.is_form_data for t in self.types)
85
+
82
86
  def get_json_template_representation(
83
87
  self,
84
88
  *,
@@ -76,6 +76,10 @@ class ModelType( # pylint: disable=abstract-method
76
76
  self.snake_case_name: str = self.yaml_data["snakeCaseName"]
77
77
  self.page_result_model: bool = self.yaml_data.get("pageResultModel", False)
78
78
 
79
+ @property
80
+ def is_form_data(self) -> bool:
81
+ return any(p.is_multipart_file_input for p in self.properties)
82
+
79
83
  @property
80
84
  def is_xml(self) -> bool:
81
85
  return self.yaml_data.get("isXml", False)
@@ -314,6 +318,15 @@ class GeneratedModelType(ModelType): # pylint: disable=abstract-method
314
318
  if kwargs.get("model_typing")
315
319
  else TypingSection.REGULAR,
316
320
  )
321
+ if self.is_form_data:
322
+ file_import.add_submodule_import(
323
+ relative_path,
324
+ "_model_base",
325
+ ImportType.LOCAL,
326
+ typing_section=TypingSection.TYPING
327
+ if kwargs.get("model_typing")
328
+ else TypingSection.REGULAR,
329
+ )
317
330
  return file_import
318
331
 
319
332
 
@@ -30,7 +30,6 @@ from .response import (
30
30
  )
31
31
  from .parameter import (
32
32
  BodyParameter,
33
- MultipartBodyParameter,
34
33
  Parameter,
35
34
  ParameterLocation,
36
35
  )
@@ -290,7 +289,6 @@ class OperationBase( # pylint: disable=too-many-public-methods
290
289
  Parameter,
291
290
  RequestBuilderParameter,
292
291
  BodyParameter,
293
- MultipartBodyParameter,
294
292
  ]
295
293
  ],
296
294
  location: ParameterLocation,
@@ -575,33 +573,13 @@ class Operation(OperationBase[Response]):
575
573
  relative_path = "..." if async_mode else ".."
576
574
  if self.code_model.options["models_mode"] == "dpg":
577
575
  if self.parameters.has_body:
578
- if not self.parameters.body_parameter.is_form_data:
576
+ if not self.has_form_data_body:
579
577
  file_import.add_submodule_import(
580
578
  f"{relative_path}_model_base",
581
579
  "SdkJSONEncoder",
582
580
  ImportType.LOCAL,
583
581
  )
584
582
  file_import.add_import("json", ImportType.STDLIB)
585
- else:
586
- file_import.add_submodule_import(
587
- relative_path, "_model_base", ImportType.LOCAL
588
- )
589
- file_import.add_submodule_import("io", "IOBase", ImportType.STDLIB)
590
- file_import.add_submodule_import(
591
- f"{relative_path}_vendor",
592
- "multipart_file",
593
- ImportType.LOCAL,
594
- )
595
- file_import.add_submodule_import(
596
- f"{relative_path}_vendor",
597
- "multipart_data",
598
- ImportType.LOCAL,
599
- )
600
- file_import.add_submodule_import(
601
- f"{relative_path}_vendor",
602
- "handle_multipart_form_data_model",
603
- ImportType.LOCAL,
604
- )
605
583
  if self.default_error_deserialization or any(
606
584
  r.type for r in self.responses
607
585
  ):
@@ -14,7 +14,6 @@ from typing import (
14
14
  Optional,
15
15
  TypeVar,
16
16
  Union,
17
- Generic,
18
17
  )
19
18
 
20
19
  from .imports import FileImport, ImportType, TypingSection
@@ -243,9 +242,18 @@ class _ParameterBase(
243
242
  class BodyParameter(_ParameterBase):
244
243
  """Body parameter."""
245
244
 
245
+ @property
246
+ def entries(self) -> List["BodyParameter"]:
247
+ return [
248
+ BodyParameter.from_yaml(e, self.code_model)
249
+ for e in self.yaml_data.get("entries", [])
250
+ ]
251
+
246
252
  @property
247
253
  def is_form_data(self) -> bool:
248
- return self.default_content_type == "multipart/form-data"
254
+ # hacky, but rn in legacy, there is no formdata model type, it's just a dict
255
+ # with all of the entries splatted out
256
+ return self.type.is_form_data or bool(self.entries)
249
257
 
250
258
  @property
251
259
  def is_partial_body(self) -> bool:
@@ -262,6 +270,10 @@ class BodyParameter(_ParameterBase):
262
270
 
263
271
  @property
264
272
  def in_method_signature(self) -> bool:
273
+ if self.yaml_data.get("entries"):
274
+ # Right now, only legacy generates with multipart bodies and entries
275
+ # and legacy generates with the multipart body arguments splatted out
276
+ return False
265
277
  return not (self.flattened or self.grouped_by)
266
278
 
267
279
  @property
@@ -278,6 +290,18 @@ class BodyParameter(_ParameterBase):
278
290
  return self.type.target_model_subtype((JSONModelType,)) is not None
279
291
  return isinstance(self.type, JSONModelType)
280
292
 
293
+ def imports(self, async_mode: bool, **kwargs: Any) -> FileImport:
294
+ file_import = super().imports(async_mode, **kwargs)
295
+ if self.is_form_data:
296
+ relative_path = "..." if async_mode else ".."
297
+ file_import.add_submodule_import(
298
+ f"{relative_path}_vendor",
299
+ "prepare_multipart_form_data",
300
+ ImportType.LOCAL,
301
+ )
302
+ file_import.add_submodule_import("typing", "List", ImportType.STDLIB)
303
+ return file_import
304
+
281
305
  @classmethod
282
306
  def from_yaml(
283
307
  cls, yaml_data: Dict[str, Any], code_model: "CodeModel"
@@ -294,46 +318,6 @@ EntryBodyParameterType = TypeVar(
294
318
  )
295
319
 
296
320
 
297
- class _MultipartBodyParameter(Generic[EntryBodyParameterType], BodyParameter):
298
- """Base class for MultipartBodyParameter and RequestBuilderMultipartBodyParameter"""
299
-
300
- def __init__(
301
- self,
302
- yaml_data: Dict[str, Any],
303
- code_model: "CodeModel",
304
- type: BaseType,
305
- entries: List[EntryBodyParameterType],
306
- ) -> None:
307
- super().__init__(yaml_data, code_model, type)
308
- self.entries = entries
309
-
310
- @property
311
- def in_method_signature(self) -> bool:
312
- # Right now, only legacy generates with multipart bodies
313
- # and legacy generates with the multipart body arguments splatted out
314
- return False
315
-
316
-
317
- class MultipartBodyParameter(
318
- _MultipartBodyParameter[BodyParameter] # pylint: disable=unsubscriptable-object
319
- ):
320
- """Multipart body parameter for Operation. Used for files and data input."""
321
-
322
- @classmethod
323
- def from_yaml(
324
- cls, yaml_data: Dict[str, Any], code_model: "CodeModel"
325
- ) -> "MultipartBodyParameter":
326
- return cls(
327
- yaml_data=yaml_data,
328
- code_model=code_model,
329
- type=code_model.lookup_type(id(yaml_data["type"])),
330
- entries=[
331
- BodyParameter.from_yaml(entry, code_model)
332
- for entry in yaml_data["entries"]
333
- ],
334
- )
335
-
336
-
337
321
  class Parameter(_ParameterBase):
338
322
  """Basic Parameter class"""
339
323
 
@@ -455,12 +439,3 @@ class ConfigParameter(Parameter):
455
439
  if self.constant:
456
440
  return ParameterMethodLocation.KWARG
457
441
  return ParameterMethodLocation.POSITIONAL
458
-
459
-
460
- def get_body_parameter(
461
- yaml_data: Dict[str, Any], code_model: "CodeModel"
462
- ) -> Union[BodyParameter, MultipartBodyParameter]:
463
- """Creates a regular body parameter or Multipart body parameter"""
464
- if yaml_data.get("entries"):
465
- return MultipartBodyParameter.from_yaml(yaml_data, code_model)
466
- return BodyParameter.from_yaml(yaml_data, code_model)
@@ -22,19 +22,15 @@ from enum import Enum
22
22
 
23
23
  from .request_builder_parameter import (
24
24
  RequestBuilderBodyParameter,
25
- RequestBuilderMultipartBodyParameter,
26
25
  RequestBuilderParameter,
27
- get_request_body_parameter,
28
26
  )
29
27
  from .parameter import (
30
- MultipartBodyParameter,
31
28
  ParameterLocation,
32
29
  BodyParameter,
33
30
  Parameter,
34
31
  ParameterMethodLocation,
35
32
  ClientParameter,
36
33
  ConfigParameter,
37
- get_body_parameter,
38
34
  )
39
35
 
40
36
  ParameterType = TypeVar(
@@ -43,10 +39,6 @@ ParameterType = TypeVar(
43
39
  BodyParameterType = TypeVar(
44
40
  "BodyParameterType", bound=Union[BodyParameter, RequestBuilderBodyParameter]
45
41
  )
46
- RequestBuilderBodyParameterType = Union[
47
- RequestBuilderBodyParameter, RequestBuilderMultipartBodyParameter
48
- ]
49
-
50
42
 
51
43
  if TYPE_CHECKING:
52
44
  from .code_model import CodeModel
@@ -314,7 +306,7 @@ class _ParameterListBase(
314
306
 
315
307
  class _ParameterList(
316
308
  _ParameterListBase[ # pylint: disable=unsubscriptable-object
317
- Parameter, Union[MultipartBodyParameter, BodyParameter]
309
+ Parameter, BodyParameter
318
310
  ]
319
311
  ):
320
312
  """Base Parameter class for the two operation ParameterLists"""
@@ -325,11 +317,9 @@ class _ParameterList(
325
317
 
326
318
  @staticmethod
327
319
  def body_parameter_creator() -> (
328
- Callable[
329
- [Dict[str, Any], "CodeModel"], Union[MultipartBodyParameter, BodyParameter]
330
- ]
320
+ Callable[[Dict[str, Any], "CodeModel"], BodyParameter]
331
321
  ):
332
- return get_body_parameter
322
+ return BodyParameter.from_yaml
333
323
 
334
324
  @property
335
325
  def implementation(self) -> str:
@@ -348,7 +338,7 @@ class ParameterList(_ParameterList):
348
338
 
349
339
  class _RequestBuilderParameterList(
350
340
  _ParameterListBase[ # pylint: disable=unsubscriptable-object
351
- RequestBuilderParameter, RequestBuilderBodyParameterType
341
+ RequestBuilderParameter, RequestBuilderBodyParameter
352
342
  ]
353
343
  ):
354
344
  """_RequestBuilderParameterList is base parameter list for RequestBuilder classes"""
@@ -361,9 +351,9 @@ class _RequestBuilderParameterList(
361
351
 
362
352
  @staticmethod
363
353
  def body_parameter_creator() -> (
364
- Callable[[Dict[str, Any], "CodeModel"], RequestBuilderBodyParameterType]
354
+ Callable[[Dict[str, Any], "CodeModel"], RequestBuilderBodyParameter]
365
355
  ):
366
- return get_request_body_parameter
356
+ return RequestBuilderBodyParameter.from_yaml
367
357
 
368
358
  @property
369
359
  def implementation(self) -> str:
@@ -372,14 +362,14 @@ class _RequestBuilderParameterList(
372
362
  @property
373
363
  def unsorted_method_params(
374
364
  self,
375
- ) -> List[Union[RequestBuilderParameter, RequestBuilderBodyParameterType]]:
365
+ ) -> List[Union[RequestBuilderParameter, RequestBuilderBodyParameter]]:
376
366
  # don't have access to client params in request builder
377
367
  retval = [
378
368
  p
379
369
  for p in super().unsorted_method_params
380
370
  if not (
381
371
  p.location == ParameterLocation.BODY
382
- and cast(RequestBuilderBodyParameterType, p).is_partial_body
372
+ and cast(RequestBuilderBodyParameter, p).is_partial_body
383
373
  )
384
374
  ]
385
375
  retval.extend(
@@ -400,7 +390,7 @@ class _RequestBuilderParameterList(
400
390
  @property
401
391
  def constant(
402
392
  self,
403
- ) -> List[Union[RequestBuilderParameter, RequestBuilderBodyParameterType]]:
393
+ ) -> List[Union[RequestBuilderParameter, RequestBuilderBodyParameter]]:
404
394
  """All constant parameters"""
405
395
  return [
406
396
  p for p in super().constant if p.location != ParameterLocation.ENDPOINT_PATH
@@ -37,6 +37,9 @@ class Property(BaseModel): # pylint: disable=too-many-instance-attributes
37
37
  if self.client_default_value is None:
38
38
  self.client_default_value = self.type.client_default_value
39
39
  self.flattened_names: List[str] = yaml_data.get("flattenedNames", [])
40
+ self.is_multipart_file_input: bool = yaml_data.get(
41
+ "isMultipartFileInput", False
42
+ )
40
43
 
41
44
  @property
42
45
  def pylint_disable(self) -> str:
@@ -94,12 +97,18 @@ class Property(BaseModel): # pylint: disable=too-many-instance-attributes
94
97
  return self.is_discriminator and self.type.type == "enum"
95
98
 
96
99
  def type_annotation(self, *, is_operation_file: bool = False) -> str:
100
+ types_type_annotation = self.type.type_annotation(
101
+ is_operation_file=is_operation_file
102
+ )
103
+ if self.is_multipart_file_input:
104
+ # we only support FileType or list of FileType
105
+ types_type_annotation = types_type_annotation.replace("bytes", "FileType")
97
106
  if self.is_enum_discriminator:
98
107
  # here we are the enum discriminator property on the base model
99
108
  return "Literal[None]"
100
109
  if self.optional and self.client_default_value is None:
101
- return f"Optional[{self.type.type_annotation(is_operation_file=is_operation_file)}]"
102
- return self.type.type_annotation(is_operation_file=is_operation_file)
110
+ return f"Optional[{types_type_annotation}]"
111
+ return types_type_annotation
103
112
 
104
113
  def get_declaration(self, value: Any = None) -> Any:
105
114
  if self.is_enum_discriminator:
@@ -114,6 +123,8 @@ class Property(BaseModel): # pylint: disable=too-many-instance-attributes
114
123
  client_default_value_declaration: Optional[str] = None,
115
124
  description: Optional[str] = None,
116
125
  ) -> Any:
126
+ if self.is_multipart_file_input:
127
+ return "[filetype]" if self.type.type == "list" else "filetype"
117
128
  if self.client_default_value:
118
129
  client_default_value_declaration = self.get_declaration(
119
130
  self.client_default_value
@@ -161,6 +172,8 @@ class Property(BaseModel): # pylint: disable=too-many-instance-attributes
161
172
  "rest_discriminator" if self.is_discriminator else "rest_field",
162
173
  ImportType.LOCAL,
163
174
  )
175
+ if self.is_multipart_file_input:
176
+ file_import.add_submodule_import(".._vendor", "FileType", ImportType.LOCAL)
164
177
  return file_import
165
178
 
166
179
  @classmethod
@@ -3,13 +3,12 @@
3
3
  # Licensed under the MIT License. See License.txt in the project root for
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
- from typing import TYPE_CHECKING, Any, Dict, Union
6
+ from typing import TYPE_CHECKING, Any, Dict
7
7
  from .parameter import (
8
8
  ParameterLocation,
9
9
  ParameterMethodLocation,
10
10
  Parameter,
11
11
  BodyParameter,
12
- _MultipartBodyParameter,
13
12
  )
14
13
  from .base import BaseType
15
14
  from .primitive_types import BinaryType, StringType
@@ -67,32 +66,6 @@ class RequestBuilderBodyParameter(BodyParameter):
67
66
  return "_content"
68
67
 
69
68
 
70
- class RequestBuilderMultipartBodyParameter(
71
- _MultipartBodyParameter[ # pylint: disable=unsubscriptable-object
72
- RequestBuilderBodyParameter
73
- ]
74
- ):
75
- """Multipart body parameter for Request BUilders"""
76
-
77
- @property
78
- def name_in_high_level_operation(self) -> str:
79
- return f"_{self.client_name}"
80
-
81
- @classmethod
82
- def from_yaml(
83
- cls, yaml_data: Dict[str, Any], code_model: "CodeModel"
84
- ) -> "RequestBuilderMultipartBodyParameter":
85
- return cls(
86
- yaml_data=yaml_data,
87
- code_model=code_model,
88
- type=code_model.lookup_type(id(yaml_data["type"])),
89
- entries=[
90
- RequestBuilderBodyParameter.from_yaml(entry, code_model)
91
- for entry in yaml_data["entries"]
92
- ],
93
- )
94
-
95
-
96
69
  class RequestBuilderParameter(Parameter):
97
70
  """Basic RequestBuilder Parameter."""
98
71
 
@@ -149,12 +122,3 @@ class RequestBuilderParameter(Parameter):
149
122
  if self.implementation == "Client":
150
123
  return f"self._config.{self.client_name}"
151
124
  return self.client_name
152
-
153
-
154
- def get_request_body_parameter(
155
- yaml_data: Dict[str, Any], code_model: "CodeModel"
156
- ) -> Union[RequestBuilderBodyParameter, RequestBuilderMultipartBodyParameter]:
157
- """Get body parameter for a request builder"""
158
- if yaml_data.get("entries"):
159
- return RequestBuilderMultipartBodyParameter.from_yaml(yaml_data, code_model)
160
- return RequestBuilderBodyParameter.from_yaml(yaml_data, code_model)
@@ -26,7 +26,6 @@ from ..models import (
26
26
  ParameterMethodLocation,
27
27
  RequestBuilderBodyParameter,
28
28
  OverloadedRequestBuilder,
29
- MultipartBodyParameter,
30
29
  Property,
31
30
  RequestBuilderType,
32
31
  CombinedType,
@@ -207,7 +206,7 @@ def _serialize_json_model_body(
207
206
 
208
207
  def _serialize_multipart_body(builder: BuilderType) -> List[str]:
209
208
  retval: List[str] = []
210
- body_param = cast(MultipartBodyParameter, builder.parameters.body_parameter)
209
+ body_param = builder.parameters.body_parameter
211
210
  # we have to construct our form data before passing to the request as well
212
211
  retval.append("# Construct form data")
213
212
  retval.append(f"_{body_param.client_name} = {{")
@@ -349,7 +348,11 @@ class _BuilderBaseSerializer(Generic[BuilderType]): # pylint: disable=abstract-
349
348
  def param_description(self, builder: BuilderType) -> List[str]:
350
349
  description_list: List[str] = []
351
350
  for param in builder.parameters.method:
352
- if not param.in_docstring or param.hide_in_operation_signature:
351
+ if (
352
+ not param.in_docstring
353
+ or param.hide_in_operation_signature
354
+ or param.method_location == ParameterMethodLocation.KWARG
355
+ ):
353
356
  continue
354
357
  description_list.extend(
355
358
  f":{param.description_keyword} {param.client_name}: {param.description}".replace(
@@ -748,17 +751,39 @@ class _OperationSerializer(
748
751
 
749
752
  This function serializes the body params that need to be serialized.
750
753
  """
751
- body_param = cast(BodyParameter, builder.parameters.body_parameter)
754
+ retval: List[str] = []
755
+ body_param = builder.parameters.body_parameter
752
756
  if body_param.is_form_data:
753
- return [
754
- f"if isinstance({body_param.client_name}, _model_base.Model):",
755
- f" _body = handle_multipart_form_data_model({body_param.client_name})",
756
- "else:",
757
- f" _body = {body_param.client_name}",
758
- "_files = {k: multipart_file(v) for k, v in _body.items() if isinstance(v, (IOBase, bytes))}",
759
- "_data = {k: multipart_data(v) for k, v in _body.items() if not isinstance(v, (IOBase, bytes))}",
757
+ model_type = cast(
758
+ ModelType,
759
+ (
760
+ body_param.type.target_model_subtype((JSONModelType, DPGModelType))
761
+ if isinstance(body_param.type, CombinedType)
762
+ else body_param.type
763
+ ),
764
+ )
765
+ file_fields = [
766
+ p.wire_name for p in model_type.properties if p.is_multipart_file_input
760
767
  ]
761
- retval: List[str] = []
768
+ data_fields = [
769
+ p.wire_name
770
+ for p in model_type.properties
771
+ if not p.is_multipart_file_input
772
+ ]
773
+ retval.extend(
774
+ [
775
+ "_body = (",
776
+ f" {body_param.client_name}.as_dict()",
777
+ f" if isinstance({body_param.client_name}, _model_base.Model) else",
778
+ f" {body_param.client_name}",
779
+ ")",
780
+ f"_file_fields: List[str] = {file_fields}",
781
+ f"_data_fields: List[str] = {data_fields}",
782
+ "_files, _data = prepare_multipart_form_data(_body, _file_fields, _data_fields)",
783
+ ]
784
+ )
785
+ return retval
786
+
762
787
  body_kwarg_name = builder.request_builder.parameters.body_parameter.client_name
763
788
  send_xml = builder.parameters.body_parameter.type.is_xml
764
789
  xml_serialization_ctxt = (
@@ -810,8 +835,8 @@ class _OperationSerializer(
810
835
  ) -> List[str]:
811
836
  """Create the body parameter before we pass it as either json or content to the request builder"""
812
837
  retval = []
813
- body_param = cast(BodyParameter, builder.parameters.body_parameter)
814
- if hasattr(body_param, "entries"):
838
+ body_param = builder.parameters.body_parameter
839
+ if body_param.entries:
815
840
  return _serialize_multipart_body(builder)
816
841
  body_kwarg_name = builder.request_builder.parameters.body_parameter.client_name
817
842
  body_param_type = body_param.type
@@ -972,9 +997,13 @@ class _OperationSerializer(
972
997
  f" {parameter.client_name}={parameter.name_in_high_level_operation},"
973
998
  f"{' # type: ignore' if type_ignore else ''}"
974
999
  )
975
- if request_builder.has_form_data_body:
976
- retval.append(" data=_data,")
1000
+ if builder.parameters.has_body and builder.parameters.body_parameter.entries:
1001
+ # this is for legacy
1002
+ client_name = builder.parameters.body_parameter.client_name
1003
+ retval.append(f" {client_name}=_{client_name},")
1004
+ elif request_builder.has_form_data_body:
977
1005
  retval.append(" files=_files,")
1006
+ retval.append(" data=_data,")
978
1007
  elif request_builder.overloads:
979
1008
  seen_body_params = set()
980
1009
  for overload in request_builder.overloads:
@@ -130,18 +130,30 @@ class GeneralSerializer(BaseSerializer):
130
130
  "MatchConditions",
131
131
  ImportType.SDKCORE,
132
132
  )
133
- if self.code_model.has_form_data:
133
+ if (
134
+ self.code_model.has_form_data
135
+ and self.code_model.options["models_mode"] == "dpg"
136
+ ):
137
+ file_import.add_submodule_import("typing", "IO", ImportType.STDLIB)
138
+ file_import.add_submodule_import("typing", "Tuple", ImportType.STDLIB)
134
139
  file_import.add_submodule_import("typing", "Union", ImportType.STDLIB)
140
+ file_import.add_submodule_import("typing", "Optional", ImportType.STDLIB)
141
+ file_import.add_submodule_import("typing", "Mapping", ImportType.STDLIB)
142
+ file_import.add_submodule_import("typing", "Sequence", ImportType.STDLIB)
143
+ file_import.add_submodule_import("typing", "Dict", ImportType.STDLIB)
135
144
  file_import.add_submodule_import("typing", "Any", ImportType.STDLIB)
136
- file_import.add_submodule_import("io", "IOBase", ImportType.STDLIB)
137
- file_import.add_submodule_import("io", "BytesIO", ImportType.STDLIB)
138
- file_import.add_import("uuid", ImportType.STDLIB)
139
- file_import.add_import("json", ImportType.STDLIB)
140
- file_import.add_mutable_mapping_import()
141
- file_import.add_submodule_import("._model_base", "Model", ImportType.LOCAL)
145
+ file_import.add_submodule_import("typing", "List", ImportType.STDLIB)
142
146
  file_import.add_submodule_import(
143
- "._model_base", "SdkJSONEncoder", ImportType.LOCAL
147
+ "._model_base",
148
+ "SdkJSONEncoder",
149
+ ImportType.LOCAL,
144
150
  )
151
+ file_import.add_submodule_import(
152
+ "._model_base",
153
+ "Model",
154
+ ImportType.LOCAL,
155
+ )
156
+ file_import.add_import("json", ImportType.STDLIB)
145
157
 
146
158
  return template.render(
147
159
  code_model=self.code_model,
@@ -257,7 +257,10 @@ class DpgModelSerializer(_ModelSerializer):
257
257
  args.append(f"visibility=[{v_list}]")
258
258
  if prop.client_default_value is not None:
259
259
  args.append(f"default={prop.client_default_value_declaration}")
260
- if hasattr(prop.type, "encode") and prop.type.encode: # type: ignore
260
+
261
+ if prop.is_multipart_file_input:
262
+ args.append("is_multipart_file_input=True")
263
+ elif hasattr(prop.type, "encode") and prop.type.encode: # type: ignore
261
264
  args.append(f'format="{prop.type.encode}"') # type: ignore
262
265
 
263
266
  field = "rest_discriminator" if prop.is_discriminator else "rest_field"
@@ -16,7 +16,7 @@ import base64
16
16
  import re
17
17
  import copy
18
18
  import typing
19
- import email
19
+ import email.utils
20
20
  from datetime import datetime, date, time, timedelta, timezone
21
21
  from json import JSONEncoder
22
22
  import isodate
@@ -470,7 +470,13 @@ def _get_rest_field(
470
470
 
471
471
 
472
472
  def _create_value(rf: typing.Optional["_RestField"], value: typing.Any) -> typing.Any:
473
- return _deserialize(rf._type, value) if (rf and rf._is_model) else _serialize(value, rf._format if rf else None)
473
+ if not rf:
474
+ return _serialize(value, None)
475
+ if rf._is_multipart_file_input:
476
+ return value
477
+ if rf._is_model:
478
+ return _deserialize(rf._type, value)
479
+ return _serialize(value, rf._format)
474
480
 
475
481
 
476
482
  class Model(_MyMutableMapping):
@@ -567,7 +573,12 @@ class Model(_MyMutableMapping):
567
573
  for k, v in self.items():
568
574
  if exclude_readonly and k in readonly_props: # pyright: ignore[reportUnboundVariable]
569
575
  continue
570
- result[k] = Model._as_dict_value(v, exclude_readonly=exclude_readonly)
576
+ is_multipart_file_input = False
577
+ try:
578
+ is_multipart_file_input = next(rf for rf in self._attr_to_rest_field.values() if rf._rest_name == k)._is_multipart_file_input
579
+ except StopIteration:
580
+ pass
581
+ result[k] = v if is_multipart_file_input else Model._as_dict_value(v, exclude_readonly=exclude_readonly)
571
582
  return result
572
583
 
573
584
  @staticmethod
@@ -575,10 +586,10 @@ class Model(_MyMutableMapping):
575
586
  if v is None or isinstance(v, _Null):
576
587
  return None
577
588
  if isinstance(v, (list, tuple, set)):
578
- return [
589
+ return type(v)(
579
590
  Model._as_dict_value(x, exclude_readonly=exclude_readonly)
580
591
  for x in v
581
- ]
592
+ )
582
593
  if isinstance(v, dict):
583
594
  return {
584
595
  dk: Model._as_dict_value(dv, exclude_readonly=exclude_readonly)
@@ -779,6 +790,7 @@ class _RestField:
779
790
  visibility: typing.Optional[typing.List[str]] = None,
780
791
  default: typing.Any = _UNSET,
781
792
  format: typing.Optional[str] = None,
793
+ is_multipart_file_input: bool = False,
782
794
  ):
783
795
  self._type = type
784
796
  self._rest_name_input = name
@@ -788,6 +800,7 @@ class _RestField:
788
800
  self._is_model = False
789
801
  self._default = default
790
802
  self._format = format
803
+ self._is_multipart_file_input = is_multipart_file_input
791
804
 
792
805
  @property
793
806
  def _rest_name(self) -> str:
@@ -833,8 +846,9 @@ def rest_field(
833
846
  visibility: typing.Optional[typing.List[str]] = None,
834
847
  default: typing.Any = _UNSET,
835
848
  format: typing.Optional[str] = None,
849
+ is_multipart_file_input: bool = False,
836
850
  ) -> typing.Any:
837
- return _RestField(name=name, type=type, visibility=visibility, default=default, format=format)
851
+ return _RestField(name=name, type=type, visibility=visibility, default=default, format=format, is_multipart_file_input=is_multipart_file_input)
838
852
 
839
853
 
840
854
  def rest_discriminator(
@@ -94,7 +94,7 @@ setup(
94
94
  {% elif azure_arm %}
95
95
  "azure-mgmt-core<2.0.0,>=1.3.2",
96
96
  {% else %}
97
- "azure-core<2.0.0,>=1.29.5",
97
+ "azure-core<2.0.0,>=1.30.0",
98
98
  {% endif %}
99
99
  ],
100
100
  {% if package_mode %}
@@ -65,38 +65,42 @@ def prep_if_none_match(etag: Optional[str], match_condition: Optional[MatchCondi
65
65
  return "*"
66
66
  return None
67
67
  {% endif %}
68
- {% if code_model.has_form_data %}
69
- class NamedBytesIO(BytesIO):
70
- def __init__(self, name: str, *args, **kwargs):
71
- super().__init__(*args, **kwargs)
72
- self.name = name
68
+ {% if code_model.has_form_data and code_model.options["models_mode"] == "dpg" %}
69
+ # file-like tuple could be `(filename, IO (or bytes))` or `(filename, IO (or bytes), content_type)`
70
+ FileContent = Union[str, bytes, IO[str], IO[bytes]]
73
71
 
74
- def multipart_file(file: Union[IOBase, bytes]) -> IOBase:
75
- if isinstance(file, IOBase):
76
- return file
77
- return NamedBytesIO("auto-name-" + str(uuid.uuid4()), file)
72
+ FileType = Union[
73
+ # file (or bytes)
74
+ FileContent,
75
+ # (filename, file (or bytes))
76
+ Tuple[Optional[str], FileContent],
77
+ # (filename, file (or bytes), content_type)
78
+ Tuple[Optional[str], FileContent, Optional[str]],
79
+ ]
78
80
 
79
- def multipart_data(data: Any) -> Any:
80
- if isinstance(data, (list, tuple, dict, Model)):
81
- return json.dumps(data, cls=SdkJSONEncoder, exclude_readonly=True)
82
- return data
81
+ FilesType = Union[Mapping[str, FileType], Sequence[Tuple[str, FileType]]]
83
82
 
84
- def handle_multipart_form_data_model(body: Model) -> MutableMapping[str, Any]: # pylint: disable=unsubscriptable-object
85
- """handle first layer of model.
86
- If its value is bytes or IO, replace it with raw value instead of serialized value.
83
+ def serialize_multipart_data_entry(data_entry: Any) -> Any:
84
+ if isinstance(data_entry, (list, tuple, dict, Model)):
85
+ return json.dumps(data_entry, cls=SdkJSONEncoder, exclude_readonly=True)
86
+ return data_entry
87
87
 
88
- :param body: The model to handle.
89
- :type body: ~payload.multipart._model_base.Model
90
- :return: The handled model.
91
- :rtype: MutableMapping[str, Any]
92
- """
93
- result = body.as_dict()
94
- rest_name_attr = {v._rest_name: k for k, v in body._attr_to_rest_field.items()} # pylint: disable=protected-access
95
- for rest_name in result.keys():
96
- attr = rest_name_attr.get(rest_name)
97
- if attr is not None:
98
- raw_value = getattr(body, attr, None)
99
- if isinstance(raw_value, (bytes, IOBase)):
100
- result[rest_name] = raw_value
101
- return result
102
- {% endif %}
88
+ def prepare_multipart_form_data(
89
+ body: Mapping[str, Any], multipart_fields: List[str], data_fields: List[str]
90
+ ) -> Tuple[List[FileType], Dict[str, Any]]:
91
+ files: List[FileType] = []
92
+ data: Dict[str, Any] = {}
93
+ for multipart_field in multipart_fields:
94
+ multipart_entry = body.get(multipart_field)
95
+ if isinstance(multipart_entry, list):
96
+ files.extend([(multipart_field, e) for e in multipart_entry ])
97
+ elif multipart_entry:
98
+ files.append((multipart_field, multipart_entry))
99
+
100
+ for data_field in data_fields:
101
+ data_entry = body.get(data_field)
102
+ if data_entry:
103
+ data[data_field] = serialize_multipart_data_entry(data_entry)
104
+
105
+ return files, data
106
+ {% endif %}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autorest/python",
3
- "version": "6.12.3",
3
+ "version": "6.12.4",
4
4
  "description": "The Python extension for generators in AutoRest.",
5
5
  "main": "index.js",
6
6
  "repository": {
package/run_cadl.py CHANGED
@@ -22,7 +22,7 @@ if __name__ == "__main__":
22
22
  env_builder = venv.EnvBuilder(with_pip=True)
23
23
  venv_context = env_builder.ensure_directories(venv_path)
24
24
 
25
- if "--debug" in sys.argv:
25
+ if "--debug" in sys.argv or "--debug=true" in sys.argv:
26
26
  try:
27
27
  import debugpy # pylint: disable=import-outside-toplevel
28
28
  except ImportError: