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

Nested classes result in invalid openapi.json

Open evroon opened this issue 6 months ago • 2 comments

Thanks for this package, makes generating OpenAPI specs much easier!

I couldn't find an existing issue, so here it is.

I noticed that when you put a pydantic model that models a request's body inside another class, it will result in an invalid openapi.json. Let's consider this code:

from openapi_pydantic import OpenAPI
from openapi_pydantic.util import PydanticSchema, construct_open_api_with_schema_class
from pydantic import BaseModel, Field


def construct_base_open_api() -> OpenAPI:
    return OpenAPI.model_validate(
        {
            "info": {"title": "My own API", "version": "v0.0.1"},
            "paths": {
                "/a": {
                    "post": {
                        "requestBody": {
                            "content": {
                                "application/json": {
                                    "schema": PydanticSchema(schema_class=ResourceA.Body)
                                }
                            }
                        },
                    }
                },
                "/b": {
                    "post": {
                        "requestBody": {
                            "content": {
                                "application/json": {
                                    "schema": PydanticSchema(schema_class=ResourceB.Body)
                                }
                            }
                        },
                    }
                },
            },
        }
    )


class ResourceA:
    class Body(BaseModel):
        a: str


class ResourceB:
    class Body(BaseModel):
        b: str


open_api = construct_base_open_api()
open_api = construct_open_api_with_schema_class(open_api)

print(open_api.model_dump_json(by_alias=True, exclude_none=True, indent=2))

Let's consider that there are two flask resources, and we define the body of the request inside the resource as nested class.

{
  "openapi": "3.1.1",
  "info": {
    "title": "My own API",
    "version": "v0.0.1"
  },
  "servers": [
    {
      "url": "/"
    }
  ],
  "paths": {
    "/a": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Body"
              }
            }
          },
          "required": false
        },
        "deprecated": false
      }
    },
    "/b": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Body"
              }
            }
          },
          "required": false
        },
        "deprecated": false
      }
    }
  },
  "components": {
    "schemas": {
      "__main____ResourceA__Body": {
        "properties": {
          "a": {
            "type": "string",
            "title": "A"
          }
        },
        "type": "object",
        "required": [
          "a"
        ],
        "title": "Body"
      },
      "__main____ResourceB__Body": {
        "properties": {
          "b": {
            "type": "string",
            "title": "B"
          }
        },
        "type": "object",
        "required": [
          "b"
        ],
        "title": "Body"
      }
    }
  }
}

That results in an OpenAPI where both requests reference #/components/schemas/Body, which is not defined.

So I think the reference #/components/schemas/Body, should instead be more specific, like #/components/schemas/__main____ResourceA__Body.

I hope this makes sense. Please let me know if I misunderstood something and this is not actually a bug :)

evroon avatar May 30 '25 11:05 evroon

I think it should be fixed if we replace __name__ by __qualname__ here based on https://stackoverflow.com/a/49969580

https://github.com/mike-oakley/openapi-pydantic/blob/8f2a7caa012b33f3c4dccec1ef857b51eb6fa818/openapi_pydantic/util.py#L180

I will try it and make a PR if that works

evroon avatar May 30 '25 11:05 evroon

See https://github.com/mike-oakley/openapi-pydantic/pull/68. I think we should just raise an exception if we encounter nested classes. It will be too difficult and too unreliable to try to solve this problem.

evroon avatar Sep 05 '25 10:09 evroon