gabcares commited on
Commit
17cdd8f
·
verified ·
1 Parent(s): 3d474fd

Update main.py

Browse files

Update SQLite Database path

Files changed (1) hide show
  1. main.py +340 -340
main.py CHANGED
@@ -1,340 +1,340 @@
1
- import os
2
- from dotenv import load_dotenv
3
-
4
- from collections.abc import AsyncIterator
5
- from contextlib import asynccontextmanager
6
-
7
- from fastapi import FastAPI
8
- from fastapi.responses import FileResponse
9
- from fastapi.staticfiles import StaticFiles
10
- from fastapi_cache import FastAPICache
11
- from fastapi_cache.backends.inmemory import InMemoryBackend
12
- # from fastapi_cache.coder import PickleCoder
13
- from fastapi_cache.decorator import cache
14
-
15
- from typing import Union, Optional, Type
16
- from utils.student import Student
17
- from utils.instructor import Instructor
18
- from utils.course import Course
19
- from utils.enrollment import Enrollment
20
-
21
- from utils.logging import logging
22
-
23
- from sqlmodel import SQLModel, select
24
- from sqlmodel.sql.expression import SelectOfScalar
25
- from sqlmodel.ext.asyncio.session import AsyncSession
26
-
27
- from sqlalchemy.ext.asyncio import create_async_engine
28
- from sqlalchemy import Engine
29
-
30
- from typing import Dict
31
-
32
-
33
- from config import (
34
- # ONE_DAY_SEC,
35
- ONE_WEEK_SEC,
36
- ENV_PATH,
37
- USE_REDIS_CACHE,
38
- USE_POSTGRES_DB,
39
- DESCRIPTION,
40
- )
41
-
42
- load_dotenv(ENV_PATH)
43
-
44
- sms_resource: Dict[str,
45
- Union[Engine, logging.Logger]] = {}
46
-
47
-
48
- @asynccontextmanager
49
- async def lifespan(_: FastAPI) -> AsyncIterator[None]:
50
- # Cache
51
- if USE_REDIS_CACHE:
52
- from redis import asyncio as aioredis
53
- from fastapi_cache.backends.redis import RedisBackend
54
- url = os.getenv("REDIS_URL")
55
- username = os.getenv("REDIS_USERNAME")
56
- password = os.getenv("REDIS_PASSWORD")
57
- redis = aioredis.from_url(url=url, username=username,
58
- password=password, encoding="utf8", decode_responses=True)
59
- FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
60
- else:
61
- # In Memory cache
62
- FastAPICache.init(InMemoryBackend())
63
-
64
- # Database
65
- if USE_POSTGRES_DB:
66
- DATABASE_URL = os.getenv("POSTGRES_URL")
67
- connect_args = {
68
- "timeout": 60
69
- }
70
- else: # sqlite
71
- DATABASE_URL = "sqlite+aiosqlite:///sms.db"
72
- # Allow a single connection to be accessed from multiple threads.
73
- connect_args = {"check_same_thread": False}
74
-
75
- # Define the async engine
76
- engine = create_async_engine(
77
- DATABASE_URL, echo=True, connect_args=connect_args)
78
-
79
- sms_resource["engine"] = engine
80
-
81
- # Startup actions: create database tables
82
- async with engine.begin() as conn:
83
- await conn.run_sync(SQLModel.metadata.create_all)
84
-
85
- # Logger
86
- logger = logging.getLogger(__name__)
87
-
88
- sms_resource["logger"] = logger
89
-
90
- yield # Application code runs here
91
-
92
- # Shutdown actions: close connections, etc.
93
- await engine.dispose()
94
-
95
-
96
- # FastAPI Object
97
- app = FastAPI(
98
- title='School Management System API',
99
- version='1.0.0',
100
- description=DESCRIPTION,
101
- lifespan=lifespan,
102
- )
103
-
104
- app.mount("/assets", StaticFiles(directory="assets"), name="assets")
105
-
106
-
107
- @app.get('/favicon.ico', include_in_schema=False)
108
- @cache(expire=ONE_WEEK_SEC, namespace='eta_favicon') # Cache for 1 week
109
- async def favicon():
110
- file_name = "favicon.ico"
111
- file_path = os.path.join(app.root_path, "assets", file_name)
112
- return FileResponse(path=file_path, headers={"Content-Disposition": "attachment; filename=" + file_name})
113
-
114
-
115
- # API
116
-
117
- OneResult = Union[Student, Instructor, Course, Enrollment]
118
- OneResultItem = Optional[OneResult]
119
-
120
- BulkResult = Dict[str, OneResult]
121
- BulkResultItem = Optional[BulkResult]
122
-
123
- Result = Union[OneResult, BulkResult]
124
- ResultItem = Union[OneResultItem, BulkResultItem]
125
-
126
-
127
- class EndpointResponse(SQLModel):
128
- execution_msg: str
129
- execution_code: int
130
- result: ResultItem
131
-
132
-
133
- class ErrorResponse(SQLModel):
134
- execution_msg: str
135
- execution_code: int
136
- error: Optional[str]
137
-
138
-
139
- # Endpoints
140
-
141
- # Status endpoint: check if api is online
142
- @app.get('/', tags=['Home'])
143
- async def status_check():
144
- return {"Status": "API is online..."}
145
-
146
-
147
- async def endpoint_output(endpoint_result: Result, code: int = 0, error: str = None) -> Union[ErrorResponse, EndpointResponse]:
148
- msg = 'Execution failed'
149
- output = ErrorResponse(**{'execution_msg': msg,
150
- 'execution_code': code, 'error': error})
151
-
152
- try:
153
- if code != 0:
154
- msg = 'Execution was successful'
155
- output = EndpointResponse(
156
- **{'execution_msg': msg,
157
- 'execution_code': code, 'result': endpoint_result}
158
- )
159
-
160
- except Exception as e:
161
- code = 0
162
- msg = 'Execution failed'
163
- errors = f"Omg, an error occurred. endpoint_output Error: {e} & endpoint_result Error: {error} & endpoint_result: {endpoint_result}"
164
- output = ErrorResponse(**{'execution_msg': msg,
165
- 'execution_code': code, 'error': errors})
166
-
167
- sms_resource["logger"].error(error)
168
-
169
- finally:
170
- return output
171
-
172
-
173
- # Caching Post requests is challenging
174
- async def sms_posts(instance: Result, idx: str = None, action: str = "add") -> Union[ErrorResponse, EndpointResponse]:
175
- async with AsyncSession(sms_resource["engine"]) as session:
176
- code = 0
177
- error = None
178
- existing = await session.get(instance.__class__, idx)
179
-
180
- # For add action, do db operation if instance is not existing. Other actions, do db operation if instance exists in db
181
- checker = existing is None if action == "add" else existing is not None
182
-
183
- try:
184
- if checker:
185
- if action == "delete":
186
- session.delete(instance)
187
- else: # add or update use add
188
- session.add(instance)
189
- await session.commit()
190
- await session.refresh(instance)
191
- code = 1
192
- except Exception as e:
193
- error = e
194
-
195
- finally:
196
- return await endpoint_output(instance, code, error)
197
-
198
-
199
- # @cache(expire=ONE_DAY_SEC, namespace='sms_gets') # Cache for 1 day
200
- async def sms_gets(sms_class: Type[Result], action: str = "first", idx: str = None, stmt: SelectOfScalar[Type[Result]] = None) -> Union[ErrorResponse, EndpointResponse]:
201
- async with AsyncSession(sms_resource["engine"]) as session:
202
- result = None
203
- error = None
204
- code = 1
205
- try:
206
- if action == "all":
207
- statement = select(sms_class) if stmt is None else stmt
208
- instance_list = (await session.exec(statement)).all()
209
- if instance_list:
210
- result = {
211
- str(instance.id): instance for instance in instance_list}
212
- elif action == "first":
213
- statement = select(sms_class).where(
214
- sms_class.id == idx) if stmt is None else stmt
215
- result = (await session.exec(statement)).first()
216
-
217
- except Exception as e:
218
- code = 0
219
- error = e
220
- finally:
221
- return await endpoint_output(result, code, error)
222
-
223
-
224
- # Student Routes
225
-
226
- @app.post('/api/v1/sms/add_student', tags=['Student'])
227
- async def add_student(student: Student) -> Union[ErrorResponse, EndpointResponse]:
228
- return await sms_posts(student, student.id, action="add")
229
-
230
-
231
- @app.put('/api/v1/sms/update_student', tags=['Student'])
232
- async def update_student(student: Student) -> Union[ErrorResponse, EndpointResponse]:
233
- return await sms_posts(student, student.id, action="update")
234
-
235
-
236
- @app.delete('/api/v1/sms/delete_student', tags=['Student'])
237
- async def delete_student(student: Student) -> Union[ErrorResponse, EndpointResponse]:
238
- return await sms_posts(student, student.id, action="delete")
239
-
240
-
241
- @app.get("/api/v1/sms/students/{id}", tags=['Student'])
242
- async def find_student(id: str) -> Union[ErrorResponse, EndpointResponse]:
243
- return await sms_gets(Student, "first", id)
244
-
245
-
246
- @app.get("/api/v1/sms/students", tags=['Student'])
247
- async def all_students() -> Union[ErrorResponse, EndpointResponse]:
248
- return await sms_gets(Student, "all")
249
-
250
-
251
- # Instructor Routes
252
-
253
- @app.post('/api/v1/sms/add_instructor', tags=['Instructor'])
254
- async def add_instructor(instructor: Instructor) -> Union[ErrorResponse, EndpointResponse]:
255
- return await sms_posts(instructor, instructor.id, action="add")
256
-
257
-
258
- @app.put('/api/v1/sms/update_instructor', tags=['Instructor'])
259
- async def update_instructor(instructor: Instructor) -> Union[ErrorResponse, EndpointResponse]:
260
- return await sms_posts(instructor, instructor.id, action="update")
261
-
262
-
263
- @app.delete('/api/v1/sms/delete_instructor', tags=['Instructor'])
264
- async def delete_student(instructor: Instructor) -> Union[ErrorResponse, EndpointResponse]:
265
- return await sms_posts(instructor, instructor.id, action="delete")
266
-
267
-
268
- @app.get("/api/v1/sms/instructors/{id}", tags=['Instructor'])
269
- async def find_instructor(id: str) -> Union[ErrorResponse, EndpointResponse]:
270
- return await sms_gets(Instructor, "first", id)
271
-
272
-
273
- @app.get("/api/v1/sms/instructors", tags=['Instructor'])
274
- async def all_instructors() -> Union[ErrorResponse, EndpointResponse]:
275
- return await sms_gets(Instructor, "all")
276
-
277
-
278
- # Course Routes
279
-
280
- @app.post('/api/v1/sms/add_course', tags=['Course'])
281
- async def add_course(course: Course) -> Union[ErrorResponse, EndpointResponse]:
282
- return await sms_posts(course, course.id, action="add")
283
-
284
-
285
- @app.put('/api/v1/sms/update_course', tags=['Course'])
286
- async def update_course(course: Course) -> Union[ErrorResponse, EndpointResponse]:
287
- return await sms_posts(course, course.id, action="update")
288
-
289
-
290
- @app.delete('/api/v1/sms/delete_course', tags=['Course'])
291
- async def delete_student(course: Course) -> Union[ErrorResponse, EndpointResponse]:
292
- return await sms_posts(course, course.id, action="delete")
293
-
294
-
295
- @app.get("/api/v1/sms/courses/{id}", tags=['Course'])
296
- async def find_course(id: str) -> Union[ErrorResponse, EndpointResponse]:
297
- return await sms_gets(Course, "first", id)
298
-
299
-
300
- @app.get("/api/v1/sms/courses", tags=['Course'])
301
- async def all_courses() -> Union[ErrorResponse, EndpointResponse]:
302
- return await sms_gets(Course, "all")
303
-
304
-
305
- # Enroll Routes
306
-
307
- @app.post('/api/v1/sms/enroll_student', tags=['Enroll'])
308
- async def enroll_student(enrollment: Enrollment) -> Union[ErrorResponse, EndpointResponse]:
309
- return await sms_posts(enrollment, enrollment.id, action="add")
310
-
311
-
312
- @app.put('/api/v1/sms/update_enrolled_student', tags=['Enroll'])
313
- async def update_enrolled_student(enrollment: Enrollment) -> Union[ErrorResponse, EndpointResponse]:
314
- return await sms_posts(enrollment, enrollment.id, action="update")
315
-
316
-
317
- @app.delete('/api/v1/sms/delete_enrolled_student', tags=['Enroll'])
318
- async def delete_enrolled_student(enrollment: Enrollment) -> Union[ErrorResponse, EndpointResponse]:
319
- return await sms_posts(enrollment, enrollment.id, action="delete")
320
-
321
-
322
- @app.get('/api/v1/sms/enrollments/{id}', tags=['Enroll'])
323
- async def find_enrollment_by_id(id: str) -> Union[ErrorResponse, EndpointResponse]:
324
- return await sms_gets(Enrollment, "first", id)
325
-
326
-
327
- @app.get('/api/v1/sms/enrollments/{student_id}', tags=['Enroll'])
328
- async def find_enrollment_by_student_id(student_id: str) -> Union[ErrorResponse, EndpointResponse]:
329
- stmt = select(Enrollment).where(Enrollment.student_id == student_id)
330
- return await sms_gets(Enrollment, action="all", stmt=stmt)
331
-
332
-
333
- @app.get('/api/v1/sms/enrollments', tags=['Enroll'])
334
- async def all_enrolled_students() -> Union[ErrorResponse, EndpointResponse]:
335
- return await sms_gets(Enrollment, "all")
336
-
337
-
338
- @app.put('/api/v1/sms/grade_student', tags=['Grade'])
339
- async def assign_grade(enrollment: Enrollment) -> Union[ErrorResponse, EndpointResponse]:
340
- return await sms_posts(enrollment, enrollment.id, action="update")
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ from collections.abc import AsyncIterator
5
+ from contextlib import asynccontextmanager
6
+
7
+ from fastapi import FastAPI
8
+ from fastapi.responses import FileResponse
9
+ from fastapi.staticfiles import StaticFiles
10
+ from fastapi_cache import FastAPICache
11
+ from fastapi_cache.backends.inmemory import InMemoryBackend
12
+ # from fastapi_cache.coder import PickleCoder
13
+ from fastapi_cache.decorator import cache
14
+
15
+ from typing import Union, Optional, Type
16
+ from utils.student import Student
17
+ from utils.instructor import Instructor
18
+ from utils.course import Course
19
+ from utils.enrollment import Enrollment
20
+
21
+ from utils.logging import logging
22
+
23
+ from sqlmodel import SQLModel, select
24
+ from sqlmodel.sql.expression import SelectOfScalar
25
+ from sqlmodel.ext.asyncio.session import AsyncSession
26
+
27
+ from sqlalchemy.ext.asyncio import create_async_engine
28
+ from sqlalchemy import Engine
29
+
30
+ from typing import Dict
31
+
32
+
33
+ from config import (
34
+ # ONE_DAY_SEC,
35
+ ONE_WEEK_SEC,
36
+ ENV_PATH,
37
+ USE_REDIS_CACHE,
38
+ USE_POSTGRES_DB,
39
+ DESCRIPTION,
40
+ )
41
+
42
+ load_dotenv(ENV_PATH)
43
+
44
+ sms_resource: Dict[str,
45
+ Union[Engine, logging.Logger]] = {}
46
+
47
+
48
+ @asynccontextmanager
49
+ async def lifespan(_: FastAPI) -> AsyncIterator[None]:
50
+ # Cache
51
+ if USE_REDIS_CACHE:
52
+ from redis import asyncio as aioredis
53
+ from fastapi_cache.backends.redis import RedisBackend
54
+ url = os.getenv("REDIS_URL")
55
+ username = os.getenv("REDIS_USERNAME")
56
+ password = os.getenv("REDIS_PASSWORD")
57
+ redis = aioredis.from_url(url=url, username=username,
58
+ password=password, encoding="utf8", decode_responses=True)
59
+ FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
60
+ else:
61
+ # In Memory cache
62
+ FastAPICache.init(InMemoryBackend())
63
+
64
+ # Database
65
+ if USE_POSTGRES_DB:
66
+ DATABASE_URL = os.getenv("POSTGRES_URL")
67
+ connect_args = {
68
+ "timeout": 60
69
+ }
70
+ else: # sqlite
71
+ DATABASE_URL = "sqlite+aiosqlite:///database/sms.db"
72
+ # Allow a single connection to be accessed from multiple threads.
73
+ connect_args = {"check_same_thread": False}
74
+
75
+ # Define the async engine
76
+ engine = create_async_engine(
77
+ DATABASE_URL, echo=True, connect_args=connect_args)
78
+
79
+ sms_resource["engine"] = engine
80
+
81
+ # Startup actions: create database tables
82
+ async with engine.begin() as conn:
83
+ await conn.run_sync(SQLModel.metadata.create_all)
84
+
85
+ # Logger
86
+ logger = logging.getLogger(__name__)
87
+
88
+ sms_resource["logger"] = logger
89
+
90
+ yield # Application code runs here
91
+
92
+ # Shutdown actions: close connections, etc.
93
+ await engine.dispose()
94
+
95
+
96
+ # FastAPI Object
97
+ app = FastAPI(
98
+ title='School Management System API',
99
+ version='1.0.0',
100
+ description=DESCRIPTION,
101
+ lifespan=lifespan,
102
+ )
103
+
104
+ app.mount("/assets", StaticFiles(directory="assets"), name="assets")
105
+
106
+
107
+ @app.get('/favicon.ico', include_in_schema=False)
108
+ @cache(expire=ONE_WEEK_SEC, namespace='eta_favicon') # Cache for 1 week
109
+ async def favicon():
110
+ file_name = "favicon.ico"
111
+ file_path = os.path.join(app.root_path, "assets", file_name)
112
+ return FileResponse(path=file_path, headers={"Content-Disposition": "attachment; filename=" + file_name})
113
+
114
+
115
+ # API
116
+
117
+ OneResult = Union[Student, Instructor, Course, Enrollment]
118
+ OneResultItem = Optional[OneResult]
119
+
120
+ BulkResult = Dict[str, OneResult]
121
+ BulkResultItem = Optional[BulkResult]
122
+
123
+ Result = Union[OneResult, BulkResult]
124
+ ResultItem = Union[OneResultItem, BulkResultItem]
125
+
126
+
127
+ class EndpointResponse(SQLModel):
128
+ execution_msg: str
129
+ execution_code: int
130
+ result: ResultItem
131
+
132
+
133
+ class ErrorResponse(SQLModel):
134
+ execution_msg: str
135
+ execution_code: int
136
+ error: Optional[str]
137
+
138
+
139
+ # Endpoints
140
+
141
+ # Status endpoint: check if api is online
142
+ @app.get('/', tags=['Home'])
143
+ async def status_check():
144
+ return {"Status": "API is online..."}
145
+
146
+
147
+ async def endpoint_output(endpoint_result: Result, code: int = 0, error: str = None) -> Union[ErrorResponse, EndpointResponse]:
148
+ msg = 'Execution failed'
149
+ output = ErrorResponse(**{'execution_msg': msg,
150
+ 'execution_code': code, 'error': error})
151
+
152
+ try:
153
+ if code != 0:
154
+ msg = 'Execution was successful'
155
+ output = EndpointResponse(
156
+ **{'execution_msg': msg,
157
+ 'execution_code': code, 'result': endpoint_result}
158
+ )
159
+
160
+ except Exception as e:
161
+ code = 0
162
+ msg = 'Execution failed'
163
+ errors = f"Omg, an error occurred. endpoint_output Error: {e} & endpoint_result Error: {error} & endpoint_result: {endpoint_result}"
164
+ output = ErrorResponse(**{'execution_msg': msg,
165
+ 'execution_code': code, 'error': errors})
166
+
167
+ sms_resource["logger"].error(error)
168
+
169
+ finally:
170
+ return output
171
+
172
+
173
+ # Caching Post requests is challenging
174
+ async def sms_posts(instance: Result, idx: str = None, action: str = "add") -> Union[ErrorResponse, EndpointResponse]:
175
+ async with AsyncSession(sms_resource["engine"]) as session:
176
+ code = 0
177
+ error = None
178
+ existing = await session.get(instance.__class__, idx)
179
+
180
+ # For add action, do db operation if instance is not existing. Other actions, do db operation if instance exists in db
181
+ checker = existing is None if action == "add" else existing is not None
182
+
183
+ try:
184
+ if checker:
185
+ if action == "delete":
186
+ session.delete(instance)
187
+ else: # add or update use add
188
+ session.add(instance)
189
+ await session.commit()
190
+ await session.refresh(instance)
191
+ code = 1
192
+ except Exception as e:
193
+ error = e
194
+
195
+ finally:
196
+ return await endpoint_output(instance, code, error)
197
+
198
+
199
+ # @cache(expire=ONE_DAY_SEC, namespace='sms_gets') # Cache for 1 day
200
+ async def sms_gets(sms_class: Type[Result], action: str = "first", idx: str = None, stmt: SelectOfScalar[Type[Result]] = None) -> Union[ErrorResponse, EndpointResponse]:
201
+ async with AsyncSession(sms_resource["engine"]) as session:
202
+ result = None
203
+ error = None
204
+ code = 1
205
+ try:
206
+ if action == "all":
207
+ statement = select(sms_class) if stmt is None else stmt
208
+ instance_list = (await session.exec(statement)).all()
209
+ if instance_list:
210
+ result = {
211
+ str(instance.id): instance for instance in instance_list}
212
+ elif action == "first":
213
+ statement = select(sms_class).where(
214
+ sms_class.id == idx) if stmt is None else stmt
215
+ result = (await session.exec(statement)).first()
216
+
217
+ except Exception as e:
218
+ code = 0
219
+ error = e
220
+ finally:
221
+ return await endpoint_output(result, code, error)
222
+
223
+
224
+ # Student Routes
225
+
226
+ @app.post('/api/v1/sms/add_student', tags=['Student'])
227
+ async def add_student(student: Student) -> Union[ErrorResponse, EndpointResponse]:
228
+ return await sms_posts(student, student.id, action="add")
229
+
230
+
231
+ @app.put('/api/v1/sms/update_student', tags=['Student'])
232
+ async def update_student(student: Student) -> Union[ErrorResponse, EndpointResponse]:
233
+ return await sms_posts(student, student.id, action="update")
234
+
235
+
236
+ @app.delete('/api/v1/sms/delete_student', tags=['Student'])
237
+ async def delete_student(student: Student) -> Union[ErrorResponse, EndpointResponse]:
238
+ return await sms_posts(student, student.id, action="delete")
239
+
240
+
241
+ @app.get("/api/v1/sms/students/{id}", tags=['Student'])
242
+ async def find_student(id: str) -> Union[ErrorResponse, EndpointResponse]:
243
+ return await sms_gets(Student, "first", id)
244
+
245
+
246
+ @app.get("/api/v1/sms/students", tags=['Student'])
247
+ async def all_students() -> Union[ErrorResponse, EndpointResponse]:
248
+ return await sms_gets(Student, "all")
249
+
250
+
251
+ # Instructor Routes
252
+
253
+ @app.post('/api/v1/sms/add_instructor', tags=['Instructor'])
254
+ async def add_instructor(instructor: Instructor) -> Union[ErrorResponse, EndpointResponse]:
255
+ return await sms_posts(instructor, instructor.id, action="add")
256
+
257
+
258
+ @app.put('/api/v1/sms/update_instructor', tags=['Instructor'])
259
+ async def update_instructor(instructor: Instructor) -> Union[ErrorResponse, EndpointResponse]:
260
+ return await sms_posts(instructor, instructor.id, action="update")
261
+
262
+
263
+ @app.delete('/api/v1/sms/delete_instructor', tags=['Instructor'])
264
+ async def delete_student(instructor: Instructor) -> Union[ErrorResponse, EndpointResponse]:
265
+ return await sms_posts(instructor, instructor.id, action="delete")
266
+
267
+
268
+ @app.get("/api/v1/sms/instructors/{id}", tags=['Instructor'])
269
+ async def find_instructor(id: str) -> Union[ErrorResponse, EndpointResponse]:
270
+ return await sms_gets(Instructor, "first", id)
271
+
272
+
273
+ @app.get("/api/v1/sms/instructors", tags=['Instructor'])
274
+ async def all_instructors() -> Union[ErrorResponse, EndpointResponse]:
275
+ return await sms_gets(Instructor, "all")
276
+
277
+
278
+ # Course Routes
279
+
280
+ @app.post('/api/v1/sms/add_course', tags=['Course'])
281
+ async def add_course(course: Course) -> Union[ErrorResponse, EndpointResponse]:
282
+ return await sms_posts(course, course.id, action="add")
283
+
284
+
285
+ @app.put('/api/v1/sms/update_course', tags=['Course'])
286
+ async def update_course(course: Course) -> Union[ErrorResponse, EndpointResponse]:
287
+ return await sms_posts(course, course.id, action="update")
288
+
289
+
290
+ @app.delete('/api/v1/sms/delete_course', tags=['Course'])
291
+ async def delete_student(course: Course) -> Union[ErrorResponse, EndpointResponse]:
292
+ return await sms_posts(course, course.id, action="delete")
293
+
294
+
295
+ @app.get("/api/v1/sms/courses/{id}", tags=['Course'])
296
+ async def find_course(id: str) -> Union[ErrorResponse, EndpointResponse]:
297
+ return await sms_gets(Course, "first", id)
298
+
299
+
300
+ @app.get("/api/v1/sms/courses", tags=['Course'])
301
+ async def all_courses() -> Union[ErrorResponse, EndpointResponse]:
302
+ return await sms_gets(Course, "all")
303
+
304
+
305
+ # Enroll Routes
306
+
307
+ @app.post('/api/v1/sms/enroll_student', tags=['Enroll'])
308
+ async def enroll_student(enrollment: Enrollment) -> Union[ErrorResponse, EndpointResponse]:
309
+ return await sms_posts(enrollment, enrollment.id, action="add")
310
+
311
+
312
+ @app.put('/api/v1/sms/update_enrolled_student', tags=['Enroll'])
313
+ async def update_enrolled_student(enrollment: Enrollment) -> Union[ErrorResponse, EndpointResponse]:
314
+ return await sms_posts(enrollment, enrollment.id, action="update")
315
+
316
+
317
+ @app.delete('/api/v1/sms/delete_enrolled_student', tags=['Enroll'])
318
+ async def delete_enrolled_student(enrollment: Enrollment) -> Union[ErrorResponse, EndpointResponse]:
319
+ return await sms_posts(enrollment, enrollment.id, action="delete")
320
+
321
+
322
+ @app.get('/api/v1/sms/enrollments/{id}', tags=['Enroll'])
323
+ async def find_enrollment_by_id(id: str) -> Union[ErrorResponse, EndpointResponse]:
324
+ return await sms_gets(Enrollment, "first", id)
325
+
326
+
327
+ @app.get('/api/v1/sms/enrollments/{student_id}', tags=['Enroll'])
328
+ async def find_enrollment_by_student_id(student_id: str) -> Union[ErrorResponse, EndpointResponse]:
329
+ stmt = select(Enrollment).where(Enrollment.student_id == student_id)
330
+ return await sms_gets(Enrollment, action="all", stmt=stmt)
331
+
332
+
333
+ @app.get('/api/v1/sms/enrollments', tags=['Enroll'])
334
+ async def all_enrolled_students() -> Union[ErrorResponse, EndpointResponse]:
335
+ return await sms_gets(Enrollment, "all")
336
+
337
+
338
+ @app.put('/api/v1/sms/grade_student', tags=['Grade'])
339
+ async def assign_grade(enrollment: Enrollment) -> Union[ErrorResponse, EndpointResponse]:
340
+ return await sms_posts(enrollment, enrollment.id, action="update")