Coverage for tests/test_tutorial/test_sql_databases/test_tutorial001.py: 100%
60 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-01-13 13:38 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-01-13 13:38 +0000
1import importlib 1fghij
2import warnings 1fghij
4import pytest 1fghij
5from dirty_equals import IsDict, IsInt 1fghij
6from fastapi.testclient import TestClient 1fghij
7from inline_snapshot import snapshot 1fghij
8from sqlalchemy import StaticPool 1fghij
9from sqlmodel import SQLModel, create_engine 1fghij
10from sqlmodel.main import default_registry 1fghij
12from tests.utils import needs_py39, needs_py310 1fghij
15def clear_sqlmodel(): 1fghij
16 # Clear the tables in the metadata for the default base model
17 SQLModel.metadata.clear() 1fghij
18 # Clear the Models associated with the registry, to avoid warnings
19 default_registry.dispose() 1fghij
22@pytest.fixture( 1fghij
23 name="client",
24 params=[
25 "tutorial001",
26 pytest.param("tutorial001_py39", marks=needs_py39),
27 pytest.param("tutorial001_py310", marks=needs_py310),
28 "tutorial001_an",
29 pytest.param("tutorial001_an_py39", marks=needs_py39),
30 pytest.param("tutorial001_an_py310", marks=needs_py310),
31 ],
32)
33def get_client(request: pytest.FixtureRequest): 1fghij
34 clear_sqlmodel() 1fghij
35 # TODO: remove when updating SQL tutorial to use new lifespan API
36 with warnings.catch_warnings(record=True): 1fghij
37 warnings.simplefilter("always") 1fghij
38 mod = importlib.import_module(f"docs_src.sql_databases.{request.param}") 1fghij
39 clear_sqlmodel() 1fghij
40 importlib.reload(mod) 1fghij
41 mod.sqlite_url = "sqlite://" 1fghij
42 mod.engine = create_engine( 1fghij
43 mod.sqlite_url, connect_args={"check_same_thread": False}, poolclass=StaticPool
44 )
46 with TestClient(mod.app) as c: 1fghij
47 yield c 1fghij
50def test_crud_app(client: TestClient): 1fghij
51 # TODO: this warns that SQLModel.from_orm is deprecated in Pydantic v1, refactor
52 # this if using obj.model_validate becomes independent of Pydantic v2
53 with warnings.catch_warnings(record=True): 1abcde
54 warnings.simplefilter("always") 1abcde
55 # No heroes before creating
56 response = client.get("heroes/") 1abcde
57 assert response.status_code == 200, response.text 1abcde
58 assert response.json() == [] 1abcde
60 # Create a hero
61 response = client.post( 1abcde
62 "/heroes/",
63 json={
64 "id": 999,
65 "name": "Dead Pond",
66 "age": 30,
67 "secret_name": "Dive Wilson",
68 },
69 )
70 assert response.status_code == 200, response.text 1abcde
71 assert response.json() == snapshot( 1abcde
72 {"age": 30, "secret_name": "Dive Wilson", "id": 999, "name": "Dead Pond"}
73 )
75 # Read a hero
76 hero_id = response.json()["id"] 1abcde
77 response = client.get(f"/heroes/{hero_id}") 1abcde
78 assert response.status_code == 200, response.text 1abcde
79 assert response.json() == snapshot( 1abcde
80 {"name": "Dead Pond", "age": 30, "id": 999, "secret_name": "Dive Wilson"}
81 )
83 # Read all heroes
84 # Create more heroes first
85 response = client.post( 1abcde
86 "/heroes/",
87 json={"name": "Spider-Boy", "age": 18, "secret_name": "Pedro Parqueador"},
88 )
89 assert response.status_code == 200, response.text 1abcde
90 response = client.post( 1abcde
91 "/heroes/", json={"name": "Rusty-Man", "secret_name": "Tommy Sharp"}
92 )
93 assert response.status_code == 200, response.text 1abcde
95 response = client.get("/heroes/") 1abcde
96 assert response.status_code == 200, response.text 1abcde
97 assert response.json() == snapshot( 1abcde
98 [
99 {
100 "name": "Dead Pond",
101 "age": 30,
102 "id": IsInt(),
103 "secret_name": "Dive Wilson",
104 },
105 {
106 "name": "Spider-Boy",
107 "age": 18,
108 "id": IsInt(),
109 "secret_name": "Pedro Parqueador",
110 },
111 {
112 "name": "Rusty-Man",
113 "age": None,
114 "id": IsInt(),
115 "secret_name": "Tommy Sharp",
116 },
117 ]
118 )
120 response = client.get("/heroes/?offset=1&limit=1") 1abcde
121 assert response.status_code == 200, response.text 1abcde
122 assert response.json() == snapshot( 1abcde
123 [
124 {
125 "name": "Spider-Boy",
126 "age": 18,
127 "id": IsInt(),
128 "secret_name": "Pedro Parqueador",
129 }
130 ]
131 )
133 # Delete a hero
134 response = client.delete(f"/heroes/{hero_id}") 1abcde
135 assert response.status_code == 200, response.text 1abcde
136 assert response.json() == snapshot({"ok": True}) 1abcde
138 response = client.get(f"/heroes/{hero_id}") 1abcde
139 assert response.status_code == 404, response.text 1abcde
141 response = client.delete(f"/heroes/{hero_id}") 1abcde
142 assert response.status_code == 404, response.text 1abcde
143 assert response.json() == snapshot({"detail": "Hero not found"}) 1abcde
146def test_openapi_schema(client: TestClient): 1fghij
147 response = client.get("/openapi.json") 1klmno
148 assert response.status_code == 200, response.text 1klmno
149 assert response.json() == snapshot( 1klmno
150 {
151 "openapi": "3.1.0",
152 "info": {"title": "FastAPI", "version": "0.1.0"},
153 "paths": {
154 "/heroes/": {
155 "post": {
156 "summary": "Create Hero",
157 "operationId": "create_hero_heroes__post",
158 "requestBody": {
159 "required": True,
160 "content": {
161 "application/json": {
162 "schema": {"$ref": "#/components/schemas/Hero"}
163 }
164 },
165 },
166 "responses": {
167 "200": {
168 "description": "Successful Response",
169 "content": {
170 "application/json": {
171 "schema": {"$ref": "#/components/schemas/Hero"}
172 }
173 },
174 },
175 "422": {
176 "description": "Validation Error",
177 "content": {
178 "application/json": {
179 "schema": {
180 "$ref": "#/components/schemas/HTTPValidationError"
181 }
182 }
183 },
184 },
185 },
186 },
187 "get": {
188 "summary": "Read Heroes",
189 "operationId": "read_heroes_heroes__get",
190 "parameters": [
191 {
192 "name": "offset",
193 "in": "query",
194 "required": False,
195 "schema": {
196 "type": "integer",
197 "default": 0,
198 "title": "Offset",
199 },
200 },
201 {
202 "name": "limit",
203 "in": "query",
204 "required": False,
205 "schema": {
206 "type": "integer",
207 "maximum": 100,
208 "default": 100,
209 "title": "Limit",
210 },
211 },
212 ],
213 "responses": {
214 "200": {
215 "description": "Successful Response",
216 "content": {
217 "application/json": {
218 "schema": {
219 "type": "array",
220 "items": {
221 "$ref": "#/components/schemas/Hero"
222 },
223 "title": "Response Read Heroes Heroes Get",
224 }
225 }
226 },
227 },
228 "422": {
229 "description": "Validation Error",
230 "content": {
231 "application/json": {
232 "schema": {
233 "$ref": "#/components/schemas/HTTPValidationError"
234 }
235 }
236 },
237 },
238 },
239 },
240 },
241 "/heroes/{hero_id}": {
242 "get": {
243 "summary": "Read Hero",
244 "operationId": "read_hero_heroes__hero_id__get",
245 "parameters": [
246 {
247 "name": "hero_id",
248 "in": "path",
249 "required": True,
250 "schema": {"type": "integer", "title": "Hero Id"},
251 }
252 ],
253 "responses": {
254 "200": {
255 "description": "Successful Response",
256 "content": {
257 "application/json": {
258 "schema": {"$ref": "#/components/schemas/Hero"}
259 }
260 },
261 },
262 "422": {
263 "description": "Validation Error",
264 "content": {
265 "application/json": {
266 "schema": {
267 "$ref": "#/components/schemas/HTTPValidationError"
268 }
269 }
270 },
271 },
272 },
273 },
274 "delete": {
275 "summary": "Delete Hero",
276 "operationId": "delete_hero_heroes__hero_id__delete",
277 "parameters": [
278 {
279 "name": "hero_id",
280 "in": "path",
281 "required": True,
282 "schema": {"type": "integer", "title": "Hero Id"},
283 }
284 ],
285 "responses": {
286 "200": {
287 "description": "Successful Response",
288 "content": {"application/json": {"schema": {}}},
289 },
290 "422": {
291 "description": "Validation Error",
292 "content": {
293 "application/json": {
294 "schema": {
295 "$ref": "#/components/schemas/HTTPValidationError"
296 }
297 }
298 },
299 },
300 },
301 },
302 },
303 },
304 "components": {
305 "schemas": {
306 "HTTPValidationError": {
307 "properties": {
308 "detail": {
309 "items": {
310 "$ref": "#/components/schemas/ValidationError"
311 },
312 "type": "array",
313 "title": "Detail",
314 }
315 },
316 "type": "object",
317 "title": "HTTPValidationError",
318 },
319 "Hero": {
320 "properties": {
321 "id": IsDict(
322 {
323 "anyOf": [{"type": "integer"}, {"type": "null"}],
324 "title": "Id",
325 }
326 )
327 | IsDict(
328 # TODO: remove when deprecating Pydantic v1
329 {
330 "type": "integer",
331 "title": "Id",
332 }
333 ),
334 "name": {"type": "string", "title": "Name"},
335 "age": IsDict(
336 {
337 "anyOf": [{"type": "integer"}, {"type": "null"}],
338 "title": "Age",
339 }
340 )
341 | IsDict(
342 # TODO: remove when deprecating Pydantic v1
343 {
344 "type": "integer",
345 "title": "Age",
346 }
347 ),
348 "secret_name": {"type": "string", "title": "Secret Name"},
349 },
350 "type": "object",
351 "required": ["name", "secret_name"],
352 "title": "Hero",
353 },
354 "ValidationError": {
355 "properties": {
356 "loc": {
357 "items": {
358 "anyOf": [{"type": "string"}, {"type": "integer"}]
359 },
360 "type": "array",
361 "title": "Location",
362 },
363 "msg": {"type": "string", "title": "Message"},
364 "type": {"type": "string", "title": "Error Type"},
365 },
366 "type": "object",
367 "required": ["loc", "msg", "type"],
368 "title": "ValidationError",
369 },
370 }
371 },
372 }
373 )