[BUG][Python] Wrapper models for oneOf values include metadata fields in serialized responses
Bug Report Checklist
- [ ] Have you provided a full/minimal spec to reproduce the issue?
- [ ] Have you validated the input using an OpenAPI validator?
- [x] Have you tested with the latest master to confirm the issue still exists?
- [x] Have you searched for related issues/PRs?
- [ ] What's the actual output vs expected output?
- [ ] [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
When using the python-pydantic-v2 generator, defining a schema with additionalProperties whose values are oneOf produces an intermediate wrapper model for each dictionary value.
For the API definition below, the generator produces for followingApiResponse class:
class ApiResponse(BaseModel):
"""
ApiResponse
""" # noqa: E501
message: StrictStr
extra: Optional[Dict[str, ApiResponseExtraValue]] = Field(default=None, description="Map of extras")
__properties: ClassVar[List[str]] = ["message", "extra"]
where the ApiResponseExtraValue class is generated as:
APIRESPONSEEXTRAVALUE_ONE_OF_SCHEMAS = ["ExtraAlpha", "ExtraBeta"]
class ApiResponseExtraValue(BaseModel):
"""
ApiResponseExtraValue
"""
# data type: ExtraAlpha
oneof_schema_1_validator: Optional[ExtraAlpha] = None
# data type: ExtraBeta
oneof_schema_2_validator: Optional[ExtraBeta] = None
actual_instance: Optional[Union[ExtraAlpha, ExtraBeta]] = None
one_of_schemas: List[str] = Literal["ExtraAlpha", "ExtraBeta"]
model_config = {
"validate_assignment": True,
"protected_namespaces": (),
}
Returning a ApiResponse currently serializes all fields of the wrapper model, resulting in responses like:
{
"oneof_schema_1_validator": null,
"oneof_schema_2_validator": null,
"actual_instance": {
"type": "beta",
"variant": "excel",
"label": "Descargar",
"downloadId": "68b84d830ed1dd568216e27a"
},
"one_of_schemas": ["ExtraButton", "ExtraTable"]
}
The desired output is only the actual payload:
{
"type": "beta",
"variant": "excel",
"label": "Descargar",
"downloadId": "68b84d830ed1dd568216e27a"
}
The generator creates a wrapper model for the extra property which was defined with oneOf value.
Wrapper fields like oneof_schema_1_validator, oneof_schema_2_validator, and one_of_schemas are metadata for the generator, not real API data. The generated code includes these fields in instance serialization, so FastAPI returns them in responses.
openapi-generator version
openapi-generator v7.15.0 (latest at the moment) generator: python-fastapi (pydantic v2) pydantic version: 2.11.7 (latest at the moment) fastapi version: 0.116.1 (latest at the moment)
OpenAPI declaration file content or url
openapi: 3.0.3
info:
title: Literal Bug Example
version: 1.0.0
paths: {}
components:
schemas:
ApiResponse:
type: object
additionalProperties: false
properties:
message:
type: string
example: "Hello world"
extra:
description: "Map of extras"
type: object
additionalProperties:
oneOf:
- $ref: '#/components/schemas/ExtraAlpha'
- $ref: '#/components/schemas/ExtraBeta'
required:
- message
ExtraAlpha:
type: object
required: [type, foo]
properties:
type:
type: string
enum: ["alpha"]
foo:
type: string
ExtraBeta:
type: object
required: [type, variant, label, url]
properties:
type:
type: string
enum: ["beta"]
variant:
type: string
enum: ["excel"]
label:
type: string
maxLength: 40
downloadId:
type: string
maxLength: 50
Generation Details
Steps to reproduce
Related issues/PRs
Suggest a fix
Manually converting metadata fields to ClassVar can remove them from the serialized response:
class ApiResponseExtraValue(BaseModel):
"""
ApiResponseExtraValue
"""
# data type: ExtraAlpha
oneof_schema_1_validator: ClassVar[Optional[ExtraAlpha]] = None
# data type: ExtraBeta
oneof_schema_2_validator: ClassVar[Optional[ExtraBeta]] = None
actual_instance: Optional[Union[ExtraAlpha, ExtraBeta]] = None
one_of_schemas: ClassVar[List[str]] = Literal["ExtraAlpha", "ExtraBeta"]
model_config = {
"validate_assignment": True,
"protected_namespaces": (),
}
However, the actual_instance wrapper is still serialized:
{
"actual_instance": {
"type": "beta",
"variant": "excel",
"label": "Descargar",
"downloadId": "68b943b1bb9793a88d289545"
}
}
when the response should be
{
"type": "download",
"variant": "excel",
"label": "Descargar",
"downloadId": "68b84d830ed1dd568216e27a"
}
The suggested fix is:
- Only serialize the actual payload (
actual_instance) when returning the model. - Treat the wrapper fields (
oneof_schema_1_validator,oneof_schema_2_validator, andone_of_schemas) as class-only metadata or remove them entirely.
I am facing a similar issue:
🐞 Bug Report / Feature Request: Improve oneOf Handling for FastAPI/Pydantic Summary: The current OpenAPI Generator implementation for Python (FastAPI + Pydantic) generates wrapper classes for oneOf schemas (e.g., CaseEmbeddingsInputPayloadCaseInformation) that rely on a field like actual_instance and custom deserialization logic. However, this structure is incompatible with FastAPI's request parsing flow and Pydantic's native validation pipeline, leading to broken or non-functional schema resolution.
{
"title": "Case Embeddings Input",
"type": "object",
"required": ["case_information"],
"properties": {
"case_information": {
"oneOf": [
{
"$ref": "./case_information_with_case_number.json"
},
{
"$ref": "./case_information_with_id_v2.json"
}
]
}
}
}
and my individual schemas are:
{
"title": "Case Information with case number",
"type": "object",
"required": [
"partner_id",
"case_number",
"case_category",
"case_description"
],
"properties": {
"partner_id": {
"type": "string",
"minLength": 1,
"maxLength": 30,
"description": "Partner Id"
},
"case_number": {
"type": "string",
"minLength": 1,
"maxLength": 30,
"description": "Case Id"
},
"id": {
"type": "string",
"minLength": 1,
"maxLength": 30,
"description": "Case Id",
"nullable": true
},
"case_category": {
"type": "string",
"minLength": 1,
"maxLength": 5000,
"description": "Case Category"
},
"case_description": {
"type": "string",
"minLength": 1,
"maxLength": 5000,
"description": "Case Description"
},
"case_language_identifier": {
"type": "string",
"minLength": 1,
"maxLength": 10,
"description": "Case Language Identifier"
}
}
}
Schema 2
{
"title": "Case Information with id",
"type": "object",
"required": [
"partner_id",
"id",
"case_category",
"case_description"
],
"properties": {
"partner_id": {
"type": "string",
"minLength": 1,
"maxLength": 30,
"description": "Partner Id"
},
"case_number": {
"type": "string",
"minLength": 1,
"maxLength": 30,
"description": "Case Id",
"nullable": true
},
"id": {
"type": "string",
"minLength": 1,
"maxLength": 30,
"description": "Case Id"
},
"case_category": {
"type": "string",
"minLength": 1,
"maxLength": 5000,
"description": "Case Category"
},
"case_description": {
"type": "string",
"minLength": 1,
"maxLength": 5000,
"description": "Case Description"
},
"case_language_identifier": {
"type": "string",
"minLength": 1,
"maxLength": 10,
"description": "Case Language Identifier"
}
}
}
which produces
class CaseEmbeddingsInputPayload(BaseModel):
case_information: Optional[CaseEmbeddingsInputPayloadCaseInformation] = None
Where CaseEmbeddingsInputPayloadCaseInformation wraps the oneOf logic using an actual_instance field and custom init() or from_json() methods. However:
- FastAPI does not invoke init() or from_json() during request parsing.
- The payload is passed as keyword arguments, which do not match the expected structure.
- As a result, actual_instance is never set, and validation fails silently. unless we manually set {"actual_instance": **kwargs"} in the init def.
- The input ends up being None, even though it matches one of the schemas.
Instead of generating a wrapper class, emit a native Python Union type:
class CaseEmbeddingsInputPayload(BaseModel):
case_information: Union[CaseInformationWithCaseNumber, CaseInformationWithIdV2]
which works.
Instead of generating a wrapper class, emit a native Python Union type
exactly. 👍
I have seen the same problem in my project and going to override the auto-generated models with simple Union types.