Coverage for tests / test_union_body_discriminator_annotated.py: 100%

41 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-12 18:15 +0000

1# Ref: https://github.com/fastapi/fastapi/discussions/14495 

2 

3from typing import Annotated, Union 1abdc

4 

5import pytest 1abdc

6from fastapi import FastAPI 1abdc

7from fastapi.testclient import TestClient 1abdc

8from inline_snapshot import snapshot 1abdc

9from pydantic import BaseModel 1abdc

10 

11 

12@pytest.fixture(name="client") 1abdc

13def client_fixture() -> TestClient: 1abdc

14 from fastapi import Body 1abc

15 from pydantic import Discriminator, Tag 1abc

16 

17 class Cat(BaseModel): 1abc

18 pet_type: str = "cat" 1abc

19 meows: int 1abc

20 

21 class Dog(BaseModel): 1abc

22 pet_type: str = "dog" 1abc

23 barks: float 1abc

24 

25 def get_pet_type(v): 1abc

26 assert isinstance(v, dict) 1efg

27 return v.get("pet_type", "") 1efg

28 

29 Pet = Annotated[ 1abc

30 Union[Annotated[Cat, Tag("cat")], Annotated[Dog, Tag("dog")]], 

31 Discriminator(get_pet_type), 

32 ] 

33 

34 app = FastAPI() 1abc

35 

36 @app.post("/pet/assignment") 1abc

37 async def create_pet_assignment(pet: Pet = Body()): 1abc

38 return pet 1hij

39 

40 @app.post("/pet/annotated") 1abc

41 async def create_pet_annotated(pet: Annotated[Pet, Body()]): 1abc

42 return pet 1efg

43 

44 client = TestClient(app) 1abc

45 return client 1abc

46 

47 

48def test_union_body_discriminator_assignment(client: TestClient) -> None: 1abdc

49 response = client.post("/pet/assignment", json={"pet_type": "cat", "meows": 5}) 1hij

50 assert response.status_code == 200, response.text 1hij

51 assert response.json() == {"pet_type": "cat", "meows": 5} 1hij

52 

53 

54def test_union_body_discriminator_annotated(client: TestClient) -> None: 1abdc

55 response = client.post("/pet/annotated", json={"pet_type": "dog", "barks": 3.5}) 1efg

56 assert response.status_code == 200, response.text 1efg

57 assert response.json() == {"pet_type": "dog", "barks": 3.5} 1efg

58 

59 

60def test_openapi_schema(client: TestClient) -> None: 1abdc

61 response = client.get("/openapi.json") 1klm

62 assert response.status_code == 200, response.text 1klm

63 assert response.json() == snapshot( 1klm

64 { 

65 "openapi": "3.1.0", 

66 "info": {"title": "FastAPI", "version": "0.1.0"}, 

67 "paths": { 

68 "/pet/assignment": { 

69 "post": { 

70 "summary": "Create Pet Assignment", 

71 "operationId": "create_pet_assignment_pet_assignment_post", 

72 "requestBody": { 

73 "content": { 

74 "application/json": { 

75 "schema": { 

76 "anyOf": [ 

77 {"$ref": "#/components/schemas/Cat"}, 

78 {"$ref": "#/components/schemas/Dog"}, 

79 ], 

80 "title": "Pet", 

81 } 

82 } 

83 }, 

84 "required": True, 

85 }, 

86 "responses": { 

87 "200": { 

88 "description": "Successful Response", 

89 "content": {"application/json": {"schema": {}}}, 

90 }, 

91 "422": { 

92 "description": "Validation Error", 

93 "content": { 

94 "application/json": { 

95 "schema": { 

96 "$ref": "#/components/schemas/HTTPValidationError" 

97 } 

98 } 

99 }, 

100 }, 

101 }, 

102 } 

103 }, 

104 "/pet/annotated": { 

105 "post": { 

106 "summary": "Create Pet Annotated", 

107 "operationId": "create_pet_annotated_pet_annotated_post", 

108 "requestBody": { 

109 "content": { 

110 "application/json": { 

111 "schema": { 

112 "oneOf": [ 

113 {"$ref": "#/components/schemas/Cat"}, 

114 {"$ref": "#/components/schemas/Dog"}, 

115 ], 

116 "title": "Pet", 

117 } 

118 } 

119 }, 

120 "required": True, 

121 }, 

122 "responses": { 

123 "200": { 

124 "description": "Successful Response", 

125 "content": {"application/json": {"schema": {}}}, 

126 }, 

127 "422": { 

128 "description": "Validation Error", 

129 "content": { 

130 "application/json": { 

131 "schema": { 

132 "$ref": "#/components/schemas/HTTPValidationError" 

133 } 

134 } 

135 }, 

136 }, 

137 }, 

138 } 

139 }, 

140 }, 

141 "components": { 

142 "schemas": { 

143 "Cat": { 

144 "properties": { 

145 "pet_type": { 

146 "type": "string", 

147 "title": "Pet Type", 

148 "default": "cat", 

149 }, 

150 "meows": {"type": "integer", "title": "Meows"}, 

151 }, 

152 "type": "object", 

153 "required": ["meows"], 

154 "title": "Cat", 

155 }, 

156 "Dog": { 

157 "properties": { 

158 "pet_type": { 

159 "type": "string", 

160 "title": "Pet Type", 

161 "default": "dog", 

162 }, 

163 "barks": {"type": "number", "title": "Barks"}, 

164 }, 

165 "type": "object", 

166 "required": ["barks"], 

167 "title": "Dog", 

168 }, 

169 "HTTPValidationError": { 

170 "properties": { 

171 "detail": { 

172 "items": { 

173 "$ref": "#/components/schemas/ValidationError" 

174 }, 

175 "type": "array", 

176 "title": "Detail", 

177 } 

178 }, 

179 "type": "object", 

180 "title": "HTTPValidationError", 

181 }, 

182 "ValidationError": { 

183 "properties": { 

184 "ctx": {"title": "Context", "type": "object"}, 

185 "input": {"title": "Input"}, 

186 "loc": { 

187 "items": { 

188 "anyOf": [{"type": "string"}, {"type": "integer"}] 

189 }, 

190 "type": "array", 

191 "title": "Location", 

192 }, 

193 "msg": {"type": "string", "title": "Message"}, 

194 "type": {"type": "string", "title": "Error Type"}, 

195 }, 

196 "type": "object", 

197 "required": ["loc", "msg", "type"], 

198 "title": "ValidationError", 

199 }, 

200 } 

201 }, 

202 } 

203 )