Skip to content

Commit ce00b4d

Browse files
Update database layer and all route handlers to use async SQLAlchemy patterns
1 parent 3d992e7 commit ce00b4d

7 files changed

Lines changed: 121 additions & 113 deletions

File tree

backend/app/alembic/versions/3452b8e33f88_initialize_models.py

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

backend/app/api/deps.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
1-
from collections.abc import Generator
1+
from collections.abc import AsyncGenerator
22
from typing import Annotated
33

44
import jwt
55
from fastapi import Depends, HTTPException, status
66
from fastapi.security import OAuth2PasswordBearer
77
from jwt.exceptions import InvalidTokenError
88
from pydantic import ValidationError
9-
from sqlmodel import Session
9+
from sqlalchemy.ext.asyncio import AsyncSession
1010

1111
from app.core import security
1212
from app.core.config import settings
13-
from app.core.db import engine
13+
from app.core.db import AsyncSessionLocal
1414
from app.models import TokenPayload, User
1515

1616
reusable_oauth2 = OAuth2PasswordBearer(
1717
tokenUrl=f"{settings.API_V1_STR}/login/access-token"
1818
)
1919

2020

21-
def get_db() -> Generator[Session, None, None]:
22-
with Session(engine) as session:
21+
async def get_db() -> AsyncGenerator[AsyncSession, None]:
22+
async with AsyncSessionLocal() as session:
2323
yield session
2424

2525

26-
SessionDep = Annotated[Session, Depends(get_db)]
26+
SessionDep = Annotated[AsyncSession, Depends(get_db)]
2727
TokenDep = Annotated[str, Depends(reusable_oauth2)]
2828

2929

30-
def get_current_user(session: SessionDep, token: TokenDep) -> User:
30+
async def get_current_user(session: SessionDep, token: TokenDep) -> User:
3131
try:
3232
payload = jwt.decode(
3333
token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
@@ -38,7 +38,7 @@ def get_current_user(session: SessionDep, token: TokenDep) -> User:
3838
status_code=status.HTTP_403_FORBIDDEN,
3939
detail="Could not validate credentials",
4040
)
41-
user = session.get(User, token_data.sub)
41+
user = await session.get(User, token_data.sub)
4242
if not user:
4343
raise HTTPException(status_code=404, detail="User not found")
4444
if not user.is_active:
@@ -49,7 +49,7 @@ def get_current_user(session: SessionDep, token: TokenDep) -> User:
4949
CurrentUser = Annotated[User, Depends(get_current_user)]
5050

5151

52-
def get_current_active_superuser(current_user: CurrentUser) -> User:
52+
async def get_current_active_superuser(current_user: CurrentUser) -> User:
5353
if not current_user.is_superuser:
5454
raise HTTPException(
5555
status_code=403, detail="The user doesn't have enough privileges"

backend/app/api/routes/items.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212

1313
@router.get("/", response_model=ItemsPublic)
14-
def read_items(
14+
async def read_items(
1515
session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100
1616
) -> Any:
1717
"""
@@ -20,37 +20,41 @@ def read_items(
2020

2121
if current_user.is_superuser:
2222
count_statement = select(func.count()).select_from(Item)
23-
count = session.exec(count_statement).one()
23+
count_result = await session.execute(count_statement)
24+
count = count_result.scalar()
2425
statement = (
2526
select(Item).order_by(col(Item.created_at).desc()).offset(skip).limit(limit)
2627
)
27-
items = session.exec(statement).all()
28+
result = await session.execute(statement)
29+
items = result.scalars().all()
2830
else:
2931
count_statement = (
3032
select(func.count())
3133
.select_from(Item)
3234
.where(Item.owner_id == current_user.id)
3335
)
34-
count = session.exec(count_statement).one()
36+
count_result = await session.execute(count_statement)
37+
count = count_result.scalar()
3538
statement = (
3639
select(Item)
3740
.where(Item.owner_id == current_user.id)
3841
.order_by(col(Item.created_at).desc())
3942
.offset(skip)
4043
.limit(limit)
4144
)
42-
items = session.exec(statement).all()
45+
result = await session.execute(statement)
46+
items = result.scalars().all()
4347

4448
items_public = [ItemPublic.model_validate(item) for item in items]
4549
return ItemsPublic(data=items_public, count=count)
4650

4751

4852
@router.get("/{id}", response_model=ItemPublic)
49-
def read_item(session: SessionDep, current_user: CurrentUser, id: uuid.UUID) -> Any:
53+
async def read_item(session: SessionDep, current_user: CurrentUser, id: uuid.UUID) -> Any:
5054
"""
5155
Get item by ID.
5256
"""
53-
item = session.get(Item, id)
57+
item = await session.get(Item, id)
5458
if not item:
5559
raise HTTPException(status_code=404, detail="Item not found")
5660
if not current_user.is_superuser and (item.owner_id != current_user.id):
@@ -59,21 +63,21 @@ def read_item(session: SessionDep, current_user: CurrentUser, id: uuid.UUID) ->
5963

6064

6165
@router.post("/", response_model=ItemPublic)
62-
def create_item(
66+
async def create_item(
6367
*, session: SessionDep, current_user: CurrentUser, item_in: ItemCreate
6468
) -> Any:
6569
"""
6670
Create new item.
6771
"""
6872
item = Item.model_validate(item_in, update={"owner_id": current_user.id})
6973
session.add(item)
70-
session.commit()
71-
session.refresh(item)
74+
await session.commit()
75+
await session.refresh(item)
7276
return item
7377

7478

7579
@router.put("/{id}", response_model=ItemPublic)
76-
def update_item(
80+
async def update_item(
7781
*,
7882
session: SessionDep,
7983
current_user: CurrentUser,
@@ -83,31 +87,31 @@ def update_item(
8387
"""
8488
Update an item.
8589
"""
86-
item = session.get(Item, id)
90+
item = await session.get(Item, id)
8791
if not item:
8892
raise HTTPException(status_code=404, detail="Item not found")
8993
if not current_user.is_superuser and (item.owner_id != current_user.id):
9094
raise HTTPException(status_code=403, detail="Not enough permissions")
9195
update_dict = item_in.model_dump(exclude_unset=True)
9296
item.sqlmodel_update(update_dict)
9397
session.add(item)
94-
session.commit()
95-
session.refresh(item)
98+
await session.commit()
99+
await session.refresh(item)
96100
return item
97101

98102

99103
@router.delete("/{id}")
100-
def delete_item(
104+
async def delete_item(
101105
session: SessionDep, current_user: CurrentUser, id: uuid.UUID
102106
) -> Message:
103107
"""
104108
Delete an item.
105109
"""
106-
item = session.get(Item, id)
110+
item = await session.get(Item, id)
107111
if not item:
108112
raise HTTPException(status_code=404, detail="Item not found")
109113
if not current_user.is_superuser and (item.owner_id != current_user.id):
110114
raise HTTPException(status_code=403, detail="Not enough permissions")
111115
session.delete(item)
112-
session.commit()
116+
await session.commit()
113117
return Message(message="Item deleted successfully")

backend/app/api/routes/login.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121

2222

2323
@router.post("/login/access-token")
24-
def login_access_token(
24+
async def login_access_token(
2525
session: SessionDep, form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
2626
) -> Token:
2727
"""
2828
OAuth2 compatible token login, get an access token for future requests
2929
"""
30-
user = crud.authenticate(
30+
user = await crud.authenticate(
3131
session=session, email=form_data.username, password=form_data.password
3232
)
3333
if not user:
@@ -51,11 +51,11 @@ def test_token(current_user: CurrentUser) -> Any:
5151

5252

5353
@router.post("/password-recovery/{email}")
54-
def recover_password(email: str, session: SessionDep) -> Message:
54+
async def recover_password(email: str, session: SessionDep) -> Message:
5555
"""
5656
Password Recovery
5757
"""
58-
user = crud.get_user_by_email(session=session, email=email)
58+
user = await crud.get_user_by_email(session=session, email=email)
5959

6060
# Always return the same response to prevent email enumeration attacks
6161
# Only send email if user actually exists
@@ -75,21 +75,21 @@ def recover_password(email: str, session: SessionDep) -> Message:
7575

7676

7777
@router.post("/reset-password/")
78-
def reset_password(session: SessionDep, body: NewPassword) -> Message:
78+
async def reset_password(session: SessionDep, body: NewPassword) -> Message:
7979
"""
8080
Reset password
8181
"""
8282
email = verify_password_reset_token(token=body.token)
8383
if not email:
8484
raise HTTPException(status_code=400, detail="Invalid token")
85-
user = crud.get_user_by_email(session=session, email=email)
85+
user = await crud.get_user_by_email(session=session, email=email)
8686
if not user:
8787
# Don't reveal that the user doesn't exist - use same error as invalid token
8888
raise HTTPException(status_code=400, detail="Invalid token")
8989
elif not user.is_active:
9090
raise HTTPException(status_code=400, detail="Inactive user")
9191
user_in_update = UserUpdate(password=body.new_password)
92-
crud.update_user(
92+
await crud.update_user(
9393
session=session,
9494
db_user=user,
9595
user_in=user_in_update,
@@ -102,11 +102,11 @@ def reset_password(session: SessionDep, body: NewPassword) -> Message:
102102
dependencies=[Depends(get_current_active_superuser)],
103103
response_class=HTMLResponse,
104104
)
105-
def recover_password_html_content(email: str, session: SessionDep) -> Any:
105+
async def recover_password_html_content(email: str, session: SessionDep) -> Any:
106106
"""
107107
HTML Content for Password Recovery
108108
"""
109-
user = crud.get_user_by_email(session=session, email=email)
109+
user = await crud.get_user_by_email(session=session, email=email)
110110

111111
if not user:
112112
raise HTTPException(

0 commit comments

Comments
 (0)