-
-
Notifications
You must be signed in to change notification settings - Fork 777
Description
Description
Multipart handling is very confusing in Connexion 3.x. I found that:
- If a client passes a non-file object to something which is expected to be a file, then the object winds up in the
body
parameter as a string. - If a developer declares a file-like parameter to be required, and makes it a positional argument (makes sense?!?), but the client passes a non-file under that parameter name, then the code will fail, because it will fail to supply a positional parameter.
Expected behaviour
The following code illustrates what seems like strange (or at least, not-documented) behavior:
openapi: "3.0.1"
info:
title: Form Data
version: "1.0"
servers:
- url: /openapi
paths:
/greeting:
post:
summary: Generate greeting
operationId: hello.post_greeting
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
name:
type: string
selfie:
type: string
format: binary
required:
- name
- selfie
responses:
200:
description: OK
from pathlib import Path
import connexion
from starlette.responses import PlainTextResponse, Response
async def post_greeting(body, selfie=None) -> Response:
return PlainTextResponse(f"Hello body={body} selfie={selfie}!", status_code=201)
app = connexion.AsyncApp(__name__, specification_dir="spec")
app.add_api("openapi.yaml", arguments={"title": "Hello World Example"})
if __name__ == "__main__":
app.run(f"{Path(__file__).stem}:app", port=8080)
Actual behaviour
# pass non-file for selfie; required param validation still passes, but selfie param is "None"
$ curl -F name=dave -F selfie=nah http://localhost:8080/openapi/greeting
Hello body={'name': 'dave', 'selfie': 'nah'} selfie=None!
$ curl -F name=dave -F selfie=@128.png http://localhost:8080/openapi/greeting
Hello body={'name': 'dave'} selfie=[<starlette.datastructures.UploadFile object at 0x7f4587243df0>]!
# pass file for both, now body is empty, but required param validation still passes
$ curl -F name=@128.png -F selfie=@128.png http://localhost:8080/openapi/greeting
Hello body={} selfie=[<starlette.datastructures.UploadFile object at 0x7f4587243df0>]!
This makes for a very confusing development experience. I had mistakenly assumed that either:
- The
selfie
param would have typestarlette.datastructures.UploadFile | str
OR body.selfie
would have typestarlette.datastructures.UploadFile | str
I'm not saying that the current behavior is wrong, and, now that I have completely worked it out, I can program against it, but it feels non-obvious to me. Some greater guidance in the documentation would really help. I had to write about 6 test programs to sort out the behavior here. The documentation doesn't really say what the semantics of "body" is.
It also seems really confusing that string-type form parameters like name
only seem to show up in the "body" parameter, but are not expanded into function params. This disparate treatment of these two kinds of things is really not clearly explained.
Steps to reproduce
See above
Additional info:
Output of the commands:
python --version
:Python 3.10.13
pip show connexion | grep "^Version\:"
3.0.1