luolingchun / flask-openapi3

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

Tuples type as array<any> instead of a clear list of expected values #196

Open JesseDeLoore opened 2 days ago

JesseDeLoore commented 2 days ago

Environment:

When typing a property with tuple[...] I would expect the OpenAPI spec to show which objects are accepted and in which order. Currently it just shows that an array is accepted, which of course is not the case.

Below is a working example.

The relevant part of the spec is:

{'dessert': {'anyOf': [{'maxItems': 2, 'minItems': 2, 'type': 'array'},
                       {'$ref': '#/components/schemas/PumpkinPie'}],
             'title': 'Dessert'}}

When inspecting the model as generated by Pydantic you can see the options are available in "prefixItems"

'properties': {'dessert': {'anyOf': [{'maxItems': 2,
                                       'minItems': 2,
                                       'prefixItems': [{'$ref': '#/components/schemas/ApplePie'},
                                                       {'$ref': '#/components/schemas/CherryPie'}],
                                       'type': 'array'},
                                      {'$ref': '#/components/schemas/PumpkinPie'}],
                            'title': 'Dessert'}},

I'm happy to write a PR if anyone knows in which direction to look to solve this issue.

from pprint import pprint
from flask_openapi3 import Info
from flask_openapi3 import OpenAPI, Tag as APITag

from typing import Literal

from pydantic import BaseModel

info = Info(title="Pets API", version="1.0.0")
app = OpenAPI(__name__, info=info)

pets_tag = APITag(name="pets", description="My Pets")
class ApplePie(BaseModel):
    fruit: Literal['apple'] = 'apple'

class PumpkinPie(BaseModel):
    filling: Literal['pumpkin'] = 'pumpkin'

class CherryPie(BaseModel):
    filling: Literal['cherry'] = 'cherry'

class AThanksgivingDinner(BaseModel):
    dessert: tuple[ApplePie, CherryPie] | PumpkinPie

@app.get("/pets", summary="get pets", tags=[pets_tag])
def get_pets(body: AThanksgivingDinner):
    """
    to get all pets
    """
    return None

if __name__ == "__main__":
    pprint(app.api_doc['components']['schemas']['AThanksgivingDinner']['properties'])
    pprint(AThanksgivingDinner.model_json_schema(True, ref_template="#/components/schemas/{model}", mode="validation"))
luolingchun commented 2 days ago

@JesseDeLoore Thank you for your report.

Add prefixItems to the Schema to solve the problem, and you are welcome to submit a PR.

class Schema(BaseModel):
    ...
    schema_not: Optional[Union[Reference, "Schema"]] = Field(default=None, alias="not")
+  prefixItems: Optional[List[Union[Reference, "Schema"]]] = None
    items: Optional[Union[Reference, "Schema"]] = None
    ...
JesseDeLoore commented 1 day ago

https://github.com/luolingchun/flask-openapi3/pull/197

I hope I did an adequate job