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