Skip to content

Commit b3d166a

Browse files
sermetegeclaude
andcommitted
Replace SQLModel with SQLAlchemy + Pydantic
- Migrated from SQLModel to pure SQLAlchemy ORM with declarative base - Separated ORM models from Pydantic schemas for better type safety - Updated all CRUD operations to use SQLAlchemy session patterns - Fixed query execution patterns (.exec() → .execute() with scalar methods) - Updated all Alembic migrations to remove SQLModel dependencies - Updated all test files to use SQLAlchemy patterns - Added httptools>=0.6.0 for Python 3.13 compatibility - Maintained API compatibility with existing endpoints 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent fd74a4d commit b3d166a

27 files changed

Lines changed: 1414 additions & 1031 deletions

.DS_Store

6 KB
Binary file not shown.

backend/app/alembic/env.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
# target_metadata = mymodel.Base.metadata
1919
# target_metadata = None
2020

21-
from app.models import SQLModel # noqa
21+
from app.models import Base # noqa
2222
from app.core.config import settings # noqa
2323

24-
target_metadata = SQLModel.metadata
24+
target_metadata = Base.metadata
2525

2626
# other values from the config, defined by the needs of env.py,
2727
# can be acquired:

backend/app/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"""
88
from alembic import op
99
import sqlalchemy as sa
10-
import sqlmodel.sql.sqltypes
10+
from sqlalchemy.dialects import postgresql
1111

1212

1313
# revision identifiers, used by Alembic.

backend/app/alembic/versions/9c0a54914c78_add_max_length_for_string_varchar_.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"""
88
from alembic import op
99
import sqlalchemy as sa
10-
import sqlmodel.sql.sqltypes
10+
from sqlalchemy.dialects import postgresql
1111

1212

1313
# revision identifiers, used by Alembic.

backend/app/alembic/versions/d98dd8ec85a3_edit_replace_id_integers_in_all_models_.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"""
88
from alembic import op
99
import sqlalchemy as sa
10-
import sqlmodel.sql.sqltypes
1110
from sqlalchemy.dialects import postgresql
1211

1312

backend/app/alembic/versions/e2412789c190_initialize_models.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
"""
88
import sqlalchemy as sa
9-
import sqlmodel.sql.sqltypes
9+
from sqlalchemy.dialects import postgresql
1010
from alembic import op
1111

1212
# revision identifiers, used by Alembic.
@@ -20,22 +20,22 @@ def upgrade():
2020
# ### commands auto generated by Alembic - please adjust! ###
2121
op.create_table(
2222
"user",
23-
sa.Column("email", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
23+
sa.Column("email", sa.String(255), nullable=False),
2424
sa.Column("is_active", sa.Boolean(), nullable=False),
2525
sa.Column("is_superuser", sa.Boolean(), nullable=False),
26-
sa.Column("full_name", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
26+
sa.Column("full_name", sa.String(255), nullable=True),
2727
sa.Column("id", sa.Integer(), nullable=False),
2828
sa.Column(
29-
"hashed_password", sqlmodel.sql.sqltypes.AutoString(), nullable=False
29+
"hashed_password", sa.String(), nullable=False
3030
),
3131
sa.PrimaryKeyConstraint("id"),
3232
)
3333
op.create_index(op.f("ix_user_email"), "user", ["email"], unique=True)
3434
op.create_table(
3535
"item",
36-
sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
36+
sa.Column("description", sa.Text(), nullable=True),
3737
sa.Column("id", sa.Integer(), nullable=False),
38-
sa.Column("title", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
38+
sa.Column("title", sa.String(255), nullable=False),
3939
sa.Column("owner_id", sa.Integer(), nullable=False),
4040
sa.ForeignKeyConstraint(
4141
["owner_id"],

backend/app/api/deps.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
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.orm import Session
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 SessionLocal
1414
from app.models import TokenPayload, User
1515

1616
reusable_oauth2 = OAuth2PasswordBearer(
@@ -19,8 +19,11 @@
1919

2020

2121
def get_db() -> Generator[Session, None, None]:
22-
with Session(engine) as session:
23-
yield session
22+
db = SessionLocal()
23+
try:
24+
yield db
25+
finally:
26+
db.close()
2427

2528

2629
SessionDep = Annotated[Session, Depends(get_db)]

backend/app/api/routes/items.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import Any
33

44
from fastapi import APIRouter, HTTPException
5-
from sqlmodel import func, select
5+
from sqlalchemy import func, select
66

77
from app.api.deps import CurrentUser, SessionDep
88
from app.models import Item, ItemCreate, ItemPublic, ItemsPublic, ItemUpdate, Message
@@ -20,23 +20,23 @@ 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 = session.execute(count_statement).scalar_one()
2424
statement = select(Item).offset(skip).limit(limit)
25-
items = session.exec(statement).all()
25+
items = session.execute(statement).scalars().all()
2626
else:
2727
count_statement = (
2828
select(func.count())
2929
.select_from(Item)
3030
.where(Item.owner_id == current_user.id)
3131
)
32-
count = session.exec(count_statement).one()
32+
count = session.execute(count_statement).scalar_one()
3333
statement = (
3434
select(Item)
3535
.where(Item.owner_id == current_user.id)
3636
.offset(skip)
3737
.limit(limit)
3838
)
39-
items = session.exec(statement).all()
39+
items = session.execute(statement).scalars().all()
4040

4141
return ItemsPublic(data=items, count=count)
4242

backend/app/api/routes/users.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import Any
33

44
from fastapi import APIRouter, Depends, HTTPException
5-
from sqlmodel import col, delete, func, select
5+
from sqlalchemy import func, select, delete
66

77
from app import crud
88
from app.api.deps import (
@@ -40,10 +40,10 @@ def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any:
4040
"""
4141

4242
count_statement = select(func.count()).select_from(User)
43-
count = session.exec(count_statement).one()
43+
count = session.execute(count_statement).scalar_one()
4444

4545
statement = select(User).offset(skip).limit(limit)
46-
users = session.exec(statement).all()
46+
users = session.execute(statement).scalars().all()
4747

4848
return UsersPublic(data=users, count=count)
4949

@@ -219,8 +219,8 @@ def delete_user(
219219
raise HTTPException(
220220
status_code=403, detail="Super users are not allowed to delete themselves"
221221
)
222-
statement = delete(Item).where(col(Item.owner_id) == user_id)
223-
session.exec(statement) # type: ignore
222+
statement = delete(Item).where(Item.owner_id == user_id)
223+
session.execute(statement)
224224
session.delete(user)
225225
session.commit()
226226
return Message(message="User deleted successfully")

backend/app/backend_pre_start.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22

3-
from sqlalchemy import Engine
4-
from sqlmodel import Session, select
3+
from sqlalchemy import Engine, select
4+
from sqlalchemy.orm import Session
55
from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
66

77
from app.core.db import engine
@@ -23,7 +23,7 @@ def init(db_engine: Engine) -> None:
2323
try:
2424
with Session(db_engine) as session:
2525
# Try to create session to check if DB is awake
26-
session.exec(select(1))
26+
session.execute(select(1))
2727
except Exception as e:
2828
logger.error(e)
2929
raise e

0 commit comments

Comments
 (0)