from typing import Dict, List, Union from pydantic import BaseModel, Field from typing_extensions import Literal from openapi_pydantic import ( Discriminator, Info, MediaType, OpenAPI, Operation, PathItem, Reference, RequestBody, Response, Schema, ) from openapi_pydantic.compat import ( DEFS_KEY, PYDANTIC_MINOR_VERSION, PYDANTIC_V2, models_json_schema, v1_schema, ) from openapi_pydantic.util import PydanticSchema, construct_open_api_with_schema_class class DataAModel(BaseModel): kind: Literal["a"] class DataBModel(BaseModel): kind: Literal["b"] class RequestModel(BaseModel): data: Union[DataAModel, DataBModel] = Field(discriminator="kind") def construct_base_open_api() -> OpenAPI: return OpenAPI( info=Info( title="My own API", version="v0.0.1", ), paths={ "/ping": PathItem( post=Operation( requestBody=RequestBody( content={ "application/json": MediaType( media_type_schema=PydanticSchema( schema_class=RequestModel ) ) } ), responses={"200": Response(description="pong")}, ) ) }, ) def test_pydantic_discriminator_schema_generation() -> None: """https://github.com/kuimono/openapi-schema-pydantic/issues/8""" a_kind: Dict[str, Union[str, List[str]]] = {"title": "Kind", "type": "string"} b_kind: Dict[str, Union[str, List[str]]] = {"title": "Kind", "type": "string"} if PYDANTIC_V2: _key_map, json_schema = models_json_schema([(RequestModel, "validation")]) # In Pydantic v2, string literal types are mapped to consts. a_kind["const"] = "a" b_kind["const"] = "b" if PYDANTIC_MINOR_VERSION < 10: # Prior to 2.10, string literal types are also mapped to enums with a single entry. a_kind["enum"] = ["a"] b_kind["enum"] = ["b"] else: json_schema = v1_schema([RequestModel]) # In Pydantic v1, string literal types are mapped to enums with a single entry. a_kind["enum"] = ["a"] b_kind["enum"] = ["b"] assert json_schema == { DEFS_KEY: { "DataAModel": { "properties": { "kind": a_kind, }, "required": ["kind"], "title": "DataAModel", "type": "object", }, "DataBModel": { "properties": { "kind": b_kind, }, "required": ["kind"], "title": "DataBModel", "type": "object", }, "RequestModel": { "properties": { "data": { "oneOf": [ {"$ref": f"#/{DEFS_KEY}/DataAModel"}, {"$ref": f"#/{DEFS_KEY}/DataBModel"}, ], "discriminator": { "mapping": { "a": f"#/{DEFS_KEY}/DataAModel", "b": f"#/{DEFS_KEY}/DataBModel", }, "propertyName": "kind", }, "title": "Data", } }, "required": ["data"], "title": "RequestModel", "type": "object", }, } } def test_pydantic_discriminator_openapi_generation() -> None: """https://github.com/kuimono/openapi-schema-pydantic/issues/8""" open_api = construct_open_api_with_schema_class(construct_base_open_api()) assert open_api.components is not None assert open_api.components.schemas is not None json_schema = open_api.components.schemas["RequestModel"] assert json_schema.properties == { "data": Schema( oneOf=[ Reference( **{ "$ref": "#/components/schemas/DataAModel", "summary": None, "description": None, } ), Reference( **{ "$ref": "#/components/schemas/DataBModel", "summary": None, "description": None, } ), ], title="Data", discriminator=Discriminator( propertyName="kind", mapping={ "a": "#/components/schemas/DataAModel", "b": "#/components/schemas/DataBModel", }, ), ) }