Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[multipart] Support bytes[]/content type #2380

Merged
merged 49 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
94e6e3d
init
msyyc Jan 23, 2024
a45a47c
rename file
msyyc Jan 23, 2024
575568f
lint
msyyc Jan 23, 2024
b8fd3ff
review
msyyc Jan 25, 2024
97932ba
update
msyyc Jan 26, 2024
ca46a17
fix cspell
msyyc Jan 26, 2024
44747f3
Merge branch 'main' into multipart-file-array-dev5
msyyc Jan 29, 2024
fba4a91
update test
msyyc Jan 29, 2024
f7d4ce6
update test
msyyc Jan 29, 2024
f0eb959
accept files input, doesn't work
Jan 29, 2024
7280ce8
don't use unreleased tcgc
Jan 29, 2024
a847978
Merge branch 'main' of https://github.com/Azure/autorest.python into …
Jan 30, 2024
6493106
Merge branch 'main' of https://github.com/Azure/autorest.python into …
Jan 30, 2024
3d78277
revert test changes to multipart
Jan 30, 2024
fbc88e6
try to pass payload multipart, waiting on tcgc
Jan 31, 2024
e72641e
can generate list of multipart file types
Jan 31, 2024
5518329
regen
Jan 31, 2024
9c4db12
update to dev 4 version
Feb 1, 2024
445d4a3
use dev.5
Feb 1, 2024
3e57197
passes multipart test
Feb 1, 2024
3e70bac
almost there with legacy
Feb 1, 2024
913c3f5
remove file_properties
Feb 1, 2024
34d9725
add back doc formatting
Feb 1, 2024
915f34b
black
Feb 1, 2024
7062043
Merge branch 'main' of https://github.com/Azure/autorest.python into …
Feb 1, 2024
4a887a1
linting checks
Feb 1, 2024
b41e539
fix gen and add dpg changes
Feb 1, 2024
ca66468
remove multipartFile section from types
Feb 1, 2024
8734335
run black
Feb 1, 2024
fe19141
remove files added to azure mock api tests
Feb 1, 2024
0375e3d
use eggs for azure-core and corehttp in main
Feb 1, 2024
1d977fd
import email.utils
Feb 1, 2024
8f53d03
generate with optional filetype
Feb 1, 2024
0da11ba
give typehint to _data
Feb 1, 2024
7bfd08f
fix typing for files
Feb 1, 2024
6704313
bump azure-core versions
Feb 1, 2024
dcf59fa
regen with fixed files typing
Feb 1, 2024
3eb1f38
prepare changelog and package.json
Feb 1, 2024
2c256e6
black
Feb 1, 2024
6d3da87
fix corehttp dep in setup.py
Feb 2, 2024
2533326
mypy fix
Feb 2, 2024
c15d6b4
use FilesType typing for _files
Feb 2, 2024
43520d4
fix mypy hopefully
Feb 2, 2024
e8d1b7a
regen with correct relative import of FileType
Feb 2, 2024
ad3174d
improve code structure
Feb 2, 2024
9731549
fix generation for autorest
Feb 2, 2024
e49b726
add complex tests
Feb 2, 2024
7ad0dbb
fix mypy and pyright of generated helper functions
Feb 2, 2024
a7a41c9
remove unused imports in generated code
Feb 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
17 changes: 17 additions & 0 deletions packages/autorest.python/ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Release

## 2024-02-01 - 6.12.4

| Library | Min Version |
| ----------------------------------------------------------------------- | ----------- |
| `@autorest/core` | `3.9.2` |
| `@autorest/modelerfour` | `4.24.3` |
| `azure-core` dep of generated code | `1.30.0` |
| `isodate` dep of generated code | `0.6.1` |
| `msrest` dep of generated code (If generating legacy code) | `0.7.1` |
| `azure-mgmt-core` dep of generated code (If generating mgmt plane code) | `1.3.2` |
| `typing-extensions` dep of generated code (If generating with constants)| `4.0.1` |

**Other Changes**

- Add support for multipart generation from typespec #2380
- Bump minimum dependency of `azure-core` to 1.30.0

## 2023-01-19 - 6.12.3

| Library | Min Version |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
ParameterLocation,
BodyParameter,
ParameterDelimeter,
MultipartBodyParameter,
ClientParameter,
ConfigParameter,
)
Expand Down Expand Up @@ -115,7 +114,6 @@
"BodyParameter",
"RequestBuilderBodyParameter",
"ParameterDelimeter",
"MultipartBodyParameter",
"CredentialType",
"ClientParameter",
"ConfigParameter",
Expand Down
4 changes: 4 additions & 0 deletions packages/autorest.python/autorest/codegen/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,7 @@ def serialization_constraints(self) -> List[str]:
@property
def type_description(self) -> str:
return self.type_annotation()

@property
def is_form_data(self) -> bool:
return False
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ def type_definition(self, **kwargs: Any) -> str:
pattern = re.compile(r"Union\[.*\]")
return f'Union[{", ".join(map(lambda x: x[6: -1] if pattern.match(x) else x, inside_types))}]'

@property
def is_form_data(self) -> bool:
return any(t.is_form_data for t in self.types)

def get_json_template_representation(
self,
*,
Expand Down
20 changes: 20 additions & 0 deletions packages/autorest.python/autorest/codegen/models/model_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ def __init__(
self.snake_case_name: str = self.yaml_data["snakeCaseName"]
self.page_result_model: bool = self.yaml_data.get("pageResultModel", False)

@property
def is_form_data(self) -> bool:
return any(p.is_multipart_file_input for p in self.properties)

@property
def is_xml(self) -> bool:
return self.yaml_data.get("isXml", False)
Expand Down Expand Up @@ -314,6 +318,22 @@ def imports(self, **kwargs: Any) -> FileImport:
if kwargs.get("model_typing")
else TypingSection.REGULAR,
)
if self.is_form_data:
file_import.add_submodule_import(
relative_path,
"_model_base",
ImportType.LOCAL,
typing_section=TypingSection.TYPING
if kwargs.get("model_typing")
else TypingSection.REGULAR,
)
if any(not p.is_multipart_file_input for p in self.properties):
file_import.add_submodule_import(
f"{relative_path}_model_base",
"SdkJSONEncoder",
ImportType.LOCAL,
)
file_import.add_import("json", ImportType.STDLIB)
return file_import


Expand Down
24 changes: 1 addition & 23 deletions packages/autorest.python/autorest/codegen/models/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
)
from .parameter import (
BodyParameter,
MultipartBodyParameter,
Parameter,
ParameterLocation,
)
Expand Down Expand Up @@ -290,7 +289,6 @@ def has_kwargs_to_pop_with_default(
Parameter,
RequestBuilderParameter,
BodyParameter,
MultipartBodyParameter,
]
],
location: ParameterLocation,
Expand Down Expand Up @@ -575,33 +573,13 @@ def imports(self, async_mode: bool, **kwargs: Any) -> FileImport:
relative_path = "..." if async_mode else ".."
if self.code_model.options["models_mode"] == "dpg":
if self.parameters.has_body:
if not self.parameters.body_parameter.is_form_data:
if not self.has_form_data_body:
file_import.add_submodule_import(
f"{relative_path}_model_base",
"SdkJSONEncoder",
ImportType.LOCAL,
)
file_import.add_import("json", ImportType.STDLIB)
else:
file_import.add_submodule_import(
relative_path, "_model_base", ImportType.LOCAL
)
file_import.add_submodule_import("io", "IOBase", ImportType.STDLIB)
file_import.add_submodule_import(
f"{relative_path}_vendor",
"multipart_file",
ImportType.LOCAL,
)
file_import.add_submodule_import(
f"{relative_path}_vendor",
"multipart_data",
ImportType.LOCAL,
)
file_import.add_submodule_import(
f"{relative_path}_vendor",
"handle_multipart_form_data_model",
ImportType.LOCAL,
)
if self.default_error_deserialization or any(
r.type for r in self.responses
):
Expand Down
77 changes: 26 additions & 51 deletions packages/autorest.python/autorest/codegen/models/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
Optional,
TypeVar,
Union,
Generic,
)

from .imports import FileImport, ImportType, TypingSection
Expand Down Expand Up @@ -243,9 +242,18 @@ def method_signature(self, async_mode: bool) -> str:
class BodyParameter(_ParameterBase):
"""Body parameter."""

@property
def entries(self) -> List["BodyParameter"]:
return [
BodyParameter.from_yaml(e, self.code_model)
for e in self.yaml_data.get("entries", [])
]

@property
def is_form_data(self) -> bool:
return self.default_content_type == "multipart/form-data"
# hacky, but rn in legacy, there is no formdata model type, it's just a dict
# with all of the entries splatted out
return self.type.is_form_data or bool(self.entries)

@property
def is_partial_body(self) -> bool:
Expand All @@ -262,6 +270,10 @@ def method_location(self) -> ParameterMethodLocation:

@property
def in_method_signature(self) -> bool:
if self.yaml_data.get("entries"):
# Right now, only legacy generates with multipart bodies and entries
# and legacy generates with the multipart body arguments splatted out
return False
return not (self.flattened or self.grouped_by)

@property
Expand All @@ -278,6 +290,18 @@ def has_json_model_type(self) -> bool:
return self.type.target_model_subtype((JSONModelType,)) is not None
return isinstance(self.type, JSONModelType)

def imports(self, async_mode: bool, **kwargs: Any) -> FileImport:
file_import = super().imports(async_mode, **kwargs)
if self.is_form_data:
relative_path = "..." if async_mode else ".."
file_import.add_submodule_import(
f"{relative_path}_vendor", "FilesType", ImportType.LOCAL
)
file_import.add_submodule_import("typing", "Dict", ImportType.STDLIB)
file_import.add_submodule_import("typing", "Any", ImportType.STDLIB)
file_import.add_submodule_import("typing", "List", ImportType.STDLIB)
return file_import

@classmethod
def from_yaml(
cls, yaml_data: Dict[str, Any], code_model: "CodeModel"
Expand All @@ -294,46 +318,6 @@ def from_yaml(
)


class _MultipartBodyParameter(Generic[EntryBodyParameterType], BodyParameter):
"""Base class for MultipartBodyParameter and RequestBuilderMultipartBodyParameter"""

def __init__(
self,
yaml_data: Dict[str, Any],
code_model: "CodeModel",
type: BaseType,
entries: List[EntryBodyParameterType],
) -> None:
super().__init__(yaml_data, code_model, type)
self.entries = entries

@property
def in_method_signature(self) -> bool:
# Right now, only legacy generates with multipart bodies
# and legacy generates with the multipart body arguments splatted out
return False


class MultipartBodyParameter(
_MultipartBodyParameter[BodyParameter] # pylint: disable=unsubscriptable-object
):
"""Multipart body parameter for Operation. Used for files and data input."""

@classmethod
def from_yaml(
cls, yaml_data: Dict[str, Any], code_model: "CodeModel"
) -> "MultipartBodyParameter":
return cls(
yaml_data=yaml_data,
code_model=code_model,
type=code_model.lookup_type(id(yaml_data["type"])),
entries=[
BodyParameter.from_yaml(entry, code_model)
for entry in yaml_data["entries"]
],
)


class Parameter(_ParameterBase):
"""Basic Parameter class"""

Expand Down Expand Up @@ -455,12 +439,3 @@ def method_location(self) -> ParameterMethodLocation:
if self.constant:
return ParameterMethodLocation.KWARG
return ParameterMethodLocation.POSITIONAL


def get_body_parameter(
yaml_data: Dict[str, Any], code_model: "CodeModel"
) -> Union[BodyParameter, MultipartBodyParameter]:
"""Creates a regular body parameter or Multipart body parameter"""
if yaml_data.get("entries"):
return MultipartBodyParameter.from_yaml(yaml_data, code_model)
return BodyParameter.from_yaml(yaml_data, code_model)
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,15 @@

from .request_builder_parameter import (
RequestBuilderBodyParameter,
RequestBuilderMultipartBodyParameter,
RequestBuilderParameter,
get_request_body_parameter,
)
from .parameter import (
MultipartBodyParameter,
ParameterLocation,
BodyParameter,
Parameter,
ParameterMethodLocation,
ClientParameter,
ConfigParameter,
get_body_parameter,
)

ParameterType = TypeVar(
Expand All @@ -43,10 +39,6 @@
BodyParameterType = TypeVar(
"BodyParameterType", bound=Union[BodyParameter, RequestBuilderBodyParameter]
)
RequestBuilderBodyParameterType = Union[
RequestBuilderBodyParameter, RequestBuilderMultipartBodyParameter
]


if TYPE_CHECKING:
from .code_model import CodeModel
Expand Down Expand Up @@ -314,7 +306,7 @@ def from_yaml(cls, yaml_data: Dict[str, Any], code_model: "CodeModel"):

class _ParameterList(
_ParameterListBase[ # pylint: disable=unsubscriptable-object
Parameter, Union[MultipartBodyParameter, BodyParameter]
Parameter, BodyParameter
]
):
"""Base Parameter class for the two operation ParameterLists"""
Expand All @@ -325,11 +317,9 @@ def parameter_creator() -> Callable[[Dict[str, Any], "CodeModel"], Parameter]:

@staticmethod
def body_parameter_creator() -> (
Callable[
[Dict[str, Any], "CodeModel"], Union[MultipartBodyParameter, BodyParameter]
]
Callable[[Dict[str, Any], "CodeModel"], BodyParameter]
):
return get_body_parameter
return BodyParameter.from_yaml

@property
def implementation(self) -> str:
Expand All @@ -348,7 +338,7 @@ class ParameterList(_ParameterList):

class _RequestBuilderParameterList(
_ParameterListBase[ # pylint: disable=unsubscriptable-object
RequestBuilderParameter, RequestBuilderBodyParameterType
RequestBuilderParameter, RequestBuilderBodyParameter
]
):
"""_RequestBuilderParameterList is base parameter list for RequestBuilder classes"""
Expand All @@ -361,9 +351,9 @@ def parameter_creator() -> (

@staticmethod
def body_parameter_creator() -> (
Callable[[Dict[str, Any], "CodeModel"], RequestBuilderBodyParameterType]
Callable[[Dict[str, Any], "CodeModel"], RequestBuilderBodyParameter]
):
return get_request_body_parameter
return RequestBuilderBodyParameter.from_yaml

@property
def implementation(self) -> str:
Expand All @@ -372,14 +362,14 @@ def implementation(self) -> str:
@property
def unsorted_method_params(
self,
) -> List[Union[RequestBuilderParameter, RequestBuilderBodyParameterType]]:
) -> List[Union[RequestBuilderParameter, RequestBuilderBodyParameter]]:
# don't have access to client params in request builder
retval = [
p
for p in super().unsorted_method_params
if not (
p.location == ParameterLocation.BODY
and cast(RequestBuilderBodyParameterType, p).is_partial_body
and cast(RequestBuilderBodyParameter, p).is_partial_body
)
]
retval.extend(
Expand All @@ -400,7 +390,7 @@ def path(self) -> List[RequestBuilderParameter]:
@property
def constant(
self,
) -> List[Union[RequestBuilderParameter, RequestBuilderBodyParameterType]]:
) -> List[Union[RequestBuilderParameter, RequestBuilderBodyParameter]]:
"""All constant parameters"""
return [
p for p in super().constant if p.location != ParameterLocation.ENDPOINT_PATH
Expand Down
Loading
Loading