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

[Bug] Doubly defined objects in the output specification

Open allComputableThings opened this issue 2 months ago • 0 comments

When we have:

@component
@dataclasses.dataclass
class Location:
    x: int
    y: int
@component
@dataclasses.dataclass
class UserProfile:
    name: str
    age: int
    email: str
    location: Location #Path

Location is define twice in the output schema. Once in components, once again within UserProfile.

This is deeply problematic for code generators which depend on generating a single class for both. ( openapi-generator-cli, for example, produces two Location classes and one cannot be used where the other is used).

UserProfile should have a ref to Location and Location should be added as a component whether or not it has the @component decorator:

{
   "$ref": f"#/components/schemas/{Location.__name__}"
}
import os
import dataclasses

from sanic import Sanic, text, Request
from sanic_ext.extensions.openapi import openapi
from sanic_ext.extensions.openapi.definitions import Response, RequestBody
from sanic_ext.extensions.openapi.openapi import component

# from typing import List

app = Sanic("app")

@component
@dataclasses.dataclass
class MyBody:
    email: str
    # @property
    # def something(self) -> int:
    #     print("Oh no you didn't!")
    #     return "OK2"

@component
@dataclasses.dataclass
class Location:
    x: int
    y: int


@component
@dataclasses.dataclass
class UserProfile:
    name: str
    age: int
    email: str
    # locations: List[Location] #Path
    location: Location #Path

    @property
    def something(self) -> int:
        print("Oh no you didn't!")
        return "OK"




@app.post("/")
@openapi.definition(
    body=RequestBody({"application/json": {
                    "$ref": f"#/components/schemas/{MyBody.__name__}"
                }}, required=True),  # if body else None,
    # body=RequestBody(Body, required=True),
    # summary="User profile update",
    # tag="one",
    # description=openapi.description(textwrap.dedent(func.__doc__)) if func.__doc__ else None,
    response=[  # Success,
        Response({
            "application/json": {
                "schema": {
                    "$ref": f"#/components/schemas/{UserProfile.__name__}"
                }
            }
        }, status=200)
        # Response(Failure, status=400)
    ],
)
def root(req: Request, *args, **kw):  # body:UserProfile):
    """
    Short description

    Long Description
    """
    return text("Hello")

allComputableThings avatar May 01 '24 18:05 allComputableThings