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

1from typing import Annotated, Any, Union 1defg

2 

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

8 

9 

10def test_discriminator_pydantic_v2() -> None: 1defg

11 from pydantic import Tag 1abc

12 

13 app = FastAPI() 1abc

14 

15 class FirstItem(BaseModel): 1abc

16 value: Literal["first"] 1abc

17 price: int 1abc

18 

19 class OtherItem(BaseModel): 1abc

20 value: Literal["other"] 1abc

21 price: float 1abc

22 

23 Item = Annotated[ 1abc

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

25 Field(discriminator="value"), 

26 ] 

27 

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

33 

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

38 

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

42 

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 )