pgjones / quart-schema

Quart-Schema is a Quart extension that provides schema validation and auto-generated API documentation.
MIT License
73 stars 23 forks source link

schemas are not referenced correctly in openapi.json #70

Open amarquard089 opened 7 months ago

amarquard089 commented 7 months ago

Description Currently, the openapi.json is faulty in the sense that even thought the schemas in the components property exists, they are not referenced in the requests/responses section.

Code to reproduce

from quart import Quart
from quart_schema import QuartSchema
from typing import List
from dataclasses import dataclass

app = Quart(__name__)
QuartSchema(app)

@dataclass
class Student:
    name: str

@dataclass
class Students:
    students: List[Student]

# works:
# "schema": {
#       "properties": {
#             "students": {
#                 "items": {
#                       "$ref": "#/components/schemas/Students"
#                    },
#                   "title": "Students",
#                    "type": "array"
#                     }
#  },
@app.get("/students")
@validate_response(model_class=Students, status_code=200)
async def get_students():
    return Students(students=[Student("stud1")]), 200   

# doesnt work
# expected behaviour here would be 
# "schema": {
#                "$ref": "#/components/schemas/Student"
# }
@app.get("/student")
@validate_response(model_class=Student, status_code=200)
async def get_student():
    return Student("stud1"), 200   

This might be due to pydantics GenerateJsonSchema (l. 458 in quart_schema/extensions.py), which only returns a $ref, if the model_class.pydantic_core_schema contains properties with lists of dataclasses / pydantic dataclasses. Otherwise, just the model with its field definitions is returned as json.

Environment Python: 3.11.5 Quart: 0.19.4 Quart-Schema: 0.18.0

Possible Fix I am not quite sure about this, but it might be beneficial to use

JsonSchemaMode = "validation" # either "validation" or "serialization" # see pydantic.json_schema.JsonSchemaMode
def_, schema = GenerateJsonSchema(ref_template=REF_TEMPLATE).generate_definitions(
            [(model_class.__name__, JsonSchemaMode, model_class.__pydantic_core_schema__)]
        )

def_ contains the definitions as references using the REF_TEMPLATE

assert def_ == {(model_class.__name__, JsonSchemaMode): {"$ref": "REF_TEMPLATE".format(model_class.__name__)}}
# i.e. it would look like this: {('Students', 'validation'): {'$ref': '#/components/schemas/Students'}}
# and like this if Student where used instead: {('Student', 'validation'): {'$ref': '#/components/schemas/Student'}}

schema is similar to GenerateJsonSchema(...).generate(...) but without $defs in case of nested classes. Instead it contains the nested classes with references to each other, e.g.

def_, schema = GenerateJsonSchema(ref_template=REF_TEMPLATE).generate_definitions(
            [(Students.__name__, JsonSchemaMode, Students.__pydantic_core_schema__)]
        )
schema = {
    "Student": {"properties": ...}, # same as schema from GenerateJsonSchema(...),generate(...) for Student
    "Students": {"properties": ...} # same as schema from GenerateJsonSchema(...),generate(...) for Students but without $defs
}
# and in case Student is used instead of Students:
schema = {'Student': {'properties': {...}, 'required': [...], 'title': 'Student', 'type': 'object'}}
pgjones commented 2 months ago

I don't understand the issue here, is it that it defines the schema rather than referencing it?

amarquard089 commented 2 months ago

I don't understand the issue here, is it that it defines the schema rather than referencing it?

Yeah exactly.