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