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

1from typing import Any, Dict, Union 1hijklmn

2 

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

9 

10from .utils import needs_pydanticv2 1hijklmn

11 

12 

13@needs_pydanticv2 1hijklmn

14def test_discriminator_pydantic_v2() -> None: 1hijklmn

15 from pydantic import Tag 1abcdefg

16 

17 app = FastAPI() 1abcdefg

18 

19 class FirstItem(BaseModel): 1abcdefg

20 value: Literal["first"] 1abcdefg

21 price: int 1abcdefg

22 

23 class OtherItem(BaseModel): 1abcdefg

24 value: Literal["other"] 1abcdefg

25 price: float 1abcdefg

26 

27 Item = Annotated[ 1abcdefg

28 Union[Annotated[FirstItem, Tag("first")], Annotated[OtherItem, Tag("other")]], 

29 Field(discriminator="value"), 

30 ] 

31 

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

37 

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

42 

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

46 

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 )