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

Conversation

msyyc
Copy link
Member

@msyyc msyyc commented Jan 23, 2024

body.update(data)
with pytest.raises(TypeError):
# caused by deepcopy when DPG model init
op(model_class(body))
Copy link
Member Author

Choose a reason for hiding this comment

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

DPG model will raise error when deepcody of init if property value is io. @iscai-msft

@msyyc msyyc requested a review from iscai-msft January 25, 2024 02:21
@@ -23,7 +23,9 @@
from .constant_type import ConstantType
from .utils import add_to_description
from .combined_type import CombinedType
from .model_type import JSONModelType
from .model_type import JSONModelType, DPGModelType
Copy link
Contributor

Choose a reason for hiding this comment

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

wit this dpg PR Azure/typespec-azure#166 all we have to do is create a new type MultipartFileType and then type this type as Union[binary, Tuple[string, binary], Tuple[string, binary, string]]

@msyyc msyyc requested a review from iscai-msft January 26, 2024 06:26

# 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.

return file
return NamedBytesIO("auto-name-" + str(uuid.uuid4()), file)
return ("auto-name-" + str(uuid.uuid4()), file)
Copy link
Contributor

Choose a reason for hiding this comment

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

why are we giving auto names to these file fields? That's def not what we should be doing. We can get the field name from the typespec and then create a tuple of all of the files entries.

So

class MyMultipartModel:
  profile_image: MultipartFile = rest_field("profileImage")
  pictures: MultipartFile[] = rest_field("pictures")

def my_multipart_func(body: MyMultipartModel):
  _files = [("profileImage", body.profile_image)]
  _files.extend([("pictures", p)] for p in body.pictures)
  request = HttpRequest(..., files=_files)
  ...

Copy link
Member Author

Choose a reason for hiding this comment

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

why are we giving auto names to these file fields?
-> According to https://www.rfc-editor.org/rfc/rfc7578#section-4.2, file name SHOULD be supplied. In my local test, the flask of python/multer of js will not parse request into files part if no file name provided. So I think SDK need to provide a name for it if users don't provide.
image

Copy link
Member Author

Choose a reason for hiding this comment

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

why are we giving auto names to these file fields? That's def not what we should be doing. We can get the field name from the typespec and then create a tuple of all of the files entries.

So

class MyMultipartModel:
  profile_image: MultipartFile = rest_field("profileImage")
  pictures: MultipartFile[] = rest_field("pictures")

def my_multipart_func(body: MyMultipartModel):
  _files = [("profileImage", body.profile_image)]
  _files.extend([("pictures", p)] for p in body.pictures)
  request = HttpRequest(..., files=_files)
  ...

Current logic is actually similar to yours and it also compatible when body type is JSON

Copy link
Member

Choose a reason for hiding this comment

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

SHOULD doesn't mean MUST. If the IO is not a file descriptor and we don't have a name, we don't set it. No autoname, this will be sent to the servirce, that may save it, or even act based on the received name.

Copy link
Member

Choose a reason for hiding this comment

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

Reading the code more, this function should not exist

@@ -278,6 +280,25 @@ def has_json_model_type(self) -> bool:
return self.type.target_model_subtype((JSONModelType,)) is not None
return isinstance(self.type, JSONModelType)

@property
Copy link
Contributor

Choose a reason for hiding this comment

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

also we can actually support data and files entered, and pass them as HttpRequest(data=data, files=files) once this pr gets merged and released Azure/azure-sdk-for-python#34021

Copy link
Member Author

Choose a reason for hiding this comment

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

Before #2380 (comment), it passes data and files. Now the code merge all part into files and it works. SDK users will not be influenced no matter SDK merge all in files or pass data/files so I think current logic is OK.

@msyyc msyyc requested a review from iscai-msft January 29, 2024 06:30
@msyyc msyyc marked this pull request as ready for review January 29, 2024 06:49
@msyyc msyyc requested a review from tadelesh as a code owner January 29, 2024 06:49
@@ -52,7 +52,7 @@ def client():
def test_multi_part(client: MultiPartClient, op_name, model_class, data, file):
op = getattr(client.form_data, op_name)
# test bytes
body = {k: open(str(v), "rb").read() for k, v in file.items()}
body = {k: ("blob", open(str(v), "rb").read(), "application/octet-stream") for k, v in file.items()}
Copy link
Member Author

Choose a reason for hiding this comment

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

Lack test for binary_array_parts/complex, and you can refer to my previous commit:

"binary_array_parts",
models.BinaryArrayPartsRequest,
{"id": "123"},
{"pictures": [PNG, PNG]},
{},
),
(
"complex",
models.ComplexPartsRequest,
{"id": "123", "previousAddresses": [models.Address(city="Y"), models.Address(city="Z")], "address": models.Address(city="X")},
{"pictures": [PNG, PNG], "profileImage": JPG},
{},
),
],
)
def test_multi_part(client: MultiPartClient, op_name, model_class, data, file, file_info):

Copy link
Contributor

Choose a reason for hiding this comment

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

oh ok, i was confused by the presence of this file since it's generic, it should be in generic_mock_api_+tests

@iscai-msft iscai-msft merged commit 59f42d1 into main Feb 2, 2024
10 checks passed
@iscai-msft iscai-msft deleted the multipart-file-array-dev5 branch February 2, 2024 21:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support passing tuple through bytes type that are meant for multipart form-data
3 participants