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 3 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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,11 @@ def imports(self, async_mode: bool, **kwargs: Any) -> FileImport:
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",
"has_file",
ImportType.LOCAL,
)
file_import.add_submodule_import(
f"{relative_path}_vendor",
"multipart_file",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -759,8 +759,8 @@ def _serialize_body_parameter(self, builder: OperationType) -> List[str]:
f" _body = handle_multipart_form_data_model({body_param.client_name})",
"else:",
f" _body = {body_param.client_name}",
"_files = {k: multipart_file(v) for k, v in _body.items() if isinstance(v, (IOBase, bytes))}",
"_data = {k: multipart_data(v) for k, v in _body.items() if not isinstance(v, (IOBase, bytes))}",
"_files = {k: multipart_file(v) for k, v in _body.items() if has_file(v)}",
"_data = {k: multipart_data(v) for k, v in _body.items() if not has_file(v)}",
]
retval: List[str] = []
body_kwarg_name = builder.request_builder.parameters.body_parameter.client_name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ def serialize_vendor_file(self, clients: List[Client]) -> str:
ImportType.SDKCORE,
)
if self.code_model.has_form_data:
file_import.add_submodule_import("typing", "List", ImportType.STDLIB)
file_import.add_submodule_import("typing", "Tuple", ImportType.STDLIB)
file_import.add_submodule_import("typing", "Union", ImportType.STDLIB)
file_import.add_submodule_import("typing", "Any", ImportType.STDLIB)
file_import.add_submodule_import("io", "IOBase", ImportType.STDLIB)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,31 @@ def prep_if_none_match(etag: Optional[str], match_condition: Optional[MatchCondi
return None
{% endif %}
{% if code_model.has_form_data %}

# file-like tuple could be `(filename, IO (or bytes))` or `(filename, IO (or bytes), content_type)`
FileType = Union[IOBase, bytes]
FileTuple = Union[Tuple[str, FileType], Tuple[str, FileType, str]]
MultiPartFile = Union[IOBase, bytes, FileTuple]
HandledMultiPartFile = Union[IOBase, FileTuple]

class NamedBytesIO(BytesIO):
def __init__(self, name: str, *args, **kwargs):
super().__init__(*args, **kwargs)
self.name = name

def multipart_file(file: Union[IOBase, bytes]) -> IOBase:
if isinstance(file, IOBase):
def has_file(data: Any) -> bool:
msyyc marked this conversation as resolved.
Show resolved Hide resolved
return isinstance(data, (IOBase, bytes)) or (isinstance(data, (list, tuple)) and any(isinstance(f, (IOBase, bytes, tuple)) for f in data))

def handle_file(file: Union[IOBase, bytes, FileTuple]) -> HandledMultiPartFile:
if isinstance(file, (IOBase, tuple)):
return file
return NamedBytesIO("auto-name-" + str(uuid.uuid4()), file)

def multipart_file(file: Union[MultiPartFile, List[MultiPartFile]]) -> Union[HandledMultiPartFile, List[HandledMultiPartFile]]:
if isinstance(file, list):
return [handle_file(f) for f in file]
return handle_file(file)

def multipart_data(data: Any) -> Any:
if isinstance(data, (list, tuple, dict, Model)):
return json.dumps(data, cls=SdkJSONEncoder, exclude_readonly=True)
Expand All @@ -96,7 +111,7 @@ def handle_multipart_form_data_model(body: Model) -> MutableMapping[str, Any]:
attr = rest_name_attr.get(rest_name)
if attr is not None:
raw_value = getattr(body, attr, None)
if isinstance(raw_value, (bytes, IOBase)):
if has_file(raw_value):
result[rest_name] = raw_value
return result
{% endif %}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from io import BytesIO, IOBase
import json
import sys
from typing import Any, Union
from typing import Any, List, Tuple, Union
import uuid

from ._model_base import Model, SdkJSONEncoder
Expand All @@ -19,18 +19,39 @@
from typing import MutableMapping # type: ignore # pylint: disable=ungrouped-imports


# file-like tuple could be `(filename, IO (or bytes))` or `(filename, IO (or bytes), content_type)`
FileType = Union[IOBase, bytes]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FileTuple = Union[Tuple[str, FileType], Tuple[str, FileType, str]]
MultiPartFile = Union[IOBase, bytes, FileTuple]
HandledMultiPartFile = Union[IOBase, FileTuple]


class NamedBytesIO(BytesIO):
def __init__(self, name: str, *args, **kwargs):
super().__init__(*args, **kwargs)
self.name = name


def multipart_file(file: Union[IOBase, bytes]) -> IOBase:
if isinstance(file, IOBase):
def has_file(data: Any) -> bool:
return isinstance(data, (IOBase, bytes)) or (
isinstance(data, (list, tuple)) and any(isinstance(f, (IOBase, bytes, tuple)) for f in data)
)


def handle_file(file: Union[IOBase, bytes, FileTuple]) -> HandledMultiPartFile:
if isinstance(file, (IOBase, tuple)):
return file
return NamedBytesIO("auto-name-" + str(uuid.uuid4()), file)


def multipart_file(
file: Union[MultiPartFile, List[MultiPartFile]]
) -> Union[HandledMultiPartFile, List[HandledMultiPartFile]]:
if isinstance(file, list):
return [handle_file(f) for f in file]
return handle_file(file)


def multipart_data(data: Any) -> Any:
if isinstance(data, (list, tuple, dict, Model)):
return json.dumps(data, cls=SdkJSONEncoder, exclude_readonly=True)
Expand All @@ -52,6 +73,6 @@ def handle_multipart_form_data_model(body: Model) -> MutableMapping[str, Any]:
attr = rest_name_attr.get(rest_name)
if attr is not None:
raw_value = getattr(body, attr, None)
if isinstance(raw_value, (bytes, IOBase)):
if has_file(raw_value):
result[rest_name] = raw_value
return result
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
# Code generated by Microsoft (R) Python Code Generator.
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
# --------------------------------------------------------------------------
from io import IOBase
import sys
from typing import Any, Callable, Dict, Optional, TypeVar, Union, overload

Expand All @@ -23,7 +22,7 @@
from azure.core.tracing.decorator_async import distributed_trace_async

from ... import _model_base, models as _models
from ..._vendor import handle_multipart_form_data_model, multipart_data, multipart_file
from ..._vendor import handle_multipart_form_data_model, has_file, multipart_data, multipart_file
from ...operations._operations import (
build_form_data_basic_request,
build_form_data_binary_array_parts_request,
Expand Down Expand Up @@ -130,8 +129,8 @@ async def basic( # pylint: disable=inconsistent-return-statements
_body = handle_multipart_form_data_model(body)
else:
_body = body
_files = {k: multipart_file(v) for k, v in _body.items() if isinstance(v, (IOBase, bytes))}
_data = {k: multipart_data(v) for k, v in _body.items() if not isinstance(v, (IOBase, bytes))}
_files = {k: multipart_file(v) for k, v in _body.items() if has_file(v)}
_data = {k: multipart_data(v) for k, v in _body.items() if not has_file(v)}

_request = build_form_data_basic_request(
data=_data,
Expand Down Expand Up @@ -250,8 +249,8 @@ async def complex( # pylint: disable=inconsistent-return-statements
_body = handle_multipart_form_data_model(body)
else:
_body = body
_files = {k: multipart_file(v) for k, v in _body.items() if isinstance(v, (IOBase, bytes))}
_data = {k: multipart_data(v) for k, v in _body.items() if not isinstance(v, (IOBase, bytes))}
_files = {k: multipart_file(v) for k, v in _body.items() if has_file(v)}
_data = {k: multipart_data(v) for k, v in _body.items() if not has_file(v)}

_request = build_form_data_complex_request(
data=_data,
Expand Down Expand Up @@ -352,8 +351,8 @@ async def json_part( # pylint: disable=inconsistent-return-statements
_body = handle_multipart_form_data_model(body)
else:
_body = body
_files = {k: multipart_file(v) for k, v in _body.items() if isinstance(v, (IOBase, bytes))}
_data = {k: multipart_data(v) for k, v in _body.items() if not isinstance(v, (IOBase, bytes))}
_files = {k: multipart_file(v) for k, v in _body.items() if has_file(v)}
_data = {k: multipart_data(v) for k, v in _body.items() if not has_file(v)}

_request = build_form_data_json_part_request(
data=_data,
Expand Down Expand Up @@ -456,8 +455,8 @@ async def binary_array_parts( # pylint: disable=inconsistent-return-statements
_body = handle_multipart_form_data_model(body)
else:
_body = body
_files = {k: multipart_file(v) for k, v in _body.items() if isinstance(v, (IOBase, bytes))}
_data = {k: multipart_data(v) for k, v in _body.items() if not isinstance(v, (IOBase, bytes))}
_files = {k: multipart_file(v) for k, v in _body.items() if has_file(v)}
_data = {k: multipart_data(v) for k, v in _body.items() if not has_file(v)}

_request = build_form_data_binary_array_parts_request(
data=_data,
Expand Down Expand Up @@ -564,8 +563,8 @@ async def json_array_parts( # pylint: disable=inconsistent-return-statements
_body = handle_multipart_form_data_model(body)
else:
_body = body
_files = {k: multipart_file(v) for k, v in _body.items() if isinstance(v, (IOBase, bytes))}
_data = {k: multipart_data(v) for k, v in _body.items() if not isinstance(v, (IOBase, bytes))}
_files = {k: multipart_file(v) for k, v in _body.items() if has_file(v)}
_data = {k: multipart_data(v) for k, v in _body.items() if not has_file(v)}

_request = build_form_data_json_array_parts_request(
data=_data,
Expand Down Expand Up @@ -664,8 +663,8 @@ async def multi_binary_parts( # pylint: disable=inconsistent-return-statements
_body = handle_multipart_form_data_model(body)
else:
_body = body
_files = {k: multipart_file(v) for k, v in _body.items() if isinstance(v, (IOBase, bytes))}
_data = {k: multipart_data(v) for k, v in _body.items() if not isinstance(v, (IOBase, bytes))}
_files = {k: multipart_file(v) for k, v in _body.items() if has_file(v)}
_data = {k: multipart_data(v) for k, v in _body.items() if not has_file(v)}

_request = build_form_data_multi_binary_parts_request(
data=_data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
# Code generated by Microsoft (R) Python Code Generator.
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
# --------------------------------------------------------------------------
from io import IOBase
import sys
from typing import Any, Callable, Dict, Optional, TypeVar, Union, overload

Expand All @@ -25,7 +24,7 @@

from .. import _model_base, models as _models
from .._serialization import Serializer
from .._vendor import handle_multipart_form_data_model, multipart_data, multipart_file
from .._vendor import handle_multipart_form_data_model, has_file, multipart_data, multipart_file

if sys.version_info >= (3, 9):
from collections.abc import MutableMapping
Expand Down Expand Up @@ -181,8 +180,8 @@ def basic( # pylint: disable=inconsistent-return-statements
_body = handle_multipart_form_data_model(body)
else:
_body = body
_files = {k: multipart_file(v) for k, v in _body.items() if isinstance(v, (IOBase, bytes))}
_data = {k: multipart_data(v) for k, v in _body.items() if not isinstance(v, (IOBase, bytes))}
_files = {k: multipart_file(v) for k, v in _body.items() if has_file(v)}
_data = {k: multipart_data(v) for k, v in _body.items() if not has_file(v)}

_request = build_form_data_basic_request(
data=_data,
Expand Down Expand Up @@ -301,8 +300,8 @@ def complex( # pylint: disable=inconsistent-return-statements
_body = handle_multipart_form_data_model(body)
else:
_body = body
_files = {k: multipart_file(v) for k, v in _body.items() if isinstance(v, (IOBase, bytes))}
_data = {k: multipart_data(v) for k, v in _body.items() if not isinstance(v, (IOBase, bytes))}
_files = {k: multipart_file(v) for k, v in _body.items() if has_file(v)}
_data = {k: multipart_data(v) for k, v in _body.items() if not has_file(v)}

_request = build_form_data_complex_request(
data=_data,
Expand Down Expand Up @@ -403,8 +402,8 @@ def json_part( # pylint: disable=inconsistent-return-statements
_body = handle_multipart_form_data_model(body)
else:
_body = body
_files = {k: multipart_file(v) for k, v in _body.items() if isinstance(v, (IOBase, bytes))}
_data = {k: multipart_data(v) for k, v in _body.items() if not isinstance(v, (IOBase, bytes))}
_files = {k: multipart_file(v) for k, v in _body.items() if has_file(v)}
_data = {k: multipart_data(v) for k, v in _body.items() if not has_file(v)}

_request = build_form_data_json_part_request(
data=_data,
Expand Down Expand Up @@ -505,8 +504,8 @@ def binary_array_parts( # pylint: disable=inconsistent-return-statements
_body = handle_multipart_form_data_model(body)
else:
_body = body
_files = {k: multipart_file(v) for k, v in _body.items() if isinstance(v, (IOBase, bytes))}
_data = {k: multipart_data(v) for k, v in _body.items() if not isinstance(v, (IOBase, bytes))}
_files = {k: multipart_file(v) for k, v in _body.items() if has_file(v)}
_data = {k: multipart_data(v) for k, v in _body.items() if not has_file(v)}

_request = build_form_data_binary_array_parts_request(
data=_data,
Expand Down Expand Up @@ -611,8 +610,8 @@ def json_array_parts( # pylint: disable=inconsistent-return-statements
_body = handle_multipart_form_data_model(body)
else:
_body = body
_files = {k: multipart_file(v) for k, v in _body.items() if isinstance(v, (IOBase, bytes))}
_data = {k: multipart_data(v) for k, v in _body.items() if not isinstance(v, (IOBase, bytes))}
_files = {k: multipart_file(v) for k, v in _body.items() if has_file(v)}
_data = {k: multipart_data(v) for k, v in _body.items() if not has_file(v)}

_request = build_form_data_json_array_parts_request(
data=_data,
Expand Down Expand Up @@ -709,8 +708,8 @@ def multi_binary_parts( # pylint: disable=inconsistent-return-statements
_body = handle_multipart_form_data_model(body)
else:
_body = body
_files = {k: multipart_file(v) for k, v in _body.items() if isinstance(v, (IOBase, bytes))}
_data = {k: multipart_data(v) for k, v in _body.items() if not isinstance(v, (IOBase, bytes))}
_files = {k: multipart_file(v) for k, v in _body.items() if has_file(v)}
_data = {k: multipart_data(v) for k, v in _body.items() if not has_file(v)}

_request = build_form_data_multi_binary_parts_request(
data=_data,
Expand Down
Loading
Loading