Skip to content

Array of files in multipart/form-data is not handled correctly #692

Open
@davidlizeng

Description

@davidlizeng

Describe the bug
For multipart/form-data with an array of files, generated code tries to serialize the array of files as JSON.

To Reproduce
Steps to reproduce the behavior:

  1. Using the spec included in this bug report, run openapi-python-client --path spec.json
  2. Try to run the following code:
from multiple_upload_client.client import Client
from multiple_upload_client.models import UploadMultipleMultipartData
from multiple_upload_client.api.files import upload_multiple
from multiple_upload_client.types import File

client = Client(base_url="http://localhost:8080")
upload_multiple.sync_detailed(
  client=client,
  multipart_data=UploadMultipleMultipartData(
    files=[
      File(
        payload=open("path to some local file", "rb"),
        file_name="sample.jpeg",
        mime_type="image/jpeg"
      )
    ]
  )
)

The following error occurs:

Traceback (most recent call last):
  File "test_multiple_upload.py", line 7, in <module>
    upload_multiple.sync_detailed(
  File "/Users/davidzeng/butler/src/experimental/test-codegen/opc/multiple-upload-client/multiple_upload_client/api/files/upload_multiple.py", line 62, in sync_detailed
    kwargs = _get_kwargs(
  File "/Users/davidzeng/butler/src/experimental/test-codegen/opc/multiple-upload-client/multiple_upload_client/api/files/upload_multiple.py", line 20, in _get_kwargs
    multipart_multipart_data = multipart_data.to_multipart()
  File "/Users/davidzeng/butler/src/experimental/test-codegen/opc/multiple-upload-client/multiple_upload_client/models/upload_multiple_multipart_data.py", line 47, in to_multipart
    files = (None, json.dumps(_temp_files).encode(), "application/json")
  File "/usr/local/opt/python@3.8/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "/usr/local/opt/python@3.8/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/local/opt/python@3.8/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/usr/local/opt/python@3.8/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type BufferedReader is not JSON serializable

Expected behavior
The generated code for handling an array of files seems to be trying to serialize the files as json:

    def to_multipart(self) -> Dict[str, Any]:
        files: Union[Unset, Tuple[None, bytes, str]] = UNSET
        if not isinstance(self.files, Unset):
            _temp_files = []
            for files_item_data in self.files:
                files_item = files_item_data.to_tuple()

                _temp_files.append(files_item)
            files = (None, json.dumps(_temp_files).encode(), "application/json")

        field_dict: Dict[str, Any] = {}
        field_dict.update(
            {key: (None, str(value).encode(), "text/plain") for key, value in self.additional_properties.items()}
        )
        field_dict.update({})
        if files is not UNSET:
            field_dict["files"] = files

        return field_dict

Based on https://www.python-httpx.org/advanced/#multipart-file-encoding, we should probably be doing something more like the following, treating the multipart data as a list of tuples, with field keys that can repeat. Each file is added to the list under the same "files" key:

    def to_multipart(self) -> List[Tuple[str, FileJsonType]]:
        field_list = []
        if not isinstance(self.files, Unset):
            for files_item_data in self.files:
                files_item = files_item_data.to_tuple()
                field_list.append(("files", files_item))

        for key, value in self.additional_properties.items():
            field_list.append((key, (None, str(value).encode(), "text/plain")))

        return field_list

OpenAPI Spec File

{
  "openapi": "3.0.0",
  "paths": {
    "/api/files/upload_multiple": {
      "post": {
        "operationId": "uploadMultiple",
        "summary": "Uploads multiple files",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "files": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    }
                  }
                }
              }
            }
          },
          "required": true
        },
        "parameters": [],
        "responses": {
          "201": {
            "description": "Returns some random string",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "tags": [
          "files"
        ]
      }
    }
  },
  "info": {
    "title": "Multiple Upload",
    "description": "Test spec for array of files in multipart/form-data",
    "version": "0.0.1",
    "contact": {}
  },
  "tags": [],
  "servers": [],
  "components": {
    "schemas": {}
  }
}

Desktop (please complete the following information):

  • OS: macOS 10.15.7
  • Python Version: 3.8.13
  • openapi-python-client version: 0.11.6

Additional context
Add any other context about the problem here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    🆘 help wantedExtra attention is needed🐞bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions