--- /dev/null
+from typing import Any, Dict, Union
+
+from dirty_equals import IsDict
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+from pydantic import BaseModel, Field
+from typing_extensions import Annotated, Literal
+
+from .utils import needs_pydanticv2
+
+
+@needs_pydanticv2
+def test_discriminator_pydantic_v2() -> None:
+ from pydantic import Tag
+
+ app = FastAPI()
+
+ class FirstItem(BaseModel):
+ value: Literal["first"]
+ price: int
+
+ class OtherItem(BaseModel):
+ value: Literal["other"]
+ price: float
+
+ Item = Annotated[
+ Union[Annotated[FirstItem, Tag("first")], Annotated[OtherItem, Tag("other")]],
+ Field(discriminator="value"),
+ ]
+
+ @app.post("/items/")
+ def save_union_body_discriminator(
+ item: Item, q: Annotated[str, Field(description="Query string")]
+ ) -> Dict[str, Any]:
+ return {"item": item}
+
+ client = TestClient(app)
+ response = client.post("/items/?q=first", json={"value": "first", "price": 100})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"item": {"value": "first", "price": 100}}
+
+ response = client.post("/items/?q=other", json={"value": "other", "price": 100.5})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"item": {"value": "other", "price": 100.5}}
+
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "summary": "Save Union Body Discriminator",
+ "operationId": "save_union_body_discriminator_items__post",
+ "parameters": [
+ {
+ "name": "q",
+ "in": "query",
+ "required": True,
+ "schema": {
+ "type": "string",
+ "description": "Query string",
+ "title": "Q",
+ },
+ }
+ ],
+ "requestBody": {
+ "required": True,
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {"$ref": "#/components/schemas/FirstItem"},
+ {"$ref": "#/components/schemas/OtherItem"},
+ ],
+ "discriminator": {
+ "propertyName": "value",
+ "mapping": {
+ "first": "#/components/schemas/FirstItem",
+ "other": "#/components/schemas/OtherItem",
+ },
+ },
+ "title": "Item",
+ }
+ }
+ },
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": IsDict(
+ {
+ # Pydantic 2.10, in Python 3.8
+ # TODO: remove when dropping support for Python 3.8
+ "type": "object",
+ "title": "Response Save Union Body Discriminator Items Post",
+ }
+ )
+ | IsDict(
+ {
+ "type": "object",
+ "additionalProperties": True,
+ "title": "Response Save Union Body Discriminator Items Post",
+ }
+ )
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "FirstItem": {
+ "properties": {
+ "value": {
+ "type": "string",
+ "const": "first",
+ "title": "Value",
+ },
+ "price": {"type": "integer", "title": "Price"},
+ },
+ "type": "object",
+ "required": ["value", "price"],
+ "title": "FirstItem",
+ },
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "OtherItem": {
+ "properties": {
+ "value": {
+ "type": "string",
+ "const": "other",
+ "title": "Value",
+ },
+ "price": {"type": "number", "title": "Price"},
+ },
+ "type": "object",
+ "required": ["value", "price"],
+ "title": "OtherItem",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }
+ )