Coverage for tests / test_union_body_discriminator.py: 100%
29 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-12 18:15 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-12 18:15 +0000
1from typing import Annotated, Any, Union 1defg
3from fastapi import FastAPI 1defg
4from fastapi.testclient import TestClient 1defg
5from inline_snapshot import snapshot 1defg
6from pydantic import BaseModel, Field 1defg
7from typing_extensions import Literal 1defg
10def test_discriminator_pydantic_v2() -> None: 1defg
11 from pydantic import Tag 1abc
13 app = FastAPI() 1abc
15 class FirstItem(BaseModel): 1abc
16 value: Literal["first"] 1abc
17 price: int 1abc
19 class OtherItem(BaseModel): 1abc
20 value: Literal["other"] 1abc
21 price: float 1abc
23 Item = Annotated[ 1abc
24 Union[Annotated[FirstItem, Tag("first")], Annotated[OtherItem, Tag("other")]],
25 Field(discriminator="value"),
26 ]
28 @app.post("/items/") 1abc
29 def save_union_body_discriminator( 1abc
30 item: Item, q: Annotated[str, Field(description="Query string")]
31 ) -> dict[str, Any]:
32 return {"item": item} 1abc
34 client = TestClient(app) 1abc
35 response = client.post("/items/?q=first", json={"value": "first", "price": 100}) 1abc
36 assert response.status_code == 200, response.text 1abc
37 assert response.json() == {"item": {"value": "first", "price": 100}} 1abc
39 response = client.post("/items/?q=other", json={"value": "other", "price": 100.5}) 1abc
40 assert response.status_code == 200, response.text 1abc
41 assert response.json() == {"item": {"value": "other", "price": 100.5}} 1abc
43 response = client.get("/openapi.json") 1abc
44 assert response.status_code == 200, response.text 1abc
45 assert response.json() == snapshot( 1abc
46 {
47 "openapi": "3.1.0",
48 "info": {"title": "FastAPI", "version": "0.1.0"},
49 "paths": {
50 "/items/": {
51 "post": {
52 "summary": "Save Union Body Discriminator",
53 "operationId": "save_union_body_discriminator_items__post",
54 "parameters": [
55 {
56 "name": "q",
57 "in": "query",
58 "required": True,
59 "schema": {
60 "type": "string",
61 "description": "Query string",
62 "title": "Q",
63 },
64 }
65 ],
66 "requestBody": {
67 "required": True,
68 "content": {
69 "application/json": {
70 "schema": {
71 "oneOf": [
72 {"$ref": "#/components/schemas/FirstItem"},
73 {"$ref": "#/components/schemas/OtherItem"},
74 ],
75 "discriminator": {
76 "propertyName": "value",
77 "mapping": {
78 "first": "#/components/schemas/FirstItem",
79 "other": "#/components/schemas/OtherItem",
80 },
81 },
82 "title": "Item",
83 }
84 }
85 },
86 },
87 "responses": {
88 "200": {
89 "description": "Successful Response",
90 "content": {
91 "application/json": {
92 "schema": {
93 "type": "object",
94 "additionalProperties": True,
95 "title": "Response Save Union Body Discriminator Items Post",
96 }
97 }
98 },
99 },
100 "422": {
101 "description": "Validation Error",
102 "content": {
103 "application/json": {
104 "schema": {
105 "$ref": "#/components/schemas/HTTPValidationError"
106 }
107 }
108 },
109 },
110 },
111 }
112 }
113 },
114 "components": {
115 "schemas": {
116 "FirstItem": {
117 "properties": {
118 "value": {
119 "type": "string",
120 "const": "first",
121 "title": "Value",
122 },
123 "price": {"type": "integer", "title": "Price"},
124 },
125 "type": "object",
126 "required": ["value", "price"],
127 "title": "FirstItem",
128 },
129 "HTTPValidationError": {
130 "properties": {
131 "detail": {
132 "items": {
133 "$ref": "#/components/schemas/ValidationError"
134 },
135 "type": "array",
136 "title": "Detail",
137 }
138 },
139 "type": "object",
140 "title": "HTTPValidationError",
141 },
142 "OtherItem": {
143 "properties": {
144 "value": {
145 "type": "string",
146 "const": "other",
147 "title": "Value",
148 },
149 "price": {"type": "number", "title": "Price"},
150 },
151 "type": "object",
152 "required": ["value", "price"],
153 "title": "OtherItem",
154 },
155 "ValidationError": {
156 "properties": {
157 "ctx": {"title": "Context", "type": "object"},
158 "input": {"title": "Input"},
159 "loc": {
160 "items": {
161 "anyOf": [{"type": "string"}, {"type": "integer"}]
162 },
163 "type": "array",
164 "title": "Location",
165 },
166 "msg": {"type": "string", "title": "Message"},
167 "type": {"type": "string", "title": "Error Type"},
168 },
169 "type": "object",
170 "required": ["loc", "msg", "type"],
171 "title": "ValidationError",
172 },
173 }
174 },
175 }
176 )