Skip to content

Commit eba9701

Browse files
authored
Merge pull request #14 from Project-CARPI/refactor-database-model
Refactor API to use new database models
2 parents 10604d8 + 75ba279 commit eba9701

File tree

11 files changed

+74
-137
lines changed

11 files changed

+74
-137
lines changed

app/db_models/__init__.py

Whitespace-only changes.

app/db_models/course.py

Lines changed: 0 additions & 11 deletions
This file was deleted.

app/db_models/course_attribute.py

Lines changed: 0 additions & 19 deletions
This file was deleted.

app/db_models/course_relationship.py

Lines changed: 0 additions & 12 deletions
This file was deleted.

app/db_models/course_restriction.py

Lines changed: 0 additions & 13 deletions
This file was deleted.

app/db_models/course_seats.py

Lines changed: 0 additions & 13 deletions
This file was deleted.

app/db_models/professor.py

Lines changed: 0 additions & 12 deletions
This file was deleted.

app/dependencies.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
from fastapi import Depends, FastAPI
66
from pydantic_settings import BaseSettings, SettingsConfigDict
77
from sqlalchemy.engine import Engine
8-
from sqlmodel import Session, SQLModel, create_engine
8+
from sqlalchemy import create_engine
9+
from sqlalchemy.orm import Session, sessionmaker
10+
import carpi_data_model.models as models
911

1012

1113
class _Settings(BaseSettings):
@@ -22,20 +24,23 @@ class _Settings(BaseSettings):
2224

2325
_settings = _Settings()
2426
_engine: Engine | None = None
27+
_session_maker: sessionmaker | None = None
2528

2629

2730
@asynccontextmanager
2831
async def lifespan_func(app: FastAPI) -> AsyncGenerator[None, None]:
2932
# Initialize database connection pool
30-
global _engine
31-
_engine = create_engine(
32-
url=f"{_settings.db_dialect}+{_settings.db_api}"
33-
+ f"://{_settings.db_username}:{_settings.db_password}"
34-
+ f"@{_settings.db_hostname}/{_settings.db_schema}",
35-
# echo=True,
36-
)
37-
# Creates tables in database based on SQLModel table models
38-
SQLModel.metadata.create_all(_engine)
33+
global _engine, _session_maker
34+
if _engine is None:
35+
_engine = create_engine(
36+
url=f"{_settings.db_dialect}+{_settings.db_api}"
37+
+ f"://{_settings.db_username}:{_settings.db_password}"
38+
+ f"@{_settings.db_hostname}/{_settings.db_schema}",
39+
# echo=True,
40+
)
41+
_session_maker = sessionmaker(_engine)
42+
# Creates tables in database based on SQLAlchemy table models
43+
models.Base.metadata.create_all(_engine)
3944

4045
yield
4146

@@ -47,7 +52,9 @@ def get_app_settings() -> _Settings:
4752

4853

4954
def get_db_session() -> Generator[Session, None, None]:
50-
with Session(_engine) as session:
55+
if _session_maker is None:
56+
raise RuntimeError("Database engine is not initialized")
57+
with _session_maker() as session:
5158
yield session
5259

5360

app/main.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from fastapi import APIRouter, FastAPI
55
from fastapi.middleware.cors import CORSMiddleware
66

7-
from app import lifespan_func
7+
from app import lifespan_func, logger
88

99

1010
def scan_and_include_routers(app: FastAPI) -> None:
@@ -15,11 +15,8 @@ def scan_and_include_routers(app: FastAPI) -> None:
1515
module = importlib.import_module(f"{package_module_name}.{module_name}")
1616
for attr_name in dir(module):
1717
attr = getattr(module, attr_name)
18-
if (
19-
isinstance(attr, APIRouter)
20-
and getattr(attr, "__module__", None) == module.__name__
21-
):
22-
18+
if isinstance(attr, APIRouter):
19+
logger.info(f"Including router from {module.__name__}")
2320
app.include_router(attr)
2421

2522

app/routers/course.py

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
from enum import Enum
22

3+
from carpi_data_model.models import (
4+
Attribute,
5+
Course,
6+
Course_Attribute,
7+
Course_Offering,
8+
Subject,
9+
)
310
from fastapi import APIRouter
4-
from sqlmodel import and_, desc, distinct, func, or_, select
5-
from sqlmodel.sql.expression import Select, SelectOfScalar
11+
from sqlalchemy import and_, desc, distinct, func, or_, select
12+
from sqlalchemy.sql import Select
613

714
from app import SessionDep
8-
from app.db_models.course import Course
9-
from app.db_models.course_attribute import Course_Attribute
10-
from app.db_models.course_seats import Course_Seats
1115

1216

1317
class CourseFilter(str, Enum):
14-
departments = "departments"
18+
subjects = "subjects"
1519
attributes = "attributes"
1620
semesters = "semesters"
1721

@@ -29,21 +33,25 @@ def search_course_query(
2933
dept_filter_regex: str,
3034
attr_filter_regex: str,
3135
sem_filter_regex: str,
32-
) -> Select | SelectOfScalar:
36+
) -> Select:
3337
return (
3438
select(
35-
Course.dept,
39+
Course.subj_code,
3640
Course.code_num,
3741
Course.title,
3842
Course.desc_text,
3943
Course.credit_min,
4044
Course.credit_max,
4145
func.group_concat(
42-
distinct(func.concat(Course_Seats.semester, " ", Course_Seats.sem_year))
46+
distinct(
47+
func.concat(Course_Offering.semester, " ", Course_Offering.sem_year)
48+
)
4349
).label("sem_list"),
44-
func.group_concat(distinct(Course_Attribute.attr)).label("attr_list"),
50+
func.group_concat(distinct(Course_Attribute.attr_code)).label("attr_list"),
4551
func.regexp_like(
46-
func.concat(Course.dept, " ", Course.code_num), search_code_regex, "i"
52+
func.concat(Course.subj_code, " ", Course.code_num),
53+
search_code_regex,
54+
"i",
4755
).label("code_match"),
4856
func.regexp_like(Course.title, search_full_regex, "i").label(
4957
"title_exact_match"
@@ -60,22 +68,22 @@ def search_course_query(
6068
),
6169
)
6270
.join(
63-
Course_Seats,
71+
Course_Offering,
6472
and_(
65-
Course.dept == Course_Seats.dept,
66-
Course.code_num == Course_Seats.code_num,
73+
Course.subj_code == Course_Offering.subj_code,
74+
Course.code_num == Course_Offering.code_num,
6775
),
6876
)
6977
.outerjoin(
7078
Course_Attribute,
7179
and_(
72-
Course.dept == Course_Attribute.dept,
80+
Course.subj_code == Course_Attribute.subj_code,
7381
Course.code_num == Course_Attribute.code_num,
7482
),
7583
)
76-
.where(func.regexp_like(Course.dept, dept_filter_regex, "i"))
84+
.where(func.regexp_like(Course.subj_code, dept_filter_regex, "i"))
7785
.group_by(
78-
Course.dept,
86+
Course.subj_code,
7987
Course.code_num,
8088
Course.title,
8189
Course.desc_text,
@@ -85,7 +93,7 @@ def search_course_query(
8593
.having(
8694
or_(
8795
func.regexp_like(
88-
func.concat(Course.dept, " ", Course.code_num),
96+
func.concat(Course.subj_code, " ", Course.code_num),
8997
search_code_regex,
9098
"i",
9199
),
@@ -96,14 +104,18 @@ def search_course_query(
96104
func.regexp_like(Course.title, search_abbrev_regex, "i"),
97105
),
98106
func.regexp_like(
99-
func.ifnull(func.group_concat(distinct(Course_Attribute.attr)), ""),
107+
func.ifnull(
108+
func.group_concat(distinct(Course_Attribute.attr_code)), ""
109+
),
100110
attr_filter_regex,
101111
"i",
102112
),
103113
func.regexp_like(
104114
func.group_concat(
105115
distinct(
106-
func.concat(Course_Seats.semester, " ", Course_Seats.sem_year)
116+
func.concat(
117+
Course_Offering.semester, " ", Course_Offering.sem_year
118+
)
107119
)
108120
),
109121
sem_filter_regex,
@@ -113,7 +125,7 @@ def search_course_query(
113125
.order_by(
114126
desc(
115127
func.regexp_like(
116-
func.concat(Course.dept, " ", Course.code_num),
128+
func.concat(Course.subj_code, " ", Course.code_num),
117129
search_code_regex,
118130
"i",
119131
)
@@ -124,7 +136,7 @@ def search_course_query(
124136
desc(func.regexp_like(Course.title, search_acronym_regex, "i")),
125137
desc(func.regexp_like(Course.title, search_abbrev_regex, "i")),
126138
Course.code_num,
127-
Course.dept,
139+
Course.subj_code,
128140
)
129141
)
130142

@@ -189,7 +201,7 @@ def search_course(
189201
regex_abbrev = regex_abbrev[:-3]
190202
else:
191203
regex_abbrev = "a^"
192-
results = session.exec(
204+
results = session.execute(
193205
search_course_query(
194206
regex_code,
195207
regex_full,
@@ -208,12 +220,12 @@ def search_course(
208220
@router.get("/filter/values/{filter}")
209221
def get_filter_values(session: SessionDep, filter: CourseFilter) -> list[str]:
210222
column = None
211-
if filter is CourseFilter.departments:
212-
column = Course.dept
223+
if filter is CourseFilter.subjects:
224+
column = Subject.subj_code
213225
elif filter is CourseFilter.attributes:
214-
column = Course_Attribute.attr
226+
column = Attribute.attr_code
215227
elif filter is CourseFilter.semesters:
216-
column = Course_Seats.semester
228+
column = Course_Offering.semester
217229
else:
218230
return None
219-
return session.exec(select(column).distinct()).all()
231+
return session.execute(select(column).distinct()).scalars().all()

0 commit comments

Comments
 (0)