Coverage for tests / test_union_body_discriminator.py: 100%

28 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-04-06 01:24 +0000

1from typing import Annotated, Any, Literal 1efgh

2 

3from fastapi import FastAPI 1efgh

4from fastapi.testclient import TestClient 1efgh

5from inline_snapshot import snapshot 1efgh

6from pydantic import BaseModel, Field 1efgh

7 

8 

9def test_discriminator_pydantic_v2() -> None: 1efgh

10 from pydantic import Tag 1adbc

11 

12 app = FastAPI() 1adbc

13 

14 class FirstItem(BaseModel): 1adbc

15 value: Literal["first"] 1abc

16 price: int 1abc

17 

18 class OtherItem(BaseModel): 1adbc

19 value: Literal["other"] 1abc

20 price: float 1abc

21 

22 Item = Annotated[ 1adbc

23 Annotated[FirstItem, Tag("first")] | Annotated[OtherItem, Tag("other")], 

24 Field(discriminator="value"), 

25 ] 

26 

27 @app.post("/items/") 1adbc

28 def save_union_body_discriminator( 1adbc

29 item: Item, q: Annotated[str, Field(description="Query string")] 

30 ) -> dict[str, Any]: 

31 return {"item": item} 1adbc

32 

33 client = TestClient(app) 1adbc

34 response = client.post("/items/?q=first", json={"value": "first", "price": 100}) 1adbc

35 assert response.status_code == 200, response.text 1adbc

36 assert response.json() == {"item": {"value": "first", "price": 100}} 1adbc

37 

38 response = client.post("/items/?q=other", json={"value": "other", "price": 100.5}) 1adbc

39 assert response.status_code == 200, response.text 1adbc

40 assert response.json() == {"item": {"value": "other", "price": 100.5}} 1adbc

41 

42 response = client.get("/openapi.json") 1adbc

43 assert response.status_code == 200, response.text 1adbc

44 assert response.json() == snapshot( 1adbc

45 { 

46 "openapi": "3.1.0", 

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

48 "paths": { 

49 "/items/": { 

50 "post": { 

51 "summary": "Save Union Body Discriminator", 

52 "operationId": "save_union_body_discriminator_items__post", 

53 "parameters": [ 

54 { 

55 "name": "q", 

56 "in": "query", 

57 "required": True, 

58 "schema": { 

59 "type": "string", 

60 "description": "Query string", 

61 "title": "Q", 

62 }, 

63 } 

64 ], 

65 "requestBody": { 

66 "required": True, 

67 "content": { 

68 "application/json": { 

69 "schema": { 

70 "oneOf": [ 

71 {"$ref": "#/components/schemas/FirstItem"}, 

72 {"$ref": "#/components/schemas/OtherItem"}, 

73 ], 

74 "discriminator": { 

75 "propertyName": "value", 

76 "mapping": { 

77 "first": "#/components/schemas/FirstItem", 

78 "other": "#/components/schemas/OtherItem", 

79 }, 

80 }, 

81 "title": "Item", 

82 } 

83 } 

84 }, 

85 }, 

86 "responses": { 

87 "200": { 

88 "description": "Successful Response", 

89 "content": { 

90 "application/json": { 

91 "schema": { 

92 "type": "object", 

93 "additionalProperties": True, 

94 "title": "Response Save Union Body Discriminator Items Post", 

95 } 

96 } 

97 }, 

98 }, 

99 "422": { 

100 "description": "Validation Error", 

101 "content": { 

102 "application/json": { 

103 "schema": { 

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

105 } 

106 } 

107 }, 

108 }, 

109 }, 

110 } 

111 } 

112 }, 

113 "components": { 

114 "schemas": { 

115 "FirstItem": { 

116 "properties": { 

117 "value": { 

118 "type": "string", 

119 "const": "first", 

120 "title": "Value", 

121 }, 

122 "price": {"type": "integer", "title": "Price"}, 

123 }, 

124 "type": "object", 

125 "required": ["value", "price"], 

126 "title": "FirstItem", 

127 }, 

128 "HTTPValidationError": { 

129 "properties": { 

130 "detail": { 

131 "items": { 

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

133 }, 

134 "type": "array", 

135 "title": "Detail", 

136 } 

137 }, 

138 "type": "object", 

139 "title": "HTTPValidationError", 

140 }, 

141 "OtherItem": { 

142 "properties": { 

143 "value": { 

144 "type": "string", 

145 "const": "other", 

146 "title": "Value", 

147 }, 

148 "price": {"type": "number", "title": "Price"}, 

149 }, 

150 "type": "object", 

151 "required": ["value", "price"], 

152 "title": "OtherItem", 

153 }, 

154 "ValidationError": { 

155 "properties": { 

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

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

158 "loc": { 

159 "items": { 

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

161 }, 

162 "type": "array", 

163 "title": "Location", 

164 }, 

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

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

167 }, 

168 "type": "object", 

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

170 "title": "ValidationError", 

171 }, 

172 } 

173 }, 

174 } 

175 )