mike-oakley / openapi-pydantic

Pydantic OpenAPI schema implementation
Other
48 stars 7 forks source link

Incorrect schema for generic classes that override `model_generic_name` #25

Closed gorilla-seb closed 6 months ago

gorilla-seb commented 9 months ago

When using generic models that override model_generic_name to generate a custom name for their generated concrete classes, the generated schema is invalid: the $refuses the custom name, but schema does not. Script to reproduce:

from typing import Any, Generic, TypeVar

from openapi_pydantic import Info, MediaType, OpenAPI, Operation, PathItem, Response
from openapi_pydantic.util import PydanticSchema, construct_open_api_with_schema_class
from pydantic import BaseModel

T_ = TypeVar("T_")

class Foo(BaseModel):
    foo: str

class Bar(BaseModel, Generic[T_]):
    data: list[T_]

    @classmethod
    def model_parametrized_name(cls: type[Any], params: tuple[type[Any], ...]) -> str:
        return f"{params[0].__name__}Bar"

Paths = {
    "/foo": PathItem(
        get=Operation(
            operationId="getFoos",
            summary="Get all foos",
            description="Fetches a paged list of foos",
            responses={
                "200": Response(
                    description="List of foos",
                    content={
                        "application/json": MediaType(
                            schema=PydanticSchema(schema_class=Bar[Foo])
                        )
                    },
                )
            },
        ),
    )
}

spec = OpenAPI(info=Info(title="TestSchema", version="1"), paths=Paths)
oas = construct_open_api_with_schema_class(spec)
oas_json = oas.model_dump_json(by_alias=True, exclude_none=True, indent=2)
print(oas_json)

Result:

{
  "openapi": "3.1.0",
  "info": {
    "title": "TestSchema",
    "version": "1"
  },
  "servers": [
    {
      "url": "/"
    }
  ],
  "paths": {
    "/foo": {
      "get": {
        "summary": "Get all foos",
        "description": "Fetches a paged list of foos",
        "operationId": "getFoo",
        "responses": {
          "200": {
            "description": "List of foos",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FooBar"
                }
              }
            }
          }
        },
        "deprecated": false
      }
    }
  },
  "components": {
    "schemas": {
      "Bar_Foo_": {
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/Foo"
            },
            "type": "array",
            "title": "Data"
          }
        },
        "type": "object",
        "required": [
          "data"
        ],
        "title": "FooBar"
      },
      "Foo": {
        "properties": {
          "foo": {
            "type": "string",
            "title": "Foo"
          }
        },
        "type": "object",
        "required": [
          "foo"
        ],
        "title": "Foo"
      }
    }
  }
}

This may be related to https://github.com/pydantic/pydantic/issues/7376 . I've added a quick and dirty workaround here: https://github.com/gorilla-seb/openapi-pydantic/commit/099cbbc7bc6d3e6687cac4b3c74f00961e02d65f

mike-oakley commented 6 months ago

Hey @gorilla-seb - thanks for raising! I think this indeed would better be fixed in Pydantic itself - seems like there is some work already on this in https://github.com/pydantic/pydantic/issues/6304. Going to close this one out for now as that seems like the fix to wait for here 😃