luolingchun / flask-openapi3

Generate REST API and OpenAPI documentation for your Flask project.
https://luolingchun.github.io/flask-openapi3/
MIT License
204 stars 33 forks source link

Parsing list of objects from API fails #185

Closed Ph0tonic closed 3 weeks ago

Ph0tonic commented 1 month ago

Environment:

Hello, I have tried to search for any related issue but haven't been able to found one. I have a form which provides a list of objects as entry to my API: image

However when I try to parse using OpenAPI endpoint:

class Signature(BaseModel):
    date: str = ""
    name: str = ""
    fonction: str = ""

class Options(BaseModel):
    signatures: List[Signature]
    header: bool = True

# some code to create app

@app.post("/generate")
def generate(form: Options) -> any:
    # some code

I get the following error:

[
  {
    "type": "missing",
    "loc": [
      "signatures"
    ],
    "msg": "Field required",
    "input": {
      "header": "true"
    },
    "url": "https://errors.pydantic.dev/2.9/v/missing"
  }
]

My guess is that the bug is linked with the way flask-openapi3 parses data and provides it to pydantik but I am not sure where. Maybe I am just using the wrong way but I haven't found any documentation of this. Any help is welcome.

luolingchun commented 1 month ago

You passed the wrong parameter, it's not signatures[], it's' signatures

curl -X 'POST' \
  'http://127.0.0.1:5000/book' \
  -H 'accept: */*' \
  -H 'Content-Type: multipart/form-data' \
  -F 'header=true' \
  -F 'signatures={"date":"","function":"","name":""}' \
  -F 'signatures={"date": "","function": "","name": ""}' \
  -F 'signatures={"date": "","function": "","name": ""}'

If it is not for file upload, it is recommended to use body instead of form.

By the way, if you want use signatures[], you can define alias in Field.

class Options(BaseModel):
    signatures: List[Signature] = Field(..., alias="signatures[]")
    header: bool = True
Ph0tonic commented 1 month ago

Hum, ok thanks, I managed to make it work. My question relied on the use of html forms. And for such, I guess there is no way without using javascript to offer 3 separated inputs for date, name and function. Therefore, in my form I was relying on the traditional way of passing such data with the following form:

<div class="signature">
    <input type="text" name="signatures[][date]">
    <input type="text" name="signatures[][name]">
    <input type="text" name="signatures[][function]">
</div>
<div class="signature">
    <input type="text" name="signatures[][date]">
    <input type="text" name="signatures[][name]">
    <input type="text" name="signatures[][function]">
</div>
<div class="signature">
    <input type="text" name="signatures[][date]">
    <input type="text" name="signatures[][name]">
    <input type="text" name="signatures[][function]">
</div>

And would have expected the following parsing:

signatures:[
   {date:'', function:'', name:''},
   {date:'', function:'', name:''},
   {date:'', function:'', name:''}
]

Do you know any way of structuring a basic form without having to use js ?

luolingchun commented 1 month ago

I tested the answer from ChatGPT, and I suggest using JS.

You want your HTML form to submit the signatures data in this format:

"signatures": [
  {"date": "", "name": "", "function": ""}
]

rather than in a format like:

signatures[0][date]: "", signatures[0][name]: "", signatures[0][function]: ""

Unfortunately, plain HTML forms don't directly support submitting nested JSON-like structures. However, there is a workaround: you can use a textarea or input field to manually submit a JSON object as part of the form data.

Revised HTML Form Design Here’s how you can modify your form so that it submits signatures as a full JSON object for each entry:


<form action="/submit" method="post" enctype="multipart/form-data">
  <input type="hidden" name="header" value="true">

  <div class="signature">
    <textarea name="signatures" placeholder='{"date": "", "name": "", "function": ""}' rows="3"></textarea>
  </div>

  <div class="signature">
    <textarea name="signatures" placeholder='{"date": "", "name": "", "function": ""}' rows="3"></textarea>
  </div>

  <div class="signature">
    <textarea name="signatures" placeholder='{"date": "", "name": "", "function": ""}' rows="3"></textarea>
  </div>

  <button type="submit">Submit</button>
</form>
Ph0tonic commented 3 weeks ago

Thanks, I indeed finally decided to use a bit a js. Maybe extending the API to support such parsing would be interesting.