openapi-generator icon indicating copy to clipboard operation
openapi-generator copied to clipboard

[BUG] [Python] python/python-legacy generated client unable to serialize JSON object in multipart/form-data request

Open dkaper65 opened this issue 3 years ago • 0 comments

Bug Report Checklist

  • [x] Have you provided a full/minimal spec to reproduce the issue?
  • [x] Have you validated the input using an OpenAPI validator (example)?
  • [x] Have you tested with the latest master to confirm the issue still exists?
  • [x] Have you searched for related issues/PRs?
  • [x] What's the actual output vs expected output?
  • [ ] [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

I have an endpoint in which I upload a file and a JSON request. Since I want both the JSON request, and the file to be part of the request body I have configured the endpoint to expect a multipart/form-data request.

When sending a request to the service via the generated python client, I get the following error: TypeError: Can not serialize value type: <class 'dict'>.

The generated Request python class is converted to a dictionary before being put into the params which are sent to the service. It appears the error originates from the aiohttp library because it fails to serialize a dictionary which is passed from rest.mustache.

Note: if you generate the client using the python generator or the python-legacy generator without the --library asyncio flag, a similar error occurs because the request dictionary cannot be serialized.

openapi-generator version

6.1.0

OpenAPI declaration file content or url

Example swagger.json file:

{
  "openapi": "3.0.1",
  "info": {
    "title": "api",
    "description": "An autogenerated client for the api.",
    "version": "v1"
  },
  "paths": {
      "/v1/create": {
          "post": {
            "operationId": "Upload",
            "requestBody": {
              "content": {
                "multipart/form-data": {
                  "schema": {
                    "type": "object",
                    "properties": {
                      "uploadRequest": {
                        "required": [
                          "id"
                        ],
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer",
                            "format": "int64"
                          }
                        },
                        "additionalProperties": false,
                        "description":"some descriptions"
                      },
                      "file": {
                        "type": "string",
                        "format": "binary"
                      }
                    }
                  }
                }
              }
            },
            "responses": {
              "200": {
                "description": "Success",
                "content": {
                  "application/json": {
                    "schema": {
                      "$ref": "#/components/schemas/Response"
                    }
                  }
                }
              }
          }
        }
      }
  },
    "components": {
        "schemas": {
          "Response": {
            "type": "object",
            "properties": {
              "str_field": {
                "type": "string",
                "nullable": true
              }
            },
            "additionalProperties": false
          },
          "UploadRequest": {
              "required": [
                "id"
              ],
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer",
                  "format": "int64"
                }
              },
              "additionalProperties": false
          }
        }
    }
}
Generation Details

Using version 6.1.0 of openapi-generator-cli

Command to generate python client:

docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli generate \
    -i /local/swagger.json \
    -g python-legacy \
    -o /local/ \
    --library asyncio

I am using the python-legacy generator so the generated client supports async/await semantics.

Steps to reproduce
  1. Generate the python client using the above command and swagger file.
  2. Make an upload request using the following sample code:
from openapi_client import api_client
from openapi_client import DefaultApi
from openapi_client.configuration import Configuration
from openapi_client.models import UploadRequest
import asyncio

config = Configuration(host="http://localhost:8000") # can be any URL, an error will be thrown before the request is made
base_client = api_client.ApiClient(configuration=config)
upload_api = DefaultApi(api_client=base_client)
upload_request = UploadRequest(id=12345)
path_to_file = "<path to file>"

loop = asyncio.get_event_loop()
loop.run_until_complete(upload_api.upload(file=path_to_file, upload_request=upload_request))
  1. Observe the error TypeError: Can not serialize value type: <class 'dict'>
Related issues/PRs
Suggest a fix

One potential solution is to simply serialize the dictionary into a JSON string before a request is made to the service. We can update this segment in rest.mustache from this:

if isinstance(v, tuple) and len(v) == 3:
    data.add_field(k,
                   value=v[1],
                   filename=v[0],
                   content_type=v[2])
else:
    data.add_field(k, v)

to this:

if isinstance(v, tuple) and len(v) == 3:
    data.add_field(k,
                   value=v[1],
                   filename=v[0],
                   content_type=v[2])
elif isinstance(v, dict):
    data.add_field(k, json.dumps(v))
else:
    data.add_field(k, v)

This way, the aiohttp library will be receiving a serialized JSON string instead of a dictionary. I have tested this change locally and have verified that it fixes this issue.

dkaper65 avatar Sep 19 '22 21:09 dkaper65