refactor: replace exec_dml with CRUD operations in various files and improve session handling

This commit is contained in:
Abhimanyu Saharan
2026-02-09 02:17:34 +05:30
parent 228b99bc9b
commit fafcac1e16
12 changed files with 392 additions and 156 deletions

View File

@@ -9,7 +9,7 @@ from typing import Any, cast
from uuid import UUID, uuid4
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from sqlalchemy import asc, or_, update
from sqlalchemy import asc, or_
from sqlalchemy.sql.elements import ColumnElement
from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession
@@ -19,9 +19,9 @@ from app.api.deps import ActorContext, require_admin_or_agent, require_org_admin
from app.core.agent_tokens import generate_agent_token, hash_agent_token
from app.core.auth import AuthContext, get_auth_context
from app.core.time import utcnow
from app.db import crud
from app.db.pagination import paginate
from app.db.session import async_session_maker, get_session
from app.db.sqlmodel_exec import exec_dml
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
from app.models.activity_events import ActivityEvent
@@ -101,7 +101,7 @@ async def _require_board(
session: AsyncSession,
board_id: UUID | str | None,
*,
user: object | None = None,
user: User | None = None,
write: bool = False,
) -> Board:
if not board_id:
@@ -113,7 +113,7 @@ async def _require_board(
if board is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Board not found")
if user is not None:
await require_board_access(session, user=user, board=board, write=write) # type: ignore[arg-type]
await require_board_access(session, user=user, board=board, write=write)
return board
@@ -972,31 +972,32 @@ async def delete_agent(
agent_id=None,
)
now = datetime.now()
await exec_dml(
await crud.update_where(
session,
update(Task)
.where(col(Task.assigned_agent_id) == agent.id)
.where(col(Task.status) == "in_progress")
.values(
assigned_agent_id=None,
status="inbox",
in_progress_at=None,
updated_at=now,
),
Task,
col(Task.assigned_agent_id) == agent.id,
col(Task.status) == "in_progress",
assigned_agent_id=None,
status="inbox",
in_progress_at=None,
updated_at=now,
commit=False,
)
await exec_dml(
await crud.update_where(
session,
update(Task)
.where(col(Task.assigned_agent_id) == agent.id)
.where(col(Task.status) != "in_progress")
.values(
assigned_agent_id=None,
updated_at=now,
),
Task,
col(Task.assigned_agent_id) == agent.id,
col(Task.status) != "in_progress",
assigned_agent_id=None,
updated_at=now,
commit=False,
)
await exec_dml(
await crud.update_where(
session,
update(ActivityEvent).where(col(ActivityEvent.agent_id) == agent.id).values(agent_id=None),
ActivityEvent,
col(ActivityEvent.agent_id) == agent.id,
agent_id=None,
commit=False,
)
await session.delete(agent)
await session.commit()

View File

@@ -5,7 +5,7 @@ from typing import Any, cast
from uuid import UUID, uuid4
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import delete, func, update
from sqlalchemy import func
from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession
@@ -14,7 +14,6 @@ from app.core.time import utcnow
from app.db import crud
from app.db.pagination import paginate
from app.db.session import get_session
from app.db.sqlmodel_exec import exec_dml
from app.models.agents import Agent
from app.models.board_group_memory import BoardGroupMemory
from app.models.board_groups import BoardGroup
@@ -276,14 +275,16 @@ async def delete_board_group(
await _require_group_access(session, group_id=group_id, member=ctx.member, write=True)
# Boards reference groups, so clear the FK first to keep deletes simple.
await exec_dml(
await crud.update_where(
session,
update(Board).where(col(Board.board_group_id) == group_id).values(board_group_id=None),
Board,
col(Board.board_group_id) == group_id,
board_group_id=None,
commit=False,
)
await exec_dml(
session,
delete(BoardGroupMemory).where(col(BoardGroupMemory.board_group_id) == group_id),
await crud.delete_where(
session, BoardGroupMemory, col(BoardGroupMemory.board_group_id) == group_id, commit=False
)
await exec_dml(session, delete(BoardGroup).where(col(BoardGroup.id) == group_id))
await crud.delete_where(session, BoardGroup, col(BoardGroup.id) == group_id, commit=False)
await session.commit()
return OkResponse()

View File

@@ -4,7 +4,7 @@ import re
from uuid import UUID, uuid4
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import delete, func
from sqlalchemy import func
from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession
@@ -19,7 +19,6 @@ from app.core.time import utcnow
from app.db import crud
from app.db.pagination import paginate
from app.db.session import get_session
from app.db.sqlmodel_exec import exec_dml
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
from app.integrations.openclaw_gateway import (
OpenClawGatewayError,
@@ -307,43 +306,38 @@ async def delete_board(
) from exc
if task_ids:
await exec_dml(
session, delete(ActivityEvent).where(col(ActivityEvent.task_id).in_(task_ids))
await crud.delete_where(
session, ActivityEvent, col(ActivityEvent.task_id).in_(task_ids), commit=False
)
await exec_dml(session, delete(TaskDependency).where(col(TaskDependency.board_id) == board.id))
await exec_dml(
session, delete(TaskFingerprint).where(col(TaskFingerprint.board_id) == board.id)
)
await crud.delete_where(session, TaskDependency, col(TaskDependency.board_id) == board.id)
await crud.delete_where(session, TaskFingerprint, col(TaskFingerprint.board_id) == board.id)
# Approvals can reference tasks and agents, so delete before both.
await exec_dml(session, delete(Approval).where(col(Approval.board_id) == board.id))
await crud.delete_where(session, Approval, col(Approval.board_id) == board.id)
await exec_dml(session, delete(BoardMemory).where(col(BoardMemory.board_id) == board.id))
await exec_dml(
session,
delete(BoardOnboardingSession).where(col(BoardOnboardingSession.board_id) == board.id),
await crud.delete_where(session, BoardMemory, col(BoardMemory.board_id) == board.id)
await crud.delete_where(
session, BoardOnboardingSession, col(BoardOnboardingSession.board_id) == board.id
)
await exec_dml(
session,
delete(OrganizationBoardAccess).where(col(OrganizationBoardAccess.board_id) == board.id),
await crud.delete_where(
session, OrganizationBoardAccess, col(OrganizationBoardAccess.board_id) == board.id
)
await exec_dml(
await crud.delete_where(
session,
delete(OrganizationInviteBoardAccess).where(
col(OrganizationInviteBoardAccess.board_id) == board.id
),
OrganizationInviteBoardAccess,
col(OrganizationInviteBoardAccess.board_id) == board.id,
)
# Tasks reference agents (assigned_agent_id) and have dependents (fingerprints/dependencies), so
# delete tasks before agents.
await exec_dml(session, delete(Task).where(col(Task.board_id) == board.id))
await crud.delete_where(session, Task, col(Task.board_id) == board.id)
if agents:
agent_ids = [agent.id for agent in agents]
await exec_dml(
session, delete(ActivityEvent).where(col(ActivityEvent.agent_id).in_(agent_ids))
await crud.delete_where(
session, ActivityEvent, col(ActivityEvent.agent_id).in_(agent_ids), commit=False
)
await exec_dml(session, delete(Agent).where(col(Agent.id).in_(agent_ids)))
await crud.delete_where(session, Agent, col(Agent.id).in_(agent_ids))
await session.delete(board)
await session.commit()
return OkResponse()

View File

@@ -21,6 +21,7 @@ from app.integrations.openclaw_gateway_protocol import (
)
from app.models.boards import Board
from app.models.gateways import Gateway
from app.models.users import User
from app.schemas.common import OkResponse
from app.schemas.gateway_api import (
GatewayCommandsResponse,
@@ -43,7 +44,7 @@ async def _resolve_gateway(
gateway_token: str | None,
gateway_main_session_key: str | None,
*,
user: object | None = None,
user: User | None = None,
) -> tuple[Board | None, GatewayClientConfig, str | None]:
if gateway_url:
return (
@@ -59,8 +60,8 @@ async def _resolve_gateway(
board = await Board.objects.by_id(board_id).first(session)
if board is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Board not found")
if isinstance(user, object) and user is not None:
await require_board_access(session, user=user, board=board, write=False) # type: ignore[arg-type]
if user is not None:
await require_board_access(session, user=user, board=board, write=False)
if not board.gateway_id:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
@@ -85,7 +86,7 @@ async def _resolve_gateway(
async def _require_gateway(
session: AsyncSession, board_id: str | None, *, user: object | None = None
session: AsyncSession, board_id: str | None, *, user: User | None = None
) -> tuple[Board, GatewayClientConfig, str | None]:
board, config, main_session = await _resolve_gateway(
session,

View File

@@ -5,7 +5,7 @@ from typing import Any, Sequence
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import delete, func, update
from sqlalchemy import func
from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession
@@ -15,7 +15,6 @@ from app.core.time import utcnow
from app.db import crud
from app.db.pagination import paginate
from app.db.session import get_session
from app.db.sqlmodel_exec import exec_dml
from app.models.activity_events import ActivityEvent
from app.models.agents import Agent
from app.models.approvals import Approval
@@ -214,67 +213,85 @@ async def delete_my_org(
)
group_ids = select(BoardGroup.id).where(col(BoardGroup.organization_id) == org_id)
await exec_dml(session, delete(ActivityEvent).where(col(ActivityEvent.task_id).in_(task_ids)))
await exec_dml(session, delete(ActivityEvent).where(col(ActivityEvent.agent_id).in_(agent_ids)))
await exec_dml(
session, delete(TaskDependency).where(col(TaskDependency.board_id).in_(board_ids))
await crud.delete_where(
session, ActivityEvent, col(ActivityEvent.task_id).in_(task_ids), commit=False
)
await exec_dml(
await crud.delete_where(
session, ActivityEvent, col(ActivityEvent.agent_id).in_(agent_ids), commit=False
)
await crud.delete_where(
session, TaskDependency, col(TaskDependency.board_id).in_(board_ids), commit=False
)
await crud.delete_where(
session, TaskFingerprint, col(TaskFingerprint.board_id).in_(board_ids), commit=False
)
await crud.delete_where(session, Approval, col(Approval.board_id).in_(board_ids), commit=False)
await crud.delete_where(
session, BoardMemory, col(BoardMemory.board_id).in_(board_ids), commit=False
)
await crud.delete_where(
session,
delete(TaskFingerprint).where(col(TaskFingerprint.board_id).in_(board_ids)),
BoardOnboardingSession,
col(BoardOnboardingSession.board_id).in_(board_ids),
commit=False,
)
await exec_dml(session, delete(Approval).where(col(Approval.board_id).in_(board_ids)))
await exec_dml(session, delete(BoardMemory).where(col(BoardMemory.board_id).in_(board_ids)))
await exec_dml(
await crud.delete_where(
session,
delete(BoardOnboardingSession).where(col(BoardOnboardingSession.board_id).in_(board_ids)),
OrganizationBoardAccess,
col(OrganizationBoardAccess.board_id).in_(board_ids),
commit=False,
)
await exec_dml(
await crud.delete_where(
session,
delete(OrganizationBoardAccess).where(col(OrganizationBoardAccess.board_id).in_(board_ids)),
OrganizationInviteBoardAccess,
col(OrganizationInviteBoardAccess.board_id).in_(board_ids),
commit=False,
)
await exec_dml(
await crud.delete_where(
session,
delete(OrganizationInviteBoardAccess).where(
col(OrganizationInviteBoardAccess.board_id).in_(board_ids)
),
OrganizationBoardAccess,
col(OrganizationBoardAccess.organization_member_id).in_(member_ids),
commit=False,
)
await exec_dml(
await crud.delete_where(
session,
delete(OrganizationBoardAccess).where(
col(OrganizationBoardAccess.organization_member_id).in_(member_ids)
),
OrganizationInviteBoardAccess,
col(OrganizationInviteBoardAccess.organization_invite_id).in_(invite_ids),
commit=False,
)
await exec_dml(
await crud.delete_where(session, Task, col(Task.board_id).in_(board_ids), commit=False)
await crud.delete_where(session, Agent, col(Agent.board_id).in_(board_ids), commit=False)
await crud.delete_where(session, Board, col(Board.organization_id) == org_id, commit=False)
await crud.delete_where(
session,
delete(OrganizationInviteBoardAccess).where(
col(OrganizationInviteBoardAccess.organization_invite_id).in_(invite_ids)
),
BoardGroupMemory,
col(BoardGroupMemory.board_group_id).in_(group_ids),
commit=False,
)
await exec_dml(session, delete(Task).where(col(Task.board_id).in_(board_ids)))
await exec_dml(session, delete(Agent).where(col(Agent.board_id).in_(board_ids)))
await exec_dml(session, delete(Board).where(col(Board.organization_id) == org_id))
await exec_dml(
await crud.delete_where(
session, BoardGroup, col(BoardGroup.organization_id) == org_id, commit=False
)
await crud.delete_where(session, Gateway, col(Gateway.organization_id) == org_id, commit=False)
await crud.delete_where(
session,
delete(BoardGroupMemory).where(col(BoardGroupMemory.board_group_id).in_(group_ids)),
OrganizationInvite,
col(OrganizationInvite.organization_id) == org_id,
commit=False,
)
await exec_dml(session, delete(BoardGroup).where(col(BoardGroup.organization_id) == org_id))
await exec_dml(session, delete(Gateway).where(col(Gateway.organization_id) == org_id))
await exec_dml(
await crud.delete_where(
session,
delete(OrganizationInvite).where(col(OrganizationInvite.organization_id) == org_id),
OrganizationMember,
col(OrganizationMember.organization_id) == org_id,
commit=False,
)
await exec_dml(
await crud.update_where(
session,
delete(OrganizationMember).where(col(OrganizationMember.organization_id) == org_id),
User,
col(User.active_organization_id) == org_id,
active_organization_id=None,
commit=False,
)
await exec_dml(
session,
update(User)
.where(col(User.active_organization_id) == org_id)
.values(active_organization_id=None),
)
await exec_dml(session, delete(Organization).where(col(Organization.id) == org_id))
await crud.delete_where(session, Organization, col(Organization.id) == org_id, commit=False)
await session.commit()
return OkResponse()
@@ -425,11 +442,11 @@ async def remove_org_member(
detail="Organization must have at least one owner",
)
await exec_dml(
await crud.delete_where(
session,
delete(OrganizationBoardAccess).where(
col(OrganizationBoardAccess.organization_member_id) == member.id
),
OrganizationBoardAccess,
col(OrganizationBoardAccess.organization_member_id) == member.id,
commit=False,
)
user = await User.objects.by_id(member.user_id).first(session)
@@ -532,11 +549,11 @@ async def revoke_org_invite(
organization_id=ctx.organization.id,
invite_id=invite_id,
)
await exec_dml(
await crud.delete_where(
session,
delete(OrganizationInviteBoardAccess).where(
col(OrganizationInviteBoardAccess.organization_invite_id) == invite.id
),
OrganizationInviteBoardAccess,
col(OrganizationInviteBoardAccess.organization_invite_id) == invite.id,
commit=False,
)
await crud.delete(session, invite)
return OrganizationInviteRead.model_validate(invite, from_attributes=True)

View File

@@ -9,7 +9,7 @@ from typing import cast
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from sqlalchemy import asc, delete, desc, or_
from sqlalchemy import asc, desc, or_
from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession
from sqlmodel.sql.expression import Select
@@ -25,9 +25,9 @@ from app.api.deps import (
)
from app.core.auth import AuthContext
from app.core.time import utcnow
from app.db import crud
from app.db.pagination import paginate
from app.db.session import async_session_maker, get_session
from app.db.sqlmodel_exec import exec_dml
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
from app.models.activity_events import ActivityEvent
@@ -997,17 +997,21 @@ async def delete_task(
if auth.user is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
await require_board_access(session, user=auth.user, board=board, write=True)
await exec_dml(session, delete(ActivityEvent).where(col(ActivityEvent.task_id) == task.id))
await exec_dml(session, delete(TaskFingerprint).where(col(TaskFingerprint.task_id) == task.id))
await exec_dml(session, delete(Approval).where(col(Approval.task_id) == task.id))
await exec_dml(
await crud.delete_where(
session, ActivityEvent, col(ActivityEvent.task_id) == task.id, commit=False
)
await crud.delete_where(
session, TaskFingerprint, col(TaskFingerprint.task_id) == task.id, commit=False
)
await crud.delete_where(session, Approval, col(Approval.task_id) == task.id, commit=False)
await crud.delete_where(
session,
delete(TaskDependency).where(
or_(
col(TaskDependency.task_id) == task.id,
col(TaskDependency.depends_on_task_id) == task.id,
)
TaskDependency,
or_(
col(TaskDependency.task_id) == task.id,
col(TaskDependency.depends_on_task_id) == task.id,
),
commit=False,
)
await session.delete(task)
await session.commit()