Coverage for tests / test_tuples.py: 100%

57 statements  

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

1from fastapi import FastAPI, Form 1abcd

2from fastapi.testclient import TestClient 1abcd

3from inline_snapshot import snapshot 1abcd

4from pydantic import BaseModel 1abcd

5 

6app = FastAPI() 1abcd

7 

8 

9class ItemGroup(BaseModel): 1abcd

10 items: list[tuple[str, str]] 1abcd

11 

12 

13class Coordinate(BaseModel): 1abcd

14 x: float 1abcd

15 y: float 1abcd

16 

17 

18@app.post("/model-with-tuple/") 1abcd

19def post_model_with_tuple(item_group: ItemGroup): 1abcd

20 return item_group 1klm

21 

22 

23@app.post("/tuple-of-models/") 1abcd

24def post_tuple_of_models(square: tuple[Coordinate, Coordinate]): 1abcd

25 return square 1nop

26 

27 

28@app.post("/tuple-form/") 1abcd

29def hello(values: tuple[int, int] = Form()): 1abcd

30 return values 1qrs

31 

32 

33client = TestClient(app) 1abcd

34 

35 

36def test_model_with_tuple_valid(): 1abcd

37 data = {"items": [["foo", "bar"], ["baz", "whatelse"]]} 1klm

38 response = client.post("/model-with-tuple/", json=data) 1klm

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

40 assert response.json() == data 1klm

41 

42 

43def test_model_with_tuple_invalid(): 1abcd

44 data = {"items": [["foo", "bar"], ["baz", "whatelse", "too", "much"]]} 1efg

45 response = client.post("/model-with-tuple/", json=data) 1efg

46 assert response.status_code == 422, response.text 1efg

47 

48 data = {"items": [["foo", "bar"], ["baz"]]} 1efg

49 response = client.post("/model-with-tuple/", json=data) 1efg

50 assert response.status_code == 422, response.text 1efg

51 

52 

53def test_tuple_with_model_valid(): 1abcd

54 data = [{"x": 1, "y": 2}, {"x": 3, "y": 4}] 1nop

55 response = client.post("/tuple-of-models/", json=data) 1nop

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

57 assert response.json() == data 1nop

58 

59 

60def test_tuple_with_model_invalid(): 1abcd

61 data = [{"x": 1, "y": 2}, {"x": 3, "y": 4}, {"x": 5, "y": 6}] 1hij

62 response = client.post("/tuple-of-models/", json=data) 1hij

63 assert response.status_code == 422, response.text 1hij

64 

65 data = [{"x": 1, "y": 2}] 1hij

66 response = client.post("/tuple-of-models/", json=data) 1hij

67 assert response.status_code == 422, response.text 1hij

68 

69 

70def test_tuple_form_valid(): 1abcd

71 response = client.post("/tuple-form/", data={"values": ("1", "2")}) 1qrs

72 assert response.status_code == 200, response.text 1qrs

73 assert response.json() == [1, 2] 1qrs

74 

75 

76def test_tuple_form_invalid(): 1abcd

77 response = client.post("/tuple-form/", data={"values": ("1", "2", "3")}) 1tuv

78 assert response.status_code == 422, response.text 1tuv

79 

80 response = client.post("/tuple-form/", data={"values": ("1")}) 1tuv

81 assert response.status_code == 422, response.text 1tuv

82 

83 

84def test_openapi_schema(): 1abcd

85 response = client.get("/openapi.json") 1wxy

86 assert response.status_code == 200, response.text 1wxy

87 assert response.json() == snapshot( 1wxy

88 { 

89 "openapi": "3.1.0", 

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

91 "paths": { 

92 "/model-with-tuple/": { 

93 "post": { 

94 "summary": "Post Model With Tuple", 

95 "operationId": "post_model_with_tuple_model_with_tuple__post", 

96 "requestBody": { 

97 "content": { 

98 "application/json": { 

99 "schema": {"$ref": "#/components/schemas/ItemGroup"} 

100 } 

101 }, 

102 "required": True, 

103 }, 

104 "responses": { 

105 "200": { 

106 "description": "Successful Response", 

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

108 }, 

109 "422": { 

110 "description": "Validation Error", 

111 "content": { 

112 "application/json": { 

113 "schema": { 

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

115 } 

116 } 

117 }, 

118 }, 

119 }, 

120 } 

121 }, 

122 "/tuple-of-models/": { 

123 "post": { 

124 "summary": "Post Tuple Of Models", 

125 "operationId": "post_tuple_of_models_tuple_of_models__post", 

126 "requestBody": { 

127 "content": { 

128 "application/json": { 

129 "schema": { 

130 "title": "Square", 

131 "maxItems": 2, 

132 "minItems": 2, 

133 "type": "array", 

134 "prefixItems": [ 

135 {"$ref": "#/components/schemas/Coordinate"}, 

136 {"$ref": "#/components/schemas/Coordinate"}, 

137 ], 

138 } 

139 } 

140 }, 

141 "required": True, 

142 }, 

143 "responses": { 

144 "200": { 

145 "description": "Successful Response", 

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

147 }, 

148 "422": { 

149 "description": "Validation Error", 

150 "content": { 

151 "application/json": { 

152 "schema": { 

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

154 } 

155 } 

156 }, 

157 }, 

158 }, 

159 } 

160 }, 

161 "/tuple-form/": { 

162 "post": { 

163 "summary": "Hello", 

164 "operationId": "hello_tuple_form__post", 

165 "requestBody": { 

166 "content": { 

167 "application/x-www-form-urlencoded": { 

168 "schema": { 

169 "$ref": "#/components/schemas/Body_hello_tuple_form__post" 

170 } 

171 } 

172 }, 

173 "required": True, 

174 }, 

175 "responses": { 

176 "200": { 

177 "description": "Successful Response", 

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

179 }, 

180 "422": { 

181 "description": "Validation Error", 

182 "content": { 

183 "application/json": { 

184 "schema": { 

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

186 } 

187 } 

188 }, 

189 }, 

190 }, 

191 } 

192 }, 

193 }, 

194 "components": { 

195 "schemas": { 

196 "Body_hello_tuple_form__post": { 

197 "title": "Body_hello_tuple_form__post", 

198 "required": ["values"], 

199 "type": "object", 

200 "properties": { 

201 "values": { 

202 "title": "Values", 

203 "maxItems": 2, 

204 "minItems": 2, 

205 "type": "array", 

206 "prefixItems": [ 

207 {"type": "integer"}, 

208 {"type": "integer"}, 

209 ], 

210 } 

211 }, 

212 }, 

213 "Coordinate": { 

214 "title": "Coordinate", 

215 "required": ["x", "y"], 

216 "type": "object", 

217 "properties": { 

218 "x": {"title": "X", "type": "number"}, 

219 "y": {"title": "Y", "type": "number"}, 

220 }, 

221 }, 

222 "HTTPValidationError": { 

223 "title": "HTTPValidationError", 

224 "type": "object", 

225 "properties": { 

226 "detail": { 

227 "title": "Detail", 

228 "type": "array", 

229 "items": { 

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

231 }, 

232 } 

233 }, 

234 }, 

235 "ItemGroup": { 

236 "title": "ItemGroup", 

237 "required": ["items"], 

238 "type": "object", 

239 "properties": { 

240 "items": { 

241 "title": "Items", 

242 "type": "array", 

243 "items": { 

244 "maxItems": 2, 

245 "minItems": 2, 

246 "type": "array", 

247 "prefixItems": [ 

248 {"type": "string"}, 

249 {"type": "string"}, 

250 ], 

251 }, 

252 } 

253 }, 

254 }, 

255 "ValidationError": { 

256 "title": "ValidationError", 

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

258 "type": "object", 

259 "properties": { 

260 "loc": { 

261 "title": "Location", 

262 "type": "array", 

263 "items": { 

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

265 }, 

266 }, 

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

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

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

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

271 }, 

272 }, 

273 } 

274 }, 

275 } 

276 )