From 8f6347dc8dee561c729b96ae444eba4c5eaf45c7 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Mon, 9 Feb 2026 20:44:05 +0530 Subject: [PATCH] refactor: simplify code formatting and improve readability across multiple files --- backend/app/api/activity.py | 10 +- backend/app/api/agent.py | 87 +++++++----------- backend/app/api/agents.py | 58 ++++++------ backend/app/api/approvals.py | 11 +-- backend/app/api/board_group_memory.py | 11 +-- backend/app/api/board_groups.py | 50 +++++----- backend/app/api/board_memory.py | 6 +- backend/app/api/board_onboarding.py | 26 +++--- backend/app/api/boards.py | 27 ++++-- backend/app/api/gateway.py | 59 +++++++----- backend/app/api/gateways.py | 17 +--- backend/app/api/metrics.py | 3 +- backend/app/api/organizations.py | 76 ++++++++++----- backend/app/api/tasks.py | 97 ++++++++------------ backend/app/core/agent_auth.py | 5 +- backend/app/core/durations.py | 4 +- backend/app/core/error_handling.py | 6 +- backend/app/core/logging.py | 3 +- backend/app/db/crud.py | 17 +--- backend/app/schemas/board_onboarding.py | 8 +- backend/app/schemas/boards.py | 4 +- backend/app/services/agent_provisioning.py | 36 ++++---- backend/app/services/board_group_snapshot.py | 11 +-- backend/app/services/board_leads.py | 6 +- backend/app/services/board_snapshot.py | 9 +- backend/app/services/mentions.py | 2 +- backend/app/services/organizations.py | 26 ++++-- backend/app/services/souls_directory.py | 10 +- backend/app/services/task_dependencies.py | 6 +- backend/app/services/template_sync.py | 19 ++-- backend/scripts/sync_gateway_templates.py | 16 +--- backend/tests/test_error_handling.py | 25 +++++ backend/tests/test_organizations_service.py | 69 +++++++++----- 33 files changed, 393 insertions(+), 427 deletions(-) diff --git a/backend/app/api/activity.py b/backend/app/api/activity.py index 53932588..780845b0 100644 --- a/backend/app/api/activity.py +++ b/backend/app/api/activity.py @@ -22,10 +22,7 @@ from app.models.activity_events import ActivityEvent from app.models.agents import Agent from app.models.boards import Board from app.models.tasks import Task -from app.schemas.activity_events import ( - ActivityEventRead, - ActivityTaskCommentFeedItemRead, -) +from app.schemas.activity_events import ActivityEventRead, ActivityTaskCommentFeedItemRead from app.schemas.pagination import DefaultLimitOffsetPage from app.services.organizations import ( OrganizationContext, @@ -198,10 +195,7 @@ async def list_task_comment_feed( def _transform(items: Sequence[Any]) -> Sequence[Any]: rows = _coerce_task_comment_rows(items) - return [ - _feed_item(event, task, board, agent) - for event, task, board, agent in rows - ] + return [_feed_item(event, task, board, agent) for event, task, board, agent in rows] return await paginate(session, statement, transformer=_transform) diff --git a/backend/app/api/agent.py b/backend/app/api/agent.py index 879d5ef5..badf311b 100644 --- a/backend/app/api/agent.py +++ b/backend/app/api/agent.py @@ -53,19 +53,9 @@ from app.schemas.gateway_coordination import ( GatewayMainAskUserResponse, ) from app.schemas.pagination import DefaultLimitOffsetPage -from app.schemas.tasks import ( - TaskCommentCreate, - TaskCommentRead, - TaskCreate, - TaskRead, - TaskUpdate, -) +from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate from app.services.activity_log import record_activity -from app.services.board_leads import ( - LeadAgentOptions, - LeadAgentRequest, - ensure_board_lead_agent, -) +from app.services.board_leads import LeadAgentOptions, LeadAgentRequest, ensure_board_lead_agent from app.services.task_dependencies import ( blocked_by_dependency_ids, dependency_status_by_id, @@ -212,7 +202,8 @@ async def _require_gateway_board( 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", + status_code=status.HTTP_404_NOT_FOUND, + detail="Board not found", ) if board.gateway_id != gateway.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) @@ -322,7 +313,8 @@ async def create_task( dependency_ids=normalized_deps, ) blocked_by = blocked_by_dependency_ids( - dependency_ids=normalized_deps, status_by_id=dep_status, + dependency_ids=normalized_deps, + status_by_id=dep_status, ) if blocked_by and (task.assigned_agent_id is not None or task.status != "inbox"): @@ -393,11 +385,7 @@ async def update_task( agent_ctx: AgentAuthContext = AGENT_CTX_DEP, ) -> TaskRead: """Update a task after board-level access checks.""" - if ( - agent_ctx.agent.board_id - and task.board_id - and agent_ctx.agent.board_id != task.board_id - ): + if agent_ctx.agent.board_id and task.board_id and agent_ctx.agent.board_id != task.board_id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) return await tasks_api.update_task( payload=payload, @@ -417,11 +405,7 @@ async def list_task_comments( agent_ctx: AgentAuthContext = AGENT_CTX_DEP, ) -> LimitOffsetPage[TaskCommentRead]: """List comments for a task visible to the authenticated agent.""" - if ( - agent_ctx.agent.board_id - and task.board_id - and agent_ctx.agent.board_id != task.board_id - ): + if agent_ctx.agent.board_id and task.board_id and agent_ctx.agent.board_id != task.board_id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) return await tasks_api.list_task_comments( task=task, @@ -430,7 +414,8 @@ async def list_task_comments( @router.post( - "/boards/{board_id}/tasks/{task_id}/comments", response_model=TaskCommentRead, + "/boards/{board_id}/tasks/{task_id}/comments", + response_model=TaskCommentRead, ) async def create_task_comment( payload: TaskCommentCreate, @@ -439,11 +424,7 @@ async def create_task_comment( agent_ctx: AgentAuthContext = AGENT_CTX_DEP, ) -> ActivityEvent: """Create a task comment on behalf of the authenticated agent.""" - if ( - agent_ctx.agent.board_id - and task.board_id - and agent_ctx.agent.board_id != task.board_id - ): + if agent_ctx.agent.board_id and task.board_id and agent_ctx.agent.board_id != task.board_id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) return await tasks_api.create_task_comment( payload=payload, @@ -454,7 +435,8 @@ async def create_task_comment( @router.get( - "/boards/{board_id}/memory", response_model=DefaultLimitOffsetPage[BoardMemoryRead], + "/boards/{board_id}/memory", + response_model=DefaultLimitOffsetPage[BoardMemoryRead], ) async def list_board_memory( is_chat: bool | None = IS_CHAT_QUERY, @@ -588,7 +570,9 @@ async def nudge_agent( config = await _gateway_config(session, board) try: await ensure_session( - target.openclaw_session_id, config=config, label=target.name, + target.openclaw_session_id, + config=config, + label=target.name, ) await send_message( message, @@ -605,7 +589,8 @@ async def nudge_agent( ) await session.commit() raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc), + status_code=status.HTTP_502_BAD_GATEWAY, + detail=str(exc), ) from exc record_activity( session, @@ -657,7 +642,8 @@ async def get_agent_soul( ) except OpenClawGatewayError as exc: raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc), + status_code=status.HTTP_502_BAD_GATEWAY, + detail=str(exc), ) from exc if isinstance(payload, str): return payload @@ -671,7 +657,8 @@ async def get_agent_soul( if isinstance(nested, str): return nested raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, detail="Invalid gateway response", + status_code=status.HTTP_502_BAD_GATEWAY, + detail="Invalid gateway response", ) @@ -712,7 +699,8 @@ async def update_agent_soul( ) except OpenClawGatewayError as exc: raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc), + status_code=status.HTTP_502_BAD_GATEWAY, + detail=str(exc), ) from exc reason = (payload.reason or "").strip() source_url = (payload.source_url or "").strip() @@ -770,9 +758,7 @@ async def ask_user_via_gateway_main( correlation = payload.correlation_id.strip() if payload.correlation_id else "" correlation_line = f"Correlation ID: {correlation}\n" if correlation else "" preferred_channel = (payload.preferred_channel or "").strip() - channel_line = ( - f"Preferred channel: {preferred_channel}\n" if preferred_channel else "" - ) + channel_line = f"Preferred channel: {preferred_channel}\n" if preferred_channel else "" tags = payload.reply_tags or ["gateway_main", "user_reply"] tags_json = json.dumps(tags) @@ -801,7 +787,10 @@ async def ask_user_via_gateway_main( try: await ensure_session(main_session_key, config=config, label="Main Agent") await send_message( - message, session_key=main_session_key, config=config, deliver=True, + message, + session_key=main_session_key, + config=config, + deliver=True, ) except OpenClawGatewayError as exc: record_activity( @@ -812,7 +801,8 @@ async def ask_user_via_gateway_main( ) await session.commit() raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc), + status_code=status.HTTP_502_BAD_GATEWAY, + detail=str(exc), ) from exc record_activity( @@ -867,11 +857,7 @@ async def message_gateway_board_lead( ) base_url = settings.base_url or "http://localhost:8000" - header = ( - "GATEWAY MAIN QUESTION" - if payload.kind == "question" - else "GATEWAY MAIN HANDOFF" - ) + header = "GATEWAY MAIN QUESTION" if payload.kind == "question" else "GATEWAY MAIN HANDOFF" correlation = payload.correlation_id.strip() if payload.correlation_id else "" correlation_line = f"Correlation ID: {correlation}\n" if correlation else "" tags = payload.reply_tags or ["gateway_main", "lead_reply"] @@ -903,7 +889,8 @@ async def message_gateway_board_lead( ) await session.commit() raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc), + status_code=status.HTTP_502_BAD_GATEWAY, + detail=str(exc), ) from exc record_activity( @@ -946,11 +933,7 @@ async def broadcast_gateway_lead_message( boards = list(await session.exec(statement)) base_url = settings.base_url or "http://localhost:8000" - header = ( - "GATEWAY MAIN QUESTION" - if payload.kind == "question" - else "GATEWAY MAIN HANDOFF" - ) + header = "GATEWAY MAIN QUESTION" if payload.kind == "question" else "GATEWAY MAIN HANDOFF" correlation = payload.correlation_id.strip() if payload.correlation_id else "" correlation_line = f"Correlation ID: {correlation}\n" if correlation else "" tags = payload.reply_tags or ["gateway_main", "lead_reply"] diff --git a/backend/app/api/agents.py b/backend/app/api/agents.py index 8085e643..b69dad35 100644 --- a/backend/app/api/agents.py +++ b/backend/app/api/agents.py @@ -23,11 +23,7 @@ from app.db import crud from app.db.pagination import paginate from app.db.session import async_session_maker, get_session from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig -from app.integrations.openclaw_gateway import ( - OpenClawGatewayError, - ensure_session, - send_message, -) +from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.models.activity_events import ActivityEvent from app.models.agents import Agent from app.models.boards import Board @@ -154,7 +150,8 @@ async def _require_board( 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", + 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) @@ -162,7 +159,8 @@ async def _require_board( async def _require_gateway( - session: AsyncSession, board: Board, + session: AsyncSession, + board: Board, ) -> tuple[Gateway, GatewayClientConfig]: if not board.gateway_id: raise HTTPException( @@ -246,7 +244,8 @@ def _coerce_agent_items(items: Sequence[Any]) -> list[Agent]: async def _find_gateway_for_main_session( - session: AsyncSession, session_key: str | None, + session: AsyncSession, + session_key: str | None, ) -> Gateway | None: if not session_key: return None @@ -306,7 +305,8 @@ async def _fetch_agent_events( async def _require_user_context( - session: AsyncSession, user: User | None, + session: AsyncSession, + user: User | None, ) -> OrganizationContext: if user is None: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) @@ -332,7 +332,8 @@ async def _require_agent_access( if not is_org_admin(ctx.member): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) gateway = await _find_gateway_for_main_session( - session, agent.openclaw_session_id, + session, + agent.openclaw_session_id, ) if gateway is None or gateway.organization_id != ctx.organization.id: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) @@ -355,7 +356,10 @@ def _record_heartbeat(session: AsyncSession, agent: Agent) -> None: def _record_instruction_failure( - session: AsyncSession, agent: Agent, error: str, action: str, + session: AsyncSession, + agent: Agent, + error: str, + action: str, ) -> None: action_label = action.replace("_", " ").capitalize() record_activity( @@ -432,10 +436,7 @@ async def _ensure_unique_agent_name( if existing_gateway: raise HTTPException( status_code=status.HTTP_409_CONFLICT, - detail=( - "An agent with this name already exists in this gateway " - "workspace." - ), + detail=("An agent with this name already exists in this gateway " "workspace."), ) desired_session_key = _build_session_key(requested_name) @@ -938,7 +939,9 @@ async def _commit_heartbeat( async def _send_wakeup_message( - agent: Agent, config: GatewayClientConfig, verb: str = "provisioned", + agent: Agent, + config: GatewayClientConfig, + verb: str = "provisioned", ) -> None: session_key = agent.openclaw_session_id or _build_session_key(agent.name) await ensure_session(session_key, config=config, label=agent.name) @@ -971,7 +974,8 @@ async def list_agents( col(Gateway.organization_id) == ctx.organization.id, ) base_filter = or_( - base_filter, col(Agent.openclaw_session_id).in_(gateway_keys), + base_filter, + col(Agent.openclaw_session_id).in_(gateway_keys), ) statement = select(Agent).where(base_filter) if board_id is not None: @@ -987,10 +991,7 @@ async def list_agents( def _transform(items: Sequence[Any]) -> Sequence[Any]: agents = _coerce_agent_items(items) - return [ - _to_agent_read(_with_computed_status(agent), main_session_keys) - for agent in agents - ] + return [_to_agent_read(_with_computed_status(agent), main_session_keys) for agent in agents] return await paginate(session, statement, transformer=_transform) @@ -1019,19 +1020,17 @@ async def stream_agents( async with async_session_maker() as stream_session: if board_id is not None: agents = await _fetch_agent_events( - stream_session, board_id, last_seen, + stream_session, + board_id, + last_seen, ) elif allowed_ids: agents = await _fetch_agent_events(stream_session, None, last_seen) - agents = [ - agent for agent in agents if agent.board_id in allowed_ids - ] + agents = [agent for agent in agents if agent.board_id in allowed_ids] else: agents = [] main_session_keys = ( - await _get_gateway_main_session_keys(stream_session) - if agents - else set() + await _get_gateway_main_session_keys(stream_session) if agents else set() ) for agent in agents: updated_at = agent.updated_at or agent.last_seen_at or utcnow() @@ -1252,7 +1251,8 @@ async def delete_agent( await _require_agent_access(session, agent=agent, ctx=ctx, write=True) board = await _require_board( - session, str(agent.board_id) if agent.board_id else None, + session, + str(agent.board_id) if agent.board_id else None, ) gateway, client_config = await _require_gateway(session, board) try: diff --git a/backend/app/api/approvals.py b/backend/app/api/approvals.py index 064c8833..e904e8fe 100644 --- a/backend/app/api/approvals.py +++ b/backend/app/api/approvals.py @@ -24,12 +24,7 @@ from app.core.time import utcnow from app.db.pagination import paginate from app.db.session import async_session_maker, get_session from app.models.approvals import Approval -from app.schemas.approvals import ( - ApprovalCreate, - ApprovalRead, - ApprovalStatus, - ApprovalUpdate, -) +from app.schemas.approvals import ApprovalCreate, ApprovalRead, ApprovalStatus, ApprovalUpdate from app.schemas.pagination import DefaultLimitOffsetPage if TYPE_CHECKING: @@ -156,9 +151,7 @@ async def stream_approvals( ).one(), ) task_ids = { - approval.task_id - for approval in approvals - if approval.task_id is not None + approval.task_id for approval in approvals if approval.task_id is not None } counts_by_task_id: dict[UUID, tuple[int, int]] = {} if task_ids: diff --git a/backend/app/api/board_group_memory.py b/backend/app/api/board_group_memory.py index 7943bd39..659e63a8 100644 --- a/backend/app/api/board_group_memory.py +++ b/backend/app/api/board_group_memory.py @@ -26,21 +26,14 @@ from app.core.time import utcnow from app.db.pagination import paginate from app.db.session import async_session_maker, get_session from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig -from app.integrations.openclaw_gateway import ( - OpenClawGatewayError, - ensure_session, - send_message, -) +from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.models.agents import Agent from app.models.board_group_memory import BoardGroupMemory from app.models.board_groups import BoardGroup from app.models.boards import Board from app.models.gateways import Gateway from app.models.users import User -from app.schemas.board_group_memory import ( - BoardGroupMemoryCreate, - BoardGroupMemoryRead, -) +from app.schemas.board_group_memory import BoardGroupMemoryCreate, BoardGroupMemoryRead from app.schemas.pagination import DefaultLimitOffsetPage from app.services.mentions import extract_mentions, matches_agent_mention from app.services.organizations import ( diff --git a/backend/app/api/board_groups.py b/backend/app/api/board_groups.py index 9ac20b96..84bc5906 100644 --- a/backend/app/api/board_groups.py +++ b/backend/app/api/board_groups.py @@ -10,12 +10,7 @@ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import func from sqlmodel import col, select -from app.api.deps import ( - ActorContext, - require_admin_or_agent, - require_org_admin, - require_org_member, -) +from app.api.deps import ActorContext, require_admin_or_agent, require_org_admin, require_org_member from app.core.time import utcnow from app.db import crud from app.db.pagination import paginate @@ -34,10 +29,7 @@ from app.schemas.board_groups import BoardGroupCreate, BoardGroupRead, BoardGrou from app.schemas.common import OkResponse from app.schemas.pagination import DefaultLimitOffsetPage from app.schemas.view_models import BoardGroupSnapshot -from app.services.agent_provisioning import ( - DEFAULT_HEARTBEAT_CONFIG, - sync_gateway_agent_heartbeats, -) +from app.services.agent_provisioning import DEFAULT_HEARTBEAT_CONFIG, sync_gateway_agent_heartbeats from app.services.board_group_snapshot import build_group_snapshot from app.services.organizations import ( OrganizationContext, @@ -86,8 +78,7 @@ async def _require_group_access( return group board_ids = [ - board.id - for board in await Board.objects.filter_by(board_group_id=group_id).all(session) + board.id for board in await Board.objects.filter_by(board_group_id=group_id).all(session) ] if not board_ids: if is_org_admin(member): @@ -144,7 +135,10 @@ async def get_board_group( ) -> BoardGroup: """Get a board group by id.""" return await _require_group_access( - session, group_id=group_id, member=ctx.member, write=False, + session, + group_id=group_id, + member=ctx.member, + write=False, ) @@ -159,7 +153,10 @@ async def get_board_group_snapshot( ) -> BoardGroupSnapshot: """Get a snapshot across boards in a group.""" group = await _require_group_access( - session, group_id=group_id, member=ctx.member, write=False, + session, + group_id=group_id, + member=ctx.member, + write=False, ) if per_board_task_limit < 0: raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY) @@ -174,9 +171,7 @@ async def get_board_group_snapshot( allowed_ids = set( await list_accessible_board_ids(session, member=ctx.member, write=False), ) - snapshot.boards = [ - item for item in snapshot.boards if item.board.id in allowed_ids - ] + snapshot.boards = [item for item in snapshot.boards if item.board.id in allowed_ids] return snapshot @@ -339,14 +334,13 @@ async def update_board_group( ) -> BoardGroup: """Update a board group.""" group = await _require_group_access( - session, group_id=group_id, member=ctx.member, write=True, + session, + group_id=group_id, + member=ctx.member, + write=True, ) updates = payload.model_dump(exclude_unset=True) - if ( - "slug" in updates - and updates["slug"] is not None - and not updates["slug"].strip() - ): + if "slug" in updates and updates["slug"] is not None and not updates["slug"].strip(): updates["slug"] = _slugify(updates.get("name") or group.name) updates["updated_at"] = utcnow() return await crud.patch(session, group, updates) @@ -360,7 +354,10 @@ async def delete_board_group( ) -> OkResponse: """Delete a board group.""" await _require_group_access( - session, group_id=group_id, member=ctx.member, write=True, + session, + group_id=group_id, + member=ctx.member, + write=True, ) # Boards reference groups, so clear the FK first to keep deletes simple. @@ -378,7 +375,10 @@ async def delete_board_group( commit=False, ) await crud.delete_where( - session, BoardGroup, col(BoardGroup.id) == group_id, commit=False, + session, + BoardGroup, + col(BoardGroup.id) == group_id, + commit=False, ) await session.commit() return OkResponse() diff --git a/backend/app/api/board_memory.py b/backend/app/api/board_memory.py index d67f6461..006aa1ab 100644 --- a/backend/app/api/board_memory.py +++ b/backend/app/api/board_memory.py @@ -24,11 +24,7 @@ from app.core.time import utcnow from app.db.pagination import paginate from app.db.session import async_session_maker, get_session from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig -from app.integrations.openclaw_gateway import ( - OpenClawGatewayError, - ensure_session, - send_message, -) +from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.models.agents import Agent from app.models.board_memory import BoardMemory from app.models.gateways import Gateway diff --git a/backend/app/api/board_onboarding.py b/backend/app/api/board_onboarding.py index 0059aa2c..456866f5 100644 --- a/backend/app/api/board_onboarding.py +++ b/backend/app/api/board_onboarding.py @@ -21,11 +21,7 @@ from app.core.config import settings from app.core.time import utcnow from app.db.session import get_session from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig -from app.integrations.openclaw_gateway import ( - OpenClawGatewayError, - ensure_session, - send_message, -) +from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.models.board_onboarding import BoardOnboardingSession from app.models.gateways import Gateway from app.schemas.board_onboarding import ( @@ -39,11 +35,7 @@ from app.schemas.board_onboarding import ( BoardOnboardingUserProfile, ) from app.schemas.boards import BoardRead -from app.services.board_leads import ( - LeadAgentOptions, - LeadAgentRequest, - ensure_board_lead_agent, -) +from app.services.board_leads import LeadAgentOptions, LeadAgentRequest, ensure_board_lead_agent if TYPE_CHECKING: from sqlmodel.ext.asyncio.session import AsyncSession @@ -62,7 +54,8 @@ ADMIN_AUTH_DEP = Depends(require_admin_auth) async def _gateway_config( - session: AsyncSession, board: Board, + session: AsyncSession, + board: Board, ) -> tuple[Gateway, GatewayClientConfig]: if not board.gateway_id: raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY) @@ -255,11 +248,15 @@ async def start_onboarding( try: await ensure_session(session_key, config=config, label="Main Agent") await send_message( - prompt, session_key=session_key, config=config, deliver=False, + prompt, + session_key=session_key, + config=config, + deliver=False, ) except OpenClawGatewayError as exc: raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc), + status_code=status.HTTP_502_BAD_GATEWAY, + detail=str(exc), ) from exc onboarding = BoardOnboardingSession( @@ -311,7 +308,8 @@ async def answer_onboarding( ) except OpenClawGatewayError as exc: raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc), + status_code=status.HTTP_502_BAD_GATEWAY, + detail=str(exc), ) from exc onboarding.messages = messages diff --git a/backend/app/api/boards.py b/backend/app/api/boards.py index 0c49d275..601b3963 100644 --- a/backend/app/api/boards.py +++ b/backend/app/api/boards.py @@ -104,7 +104,9 @@ async def _require_gateway_for_create( session: AsyncSession = SESSION_DEP, ) -> Gateway: return await _require_gateway( - session, payload.gateway_id, organization_id=ctx.organization.id, + session, + payload.gateway_id, + organization_id=ctx.organization.id, ) @@ -155,7 +157,9 @@ async def _apply_board_update( updates = payload.model_dump(exclude_unset=True) if "gateway_id" in updates: await _require_gateway( - session, updates["gateway_id"], organization_id=board.organization_id, + session, + updates["gateway_id"], + organization_id=board.organization_id, ) if "board_group_id" in updates and updates["board_group_id"] is not None: await _require_board_group( @@ -164,10 +168,7 @@ async def _apply_board_update( organization_id=board.organization_id, ) crud.apply_updates(board, updates) - if ( - updates.get("board_type") == "goal" - and (not board.objective or not board.success_metrics) - ): + if updates.get("board_type") == "goal" and (not board.objective or not board.success_metrics): # Validate only when explicitly switching to goal boards. raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -183,7 +184,8 @@ async def _apply_board_update( async def _board_gateway( - session: AsyncSession, board: Board, + session: AsyncSession, + board: Board, ) -> tuple[Gateway | None, GatewayClientConfig | None]: if not board.gateway_id: return None, None @@ -255,7 +257,8 @@ async def list_boards( if board_group_id is not None: statement = statement.where(col(Board.board_group_id) == board_group_id) statement = statement.order_by( - func.lower(col(Board.name)).asc(), col(Board.created_at).desc(), + func.lower(col(Board.name)).asc(), + col(Board.created_at).desc(), ) return await paginate(session, statement) @@ -350,10 +353,14 @@ async def delete_board( commit=False, ) await crud.delete_where( - session, TaskDependency, col(TaskDependency.board_id) == board.id, + session, + TaskDependency, + col(TaskDependency.board_id) == board.id, ) await crud.delete_where( - session, TaskFingerprint, col(TaskFingerprint.board_id) == board.id, + session, + TaskFingerprint, + col(TaskFingerprint.board_id) == board.id, ) # Approvals can reference tasks and agents, so delete before both. diff --git a/backend/app/api/gateway.py b/backend/app/api/gateway.py index 1abc12ef..7c4b2515 100644 --- a/backend/app/api/gateway.py +++ b/backend/app/api/gateway.py @@ -91,7 +91,8 @@ async def _resolve_gateway( board = await Board.objects.by_id(params.board_id).first(session) if board is None: raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail="Board not found", + 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=False) @@ -119,7 +120,10 @@ async def _resolve_gateway( async def _require_gateway( - session: AsyncSession, board_id: str | None, *, user: User | None = None, + session: AsyncSession, + board_id: str | None, + *, + user: User | None = None, ) -> tuple[Board, GatewayClientConfig, str | None]: params = GatewayResolveQuery(board_id=board_id) board, config, main_session = await _resolve_gateway( @@ -161,7 +165,9 @@ async def gateways_status( if main_session: try: ensured = await ensure_session( - main_session, config=config, label="Main Agent", + main_session, + config=config, + label="Main Agent", ) if isinstance(ensured, dict): main_session_entry = ensured.get("entry") or ensured @@ -178,7 +184,9 @@ async def gateways_status( ) except OpenClawGatewayError as exc: return GatewaysStatusResponse( - connected=False, gateway_url=config.url, error=str(exc), + connected=False, + gateway_url=config.url, + error=str(exc), ) @@ -202,7 +210,8 @@ async def list_gateway_sessions( sessions = await openclaw_call("sessions.list", config=config) except OpenClawGatewayError as exc: raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc), + status_code=status.HTTP_502_BAD_GATEWAY, + detail=str(exc), ) from exc if isinstance(sessions, dict): sessions_list = _as_object_list(sessions.get("sessions")) @@ -213,7 +222,9 @@ async def list_gateway_sessions( if main_session: try: ensured = await ensure_session( - main_session, config=config, label="Main Agent", + main_session, + config=config, + label="Main Agent", ) if isinstance(ensured, dict): main_session_entry = ensured.get("entry") or ensured @@ -233,11 +244,7 @@ async def _list_sessions(config: GatewayClientConfig) -> list[dict[str, object]] raw_items = _as_object_list(sessions.get("sessions")) else: raw_items = _as_object_list(sessions) - return [ - item - for item in raw_items - if isinstance(item, dict) - ] + return [item for item in raw_items if isinstance(item, dict)] async def _with_main_session( @@ -246,9 +253,7 @@ async def _with_main_session( config: GatewayClientConfig, main_session: str | None, ) -> list[dict[str, object]]: - if not main_session or any( - item.get("key") == main_session for item in sessions_list - ): + if not main_session or any(item.get("key") == main_session for item in sessions_list): return sessions_list try: await ensure_session(main_session, config=config, label="Main Agent") @@ -278,7 +283,8 @@ async def get_gateway_session( sessions_list = await _list_sessions(config) except OpenClawGatewayError as exc: raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc), + status_code=status.HTTP_502_BAD_GATEWAY, + detail=str(exc), ) from exc sessions_list = await _with_main_session( sessions_list, @@ -286,12 +292,15 @@ async def get_gateway_session( main_session=main_session, ) session_entry = next( - (item for item in sessions_list if item.get("key") == session_id), None, + (item for item in sessions_list if item.get("key") == session_id), + None, ) if session_entry is None and main_session and session_id == main_session: try: ensured = await ensure_session( - main_session, config=config, label="Main Agent", + main_session, + config=config, + label="Main Agent", ) if isinstance(ensured, dict): session_entry = ensured.get("entry") or ensured @@ -299,13 +308,15 @@ async def get_gateway_session( session_entry = None if session_entry is None: raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail="Session not found", + status_code=status.HTTP_404_NOT_FOUND, + detail="Session not found", ) return GatewaySessionResponse(session=session_entry) @router.get( - "/sessions/{session_id}/history", response_model=GatewaySessionHistoryResponse, + "/sessions/{session_id}/history", + response_model=GatewaySessionHistoryResponse, ) async def get_session_history( session_id: str, @@ -322,7 +333,8 @@ async def get_session_history( history = await get_chat_history(session_id, config=config) except OpenClawGatewayError as exc: raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc), + status_code=status.HTTP_502_BAD_GATEWAY, + detail=str(exc), ) from exc if isinstance(history, dict) and isinstance(history.get("messages"), list): return GatewaySessionHistoryResponse(history=history["messages"]) @@ -339,7 +351,9 @@ async def send_gateway_session_message( ) -> OkResponse: """Send a message into a specific gateway session.""" board, config, main_session = await _require_gateway( - session, board_id, user=auth.user, + session, + board_id, + user=auth.user, ) if auth.user is None: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) @@ -350,7 +364,8 @@ async def send_gateway_session_message( await send_message(payload.content, session_key=session_id, config=config) except OpenClawGatewayError as exc: raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc), + status_code=status.HTTP_502_BAD_GATEWAY, + detail=str(exc), ) from exc return OkResponse() diff --git a/backend/app/api/gateways.py b/backend/app/api/gateways.py index 79cd97ec..75786bbd 100644 --- a/backend/app/api/gateways.py +++ b/backend/app/api/gateways.py @@ -17,11 +17,7 @@ from app.db import crud from app.db.pagination import paginate from app.db.session import get_session from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig -from app.integrations.openclaw_gateway import ( - OpenClawGatewayError, - ensure_session, - send_message, -) +from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.models.agents import Agent from app.models.gateways import Gateway from app.schemas.common import OkResponse @@ -38,12 +34,8 @@ from app.services.agent_provisioning import ( ProvisionOptions, provision_main_agent, ) -from app.services.template_sync import ( - GatewayTemplateSyncOptions, -) -from app.services.template_sync import ( - sync_gateway_templates as sync_gateway_templates_service, -) +from app.services.template_sync import GatewayTemplateSyncOptions +from app.services.template_sync import sync_gateway_templates as sync_gateway_templates_service if TYPE_CHECKING: from fastapi_pagination.limit_offset import LimitOffsetPage @@ -109,7 +101,8 @@ async def _require_gateway( ) if gateway is None: raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail="Gateway not found", + status_code=status.HTTP_404_NOT_FOUND, + detail="Gateway not found", ) return gateway diff --git a/backend/app/api/metrics.py b/backend/app/api/metrics.py index 7f919407..edf2febf 100644 --- a/backend/app/api/metrics.py +++ b/backend/app/api/metrics.py @@ -8,8 +8,9 @@ from typing import Literal from uuid import UUID from fastapi import APIRouter, Depends, Query -from sqlalchemy import DateTime, case, func +from sqlalchemy import DateTime, case from sqlalchemy import cast as sql_cast +from sqlalchemy import func from sqlmodel import col, select from sqlmodel.ext.asyncio.session import AsyncSession diff --git a/backend/app/api/organizations.py b/backend/app/api/organizations.py index 31878ebc..aeb8b881 100644 --- a/backend/app/api/organizations.py +++ b/backend/app/api/organizations.py @@ -80,7 +80,8 @@ ORG_ADMIN_DEP = Depends(require_org_admin) def _member_to_read( - member: OrganizationMember, user: User | None, + member: OrganizationMember, + user: User | None, ) -> OrganizationMemberRead: model = OrganizationMemberRead.model_validate(member, from_attributes=True) if user is not None: @@ -167,9 +168,7 @@ async def list_my_organizations( await get_active_membership(session, auth.user) db_user = await User.objects.by_id(auth.user.id).first(session) - active_id = ( - db_user.active_organization_id if db_user else auth.user.active_organization_id - ) + active_id = db_user.active_organization_id if db_user else auth.user.active_organization_id statement = ( select(Organization, OrganizationMember) @@ -202,7 +201,9 @@ async def set_active_org( if auth.user is None: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) member = await set_active_organization( - session, user=auth.user, organization_id=payload.organization_id, + session, + user=auth.user, + organization_id=payload.organization_id, ) organization = await Organization.objects.by_id(member.organization_id).first( session, @@ -245,7 +246,10 @@ async def delete_my_org( group_ids = select(BoardGroup.id).where(col(BoardGroup.organization_id) == org_id) await crud.delete_where( - session, ActivityEvent, col(ActivityEvent.task_id).in_(task_ids), commit=False, + session, + ActivityEvent, + col(ActivityEvent.task_id).in_(task_ids), + commit=False, ) await crud.delete_where( session, @@ -266,10 +270,16 @@ async def delete_my_org( commit=False, ) await crud.delete_where( - session, Approval, col(Approval.board_id).in_(board_ids), commit=False, + 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, + session, + BoardMemory, + col(BoardMemory.board_id).in_(board_ids), + commit=False, ) await crud.delete_where( session, @@ -302,13 +312,22 @@ async def delete_my_org( commit=False, ) await crud.delete_where( - session, Task, col(Task.board_id).in_(board_ids), commit=False, + 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, + 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, + session, + Board, + col(Board.organization_id) == org_id, + commit=False, ) await crud.delete_where( session, @@ -317,10 +336,16 @@ async def delete_my_org( commit=False, ) await crud.delete_where( - session, BoardGroup, col(BoardGroup.organization_id) == org_id, commit=False, + session, + BoardGroup, + col(BoardGroup.organization_id) == org_id, + commit=False, ) await crud.delete_where( - session, Gateway, col(Gateway.organization_id) == org_id, commit=False, + session, + Gateway, + col(Gateway.organization_id) == org_id, + commit=False, ) await crud.delete_where( session, @@ -342,7 +367,10 @@ async def delete_my_org( commit=False, ) await crud.delete_where( - session, Organization, col(Organization.id) == org_id, commit=False, + session, + Organization, + col(Organization.id) == org_id, + commit=False, ) await session.commit() return OkResponse() @@ -360,14 +388,14 @@ async def get_my_membership( ).all(session) model = _member_to_read(ctx.member, user) model.board_access = [ - OrganizationBoardAccessRead.model_validate(row, from_attributes=True) - for row in access_rows + OrganizationBoardAccessRead.model_validate(row, from_attributes=True) for row in access_rows ] return model @router.get( - "/me/members", response_model=DefaultLimitOffsetPage[OrganizationMemberRead], + "/me/members", + response_model=DefaultLimitOffsetPage[OrganizationMemberRead], ) async def list_org_members( session: AsyncSession = SESSION_DEP, @@ -410,8 +438,7 @@ async def get_org_member( ).all(session) model = _member_to_read(member, user) model.board_access = [ - OrganizationBoardAccessRead.model_validate(row, from_attributes=True) - for row in access_rows + OrganizationBoardAccessRead.model_validate(row, from_attributes=True) for row in access_rows ] return model @@ -529,9 +556,7 @@ async def remove_org_member( user.active_organization_id = fallback_membership else: user.active_organization_id = ( - fallback_membership.organization_id - if fallback_membership is not None - else None + fallback_membership.organization_id if fallback_membership is not None else None ) session.add(user) @@ -540,7 +565,8 @@ async def remove_org_member( @router.get( - "/me/invites", response_model=DefaultLimitOffsetPage[OrganizationInviteRead], + "/me/invites", + response_model=DefaultLimitOffsetPage[OrganizationInviteRead], ) async def list_org_invites( session: AsyncSession = SESSION_DEP, @@ -607,7 +633,9 @@ async def create_org_invite( if valid_board_ids != board_ids: raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY) await apply_invite_board_access( - session, invite=invite, entries=payload.board_access, + session, + invite=invite, + entries=payload.board_access, ) await session.commit() await session.refresh(invite) diff --git a/backend/app/api/tasks.py b/backend/app/api/tasks.py index 5a77856c..8db6fea9 100644 --- a/backend/app/api/tasks.py +++ b/backend/app/api/tasks.py @@ -29,11 +29,7 @@ from app.db import crud from app.db.pagination import paginate from app.db.session import async_session_maker, get_session from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig -from app.integrations.openclaw_gateway import ( - OpenClawGatewayError, - ensure_session, - send_message, -) +from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.models.activity_events import ActivityEvent from app.models.agents import Agent from app.models.approvals import Approval @@ -45,13 +41,7 @@ from app.models.tasks import Task from app.schemas.common import OkResponse from app.schemas.errors import BlockedTaskError from app.schemas.pagination import DefaultLimitOffsetPage -from app.schemas.tasks import ( - TaskCommentCreate, - TaskCommentRead, - TaskCreate, - TaskRead, - TaskUpdate, -) +from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate from app.services.activity_log import record_activity from app.services.mentions import extract_mentions, matches_agent_mention from app.services.organizations import require_board_access @@ -263,8 +253,7 @@ async def _reconcile_dependents_for_dependency_toggle( event_type="task.status_changed", task_id=dependent.id, message=( - "Task returned to inbox: dependency reopened " - f"({dependency_task.title})." + "Task returned to inbox: dependency reopened " f"({dependency_task.title})." ), agent_id=actor_agent_id, ) @@ -313,7 +302,8 @@ def _serialize_comment(event: ActivityEvent) -> dict[str, object]: async def _gateway_config( - session: AsyncSession, board: Board, + session: AsyncSession, + board: Board, ) -> GatewayClientConfig | None: if not board.gateway_id: return None @@ -368,10 +358,7 @@ async def _notify_agent_on_task_assign( message = ( "TASK ASSIGNED\n" + "\n".join(details) - + ( - "\n\nTake action: open the task and begin work. " - "Post updates as task comments." - ) + + ("\n\nTake action: open the task and begin work. " "Post updates as task comments.") ) try: await _send_agent_task_message( @@ -607,9 +594,7 @@ async def _stream_dependency_state( rows: list[tuple[ActivityEvent, Task | None]], ) -> tuple[dict[UUID, list[UUID]], dict[UUID, str]]: task_ids = [ - task.id - for event, task in rows - if task is not None and event.event_type != "task.comment" + task.id for event, task in rows if task is not None and event.event_type != "task.comment" ] if not task_ids: return {}, {} @@ -786,7 +771,8 @@ async def create_task( dependency_ids=normalized_deps, ) blocked_by = blocked_by_dependency_ids( - dependency_ids=normalized_deps, status_by_id=dep_status, + dependency_ids=normalized_deps, + status_by_id=dep_status, ) if blocked_by and (task.assigned_agent_id is not None or task.status != "inbox"): raise _blocked_task_error(blocked_by) @@ -861,9 +847,7 @@ async def update_task( updates = payload.model_dump(exclude_unset=True) comment = payload.comment if "comment" in payload.model_fields_set else None depends_on_task_ids = ( - payload.depends_on_task_ids - if "depends_on_task_ids" in payload.model_fields_set - else None + payload.depends_on_task_ids if "depends_on_task_ids" in payload.model_fields_set else None ) updates.pop("comment", None) updates.pop("depends_on_task_ids", None) @@ -906,13 +890,22 @@ async def delete_task( raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) await require_board_access(session, user=auth.user, board=board, write=True) await crud.delete_where( - session, ActivityEvent, col(ActivityEvent.task_id) == task.id, commit=False, + session, + ActivityEvent, + col(ActivityEvent.task_id) == task.id, + commit=False, ) await crud.delete_where( - session, TaskFingerprint, col(TaskFingerprint.task_id) == task.id, commit=False, + session, + TaskFingerprint, + col(TaskFingerprint.task_id) == task.id, + commit=False, ) await crud.delete_where( - session, Approval, col(Approval.task_id) == task.id, commit=False, + session, + Approval, + col(Approval.task_id) == task.id, + commit=False, ) await crud.delete_where( session, @@ -929,7 +922,8 @@ async def delete_task( @router.get( - "/{task_id}/comments", response_model=DefaultLimitOffsetPage[TaskCommentRead], + "/{task_id}/comments", + response_model=DefaultLimitOffsetPage[TaskCommentRead], ) async def list_task_comments( task: Task = TASK_DEP, @@ -1241,11 +1235,7 @@ async def _lead_apply_assignment( status_code=status.HTTP_403_FORBIDDEN, detail="Board leads cannot assign tasks to themselves.", ) - if ( - agent.board_id - and update.task.board_id - and agent.board_id != update.task.board_id - ): + if agent.board_id and update.task.board_id and agent.board_id != update.task.board_id: raise HTTPException(status_code=status.HTTP_409_CONFLICT) update.task.assigned_agent_id = agent.id @@ -1256,19 +1246,13 @@ def _lead_apply_status(update: _TaskUpdateInput) -> None: if update.task.status != "review": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, - detail=( - "Board leads can only change status when a task is " - "in review." - ), + detail=("Board leads can only change status when a task is " "in review."), ) target_status = _required_status_value(update.updates["status"]) if target_status not in {"done", "inbox"}: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, - detail=( - "Board leads can only move review tasks to done " - "or inbox." - ), + detail=("Board leads can only move review tasks to done " "or inbox."), ) if target_status == "inbox": update.task.assigned_agent_id = None @@ -1397,9 +1381,7 @@ async def _apply_non_lead_agent_task_rules( update.task.assigned_agent_id = None update.task.in_progress_at = None else: - update.task.assigned_agent_id = ( - update.actor.agent.id if update.actor.agent else None - ) + update.task.assigned_agent_id = update.actor.agent.id if update.actor.agent else None if status_value == "in_progress": update.task.in_progress_at = utcnow() @@ -1462,11 +1444,7 @@ async def _apply_admin_task_rules( agent = await Agent.objects.by_id(assigned_agent_id).first(session) if agent is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) - if ( - agent.board_id - and update.task.board_id - and agent.board_id != update.task.board_id - ): + if agent.board_id and update.task.board_id and agent.board_id != update.task.board_id: raise HTTPException(status_code=status.HTTP_409_CONFLICT) @@ -1481,9 +1459,11 @@ async def _record_task_comment_from_update( event_type="task.comment", message=update.comment, task_id=update.task.id, - agent_id=update.actor.agent.id - if update.actor.actor_type == "agent" and update.actor.agent - else None, + agent_id=( + update.actor.agent.id + if update.actor.actor_type == "agent" and update.actor.agent + else None + ), ) session.add(event) await session.commit() @@ -1496,9 +1476,7 @@ async def _record_task_update_activity( ) -> None: event_type, message = _task_event_details(update.task, update.previous_status) actor_agent_id = ( - update.actor.agent.id - if update.actor.actor_type == "agent" and update.actor.agent - else None + update.actor.agent.id if update.actor.actor_type == "agent" and update.actor.agent else None ) record_activity( session, @@ -1525,10 +1503,7 @@ async def _notify_task_update_assignment_changes( if ( update.task.status == "inbox" and update.task.assigned_agent_id is None - and ( - update.previous_status != "inbox" - or update.previous_assigned is not None - ) + and (update.previous_status != "inbox" or update.previous_assigned is not None) ): board = ( await Board.objects.by_id(update.task.board_id).first(session) diff --git a/backend/app/core/agent_auth.py b/backend/app/core/agent_auth.py index 4abddce3..494ea6cf 100644 --- a/backend/app/core/agent_auth.py +++ b/backend/app/core/agent_auth.py @@ -77,10 +77,7 @@ async def _touch_agent_presence( real activity even if the heartbeat loop isn't running. """ now = utcnow() - if ( - agent.last_seen_at is not None - and now - agent.last_seen_at < _LAST_SEEN_TOUCH_INTERVAL - ): + if agent.last_seen_at is not None and now - agent.last_seen_at < _LAST_SEEN_TOUCH_INTERVAL: return agent.last_seen_at = now diff --git a/backend/app/core/durations.py b/backend/app/core/durations.py index 5ab16bf3..f7e9d8dc 100644 --- a/backend/app/core/durations.py +++ b/backend/app/core/durations.py @@ -19,9 +19,7 @@ _MULTIPLIERS: dict[str, int] = { _MAX_SCHEDULE_SECONDS = 60 * 60 * 24 * 365 * 10 _ERR_SCHEDULE_REQUIRED = "schedule is required" -_ERR_SCHEDULE_INVALID = ( - 'Invalid schedule. Expected format like "10m", "1h", "2d", "1w".' -) +_ERR_SCHEDULE_INVALID = 'Invalid schedule. Expected format like "10m", "1h", "2d", "1w".' _ERR_SCHEDULE_NONPOSITIVE = "Schedule must be greater than 0." _ERR_SCHEDULE_TOO_LARGE = "Schedule is too large (max 10 years)." diff --git a/backend/app/core/error_handling.py b/backend/app/core/error_handling.py index a27a6a25..b1fb7ad1 100644 --- a/backend/app/core/error_handling.py +++ b/backend/app/core/error_handling.py @@ -13,7 +13,7 @@ from fastapi.responses import JSONResponse from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.responses import Response -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from starlette.types import ASGIApp, Message, Receive, Scope, Send logger = logging.getLogger(__name__) @@ -44,9 +44,7 @@ class RequestIdMiddleware: if message["type"] == "http.response.start": # Starlette uses `list[tuple[bytes, bytes]]` here. headers: list[tuple[bytes, bytes]] = message.setdefault("headers", []) - if not any( - key.lower() == self._header_name_bytes for key, _ in headers - ): + if not any(key.lower() == self._header_name_bytes for key, _ in headers): request_id_bytes = request_id.encode("latin-1") headers.append((self._header_name_bytes, request_id_bytes)) await send(message) diff --git a/backend/app/core/logging.py b/backend/app/core/logging.py index 9f8102d0..996b6ba4 100644 --- a/backend/app/core/logging.py +++ b/backend/app/core/logging.py @@ -198,8 +198,7 @@ class AppLogger: formatter: logging.Formatter = JsonFormatter() else: formatter = KeyValueFormatter( - "%(asctime)s %(levelname)s %(name)s %(message)s " - "app=%(app)s version=%(version)s", + "%(asctime)s %(levelname)s %(name)s %(message)s " "app=%(app)s version=%(version)s", ) if settings.log_use_utc: formatter.converter = time.gmtime diff --git a/backend/app/db/crud.py b/backend/app/db/crud.py index a5905f1f..10996c39 100644 --- a/backend/app/db/crud.py +++ b/backend/app/db/crud.py @@ -161,10 +161,7 @@ async def list_by( async def exists(session: AsyncSession, model: type[ModelT], **lookup: object) -> bool: """Return whether any object exists for lookup values.""" - return ( - (await session.exec(_lookup_statement(model, lookup).limit(1))).first() - is not None - ) + return (await session.exec(_lookup_statement(model, lookup).limit(1))).first() is not None def _criteria_statement( @@ -219,11 +216,7 @@ async def update_where( commit = bool(options.pop("commit", False)) exclude_none = bool(options.pop("exclude_none", False)) allowed_fields_raw = options.pop("allowed_fields", None) - allowed_fields = ( - allowed_fields_raw - if isinstance(allowed_fields_raw, set) - else None - ) + allowed_fields = allowed_fields_raw if isinstance(allowed_fields_raw, set) else None source_updates: dict[str, Any] = {} if updates: source_updates.update(dict(updates)) @@ -276,11 +269,7 @@ async def patch( """Apply partial updates and persist object.""" exclude_none = bool(options.pop("exclude_none", False)) allowed_fields_raw = options.pop("allowed_fields", None) - allowed_fields = ( - allowed_fields_raw - if isinstance(allowed_fields_raw, set) - else None - ) + allowed_fields = allowed_fields_raw if isinstance(allowed_fields_raw, set) else None commit = bool(options.pop("commit", True)) refresh = bool(options.pop("refresh", True)) apply_updates( diff --git a/backend/app/schemas/board_onboarding.py b/backend/app/schemas/board_onboarding.py index ba39b136..862f8d60 100644 --- a/backend/app/schemas/board_onboarding.py +++ b/backend/app/schemas/board_onboarding.py @@ -36,12 +36,8 @@ class BoardOnboardingConfirm(SQLModel): @model_validator(mode="after") def validate_goal_fields(self) -> Self: """Require goal metadata when the board type is `goal`.""" - if self.board_type == "goal" and ( - not self.objective or not self.success_metrics - ): - message = ( - "Confirmed goal boards require objective and success_metrics" - ) + if self.board_type == "goal" and (not self.objective or not self.success_metrics): + message = "Confirmed goal boards require objective and success_metrics" raise ValueError(message) return self diff --git a/backend/app/schemas/boards.py b/backend/app/schemas/boards.py index 748db9d4..7b4afa51 100644 --- a/backend/app/schemas/boards.py +++ b/backend/app/schemas/boards.py @@ -9,9 +9,7 @@ from uuid import UUID from pydantic import model_validator from sqlmodel import SQLModel -_ERR_GOAL_FIELDS_REQUIRED = ( - "Confirmed goal boards require objective and success_metrics" -) +_ERR_GOAL_FIELDS_REQUIRED = "Confirmed goal boards require objective and success_metrics" _ERR_GATEWAY_REQUIRED = "gateway_id is required" RUNTIME_ANNOTATION_TYPES = (datetime, UUID) diff --git a/backend/app/services/agent_provisioning.py b/backend/app/services/agent_provisioning.py index 7db20398..ce17325f 100644 --- a/backend/app/services/agent_provisioning.py +++ b/backend/app/services/agent_provisioning.py @@ -15,11 +15,7 @@ from jinja2 import Environment, FileSystemLoader, StrictUndefined, select_autoes from app.core.config import settings from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig -from app.integrations.openclaw_gateway import ( - OpenClawGatewayError, - ensure_session, - openclaw_call, -) +from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, openclaw_call if TYPE_CHECKING: from app.models.agents import Agent @@ -414,7 +410,9 @@ async def _supported_gateway_files(config: GatewayClientConfig) -> set[str]: if not agent_id: return set(DEFAULT_GATEWAY_FILES) files_payload = await openclaw_call( - "agents.files.list", {"agentId": agent_id}, config=config, + "agents.files.list", + {"agentId": agent_id}, + config=config, ) if isinstance(files_payload, dict): files = files_payload.get("files") or [] @@ -438,11 +436,14 @@ async def _reset_session(session_key: str, config: GatewayClientConfig) -> None: async def _gateway_agent_files_index( - agent_id: str, config: GatewayClientConfig, + agent_id: str, + config: GatewayClientConfig, ) -> dict[str, dict[str, Any]]: try: payload = await openclaw_call( - "agents.files.list", {"agentId": agent_id}, config=config, + "agents.files.list", + {"agentId": agent_id}, + config=config, ) if isinstance(payload, dict): files = payload.get("files") or [] @@ -486,18 +487,14 @@ def _render_agent_files( ) heartbeat_path = _templates_root() / heartbeat_template if heartbeat_path.exists(): - rendered[name] = ( - env.get_template(heartbeat_template).render(**context).strip() - ) + rendered[name] = env.get_template(heartbeat_template).render(**context).strip() continue override = overrides.get(name) if override: rendered[name] = env.from_string(override).render(**context).strip() continue template_name = ( - template_overrides[name] - if template_overrides and name in template_overrides - else name + template_overrides[name] if template_overrides and name in template_overrides else name ) path = _templates_root() / template_name if path.exists(): @@ -596,8 +593,7 @@ def _heartbeat_entry_map( entries: list[tuple[str, str, dict[str, Any]]], ) -> dict[str, tuple[str, dict[str, Any]]]: return { - agent_id: (workspace_path, heartbeat) - for agent_id, workspace_path, heartbeat in entries + agent_id: (workspace_path, heartbeat) for agent_id, workspace_path, heartbeat in entries } @@ -694,9 +690,7 @@ async def _remove_gateway_agent_list( raise OpenClawGatewayError(msg) new_list = [ - entry - for entry in lst - if not (isinstance(entry, dict) and entry.get("id") == agent_id) + entry for entry in lst if not (isinstance(entry, dict) and entry.get("id") == agent_id) ] if len(new_list) == len(lst): return @@ -841,7 +835,9 @@ async def provision_main_agent( raise ValueError(msg) client_config = GatewayClientConfig(url=gateway.url, token=gateway.token) await ensure_session( - gateway.main_session_key, config=client_config, label="Main Agent", + gateway.main_session_key, + config=client_config, + label="Main Agent", ) agent_id = await _gateway_default_agent_id( diff --git a/backend/app/services/board_group_snapshot.py b/backend/app/services/board_group_snapshot.py index e11c6fb0..59d4cb2c 100644 --- a/backend/app/services/board_group_snapshot.py +++ b/backend/app/services/board_group_snapshot.py @@ -38,10 +38,7 @@ def _status_weight_expr() -> ColumnElement[int]: def _priority_weight_expr() -> ColumnElement[int]: """Return a SQL expression that sorts task priorities by configured order.""" - whens = [ - (col(Task.priority) == key, weight) - for key, weight in _PRIORITY_ORDER.items() - ] + whens = [(col(Task.priority) == key, weight) for key, weight in _PRIORITY_ORDER.items()] return case(*whens, else_=99) @@ -106,11 +103,7 @@ async def _agent_names( tasks: list[Task], ) -> dict[UUID, str]: """Return agent names keyed by assigned agent ids in the provided tasks.""" - assigned_ids = { - task.assigned_agent_id - for task in tasks - if task.assigned_agent_id is not None - } + assigned_ids = {task.assigned_agent_id for task in tasks if task.assigned_agent_id is not None} if not assigned_ids: return {} return dict( diff --git a/backend/app/services/board_leads.py b/backend/app/services/board_leads.py index ed5289a2..41f6d803 100644 --- a/backend/app/services/board_leads.py +++ b/backend/app/services/board_leads.py @@ -10,11 +10,7 @@ from sqlmodel import col, select from app.core.agent_tokens import generate_agent_token, hash_agent_token from app.core.time import utcnow from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig -from app.integrations.openclaw_gateway import ( - OpenClawGatewayError, - ensure_session, - send_message, -) +from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.models.agents import Agent from app.services.agent_provisioning import ( DEFAULT_HEARTBEAT_CONFIG, diff --git a/backend/app/services/board_snapshot.py b/backend/app/services/board_snapshot.py index 33065c5d..c0cc5ce9 100644 --- a/backend/app/services/board_snapshot.py +++ b/backend/app/services/board_snapshot.py @@ -55,8 +55,7 @@ def _agent_to_read(agent: Agent, main_session_keys: set[str]) -> AgentRead: model = AgentRead.model_validate(agent, from_attributes=True) computed_status = _computed_agent_status(agent) is_gateway_main = bool( - agent.openclaw_session_id - and agent.openclaw_session_id in main_session_keys, + agent.openclaw_session_id and agent.openclaw_session_id in main_session_keys, ) return model.model_copy( update={ @@ -84,11 +83,7 @@ def _task_to_card( ) -> TaskCardRead: card = TaskCardRead.model_validate(task, from_attributes=True) approvals_count, approvals_pending_count = counts_by_task_id.get(task.id, (0, 0)) - assignee = ( - agent_name_by_id.get(task.assigned_agent_id) - if task.assigned_agent_id - else None - ) + assignee = agent_name_by_id.get(task.assigned_agent_id) if task.assigned_agent_id else None depends_on_task_ids = deps_by_task_id.get(task.id, []) blocked_by_task_ids = blocked_by_dependency_ids( dependency_ids=depends_on_task_ids, diff --git a/backend/app/services/mentions.py b/backend/app/services/mentions.py index 980ffc63..035d017a 100644 --- a/backend/app/services/mentions.py +++ b/backend/app/services/mentions.py @@ -5,7 +5,7 @@ from __future__ import annotations import re from typing import TYPE_CHECKING -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from app.models.agents import Agent # Mention tokens are single, space-free words (e.g. "@alex", "@lead"). diff --git a/backend/app/services/organizations.py b/backend/app/services/organizations.py index ff2025cc..985d5faf 100644 --- a/backend/app/services/organizations.py +++ b/backend/app/services/organizations.py @@ -79,7 +79,8 @@ async def get_member( async def get_first_membership( - session: AsyncSession, user_id: UUID, + session: AsyncSession, + user_id: UUID, ) -> OrganizationMember | None: """Return the oldest membership for a user, if any.""" return ( @@ -99,7 +100,8 @@ async def set_active_organization( member = await get_member(session, user_id=user.id, organization_id=organization_id) if member is None: raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, detail="No org access", + status_code=status.HTTP_403_FORBIDDEN, + detail="No org access", ) if user.active_organization_id != organization_id: user.active_organization_id = organization_id @@ -177,8 +179,7 @@ async def accept_invite( access_rows = list( await session.exec( select(OrganizationInviteBoardAccess).where( - col(OrganizationInviteBoardAccess.organization_invite_id) - == invite.id, + col(OrganizationInviteBoardAccess.organization_invite_id) == invite.id, ), ), ) @@ -207,7 +208,8 @@ async def accept_invite( async def ensure_member_for_user( - session: AsyncSession, user: User, + session: AsyncSession, + user: User, ) -> OrganizationMember: """Ensure a user has some membership, creating one if necessary.""" existing = await get_active_membership(session, user) @@ -291,21 +293,27 @@ async def require_board_access( ) -> OrganizationMember: """Require board access for a user and return matching membership.""" member = await get_member( - session, user_id=user.id, organization_id=board.organization_id, + session, + user_id=user.id, + organization_id=board.organization_id, ) if member is None: raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, detail="No org access", + status_code=status.HTTP_403_FORBIDDEN, + detail="No org access", ) if not await has_board_access(session, member=member, board=board, write=write): raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, detail="Board access denied", + status_code=status.HTTP_403_FORBIDDEN, + detail="Board access denied", ) return member def board_access_filter( - member: OrganizationMember, *, write: bool, + member: OrganizationMember, + *, + write: bool, ) -> ColumnElement[bool]: """Build a SQL filter expression for boards visible to a member.""" if write and member_all_boards_write(member): diff --git a/backend/app/services/souls_directory.py b/backend/app/services/souls_directory.py index d144a482..89ff2e28 100644 --- a/backend/app/services/souls_directory.py +++ b/backend/app/services/souls_directory.py @@ -42,10 +42,7 @@ class SoulRef: def _parse_sitemap_soul_refs(sitemap_xml: str) -> list[SoulRef]: """Parse sitemap XML and extract valid souls.directory handle/slug refs.""" # Extract values without XML entity expansion. - urls = [ - unescape(match.group(1)).strip() - for match in _LOC_PATTERN.finditer(sitemap_xml) - ] + urls = [unescape(match.group(1)).strip() for match in _LOC_PATTERN.finditer(sitemap_xml)] refs: list[SoulRef] = [] for url in urls: @@ -110,10 +107,7 @@ async def fetch_soul_markdown( normalized_slug = slug.strip().strip("/") if normalized_slug.endswith(".md"): normalized_slug = normalized_slug[: -len(".md")] - url = ( - f"{SOULS_DIRECTORY_BASE_URL}/api/souls/" - f"{normalized_handle}/{normalized_slug}.md" - ) + url = f"{SOULS_DIRECTORY_BASE_URL}/api/souls/" f"{normalized_handle}/{normalized_slug}.md" owns_client = client is None if client is None: diff --git a/backend/app/services/task_dependencies.py b/backend/app/services/task_dependencies.py index 22059151..a9c8cd66 100644 --- a/backend/app/services/task_dependencies.py +++ b/backend/app/services/task_dependencies.py @@ -79,11 +79,7 @@ def blocked_by_dependency_ids( status_by_id: Mapping[UUID, str], ) -> list[UUID]: """Return dependency ids that are not yet in the done status.""" - return [ - dep_id - for dep_id in dependency_ids - if status_by_id.get(dep_id) != DONE_STATUS - ] + return [dep_id for dep_id in dependency_ids if status_by_id.get(dep_id) != DONE_STATUS] async def blocked_by_for_task( diff --git a/backend/app/services/template_sync.py b/backend/app/services/template_sync.py index aa88415c..306b5cef 100644 --- a/backend/app/services/template_sync.py +++ b/backend/app/services/template_sync.py @@ -14,11 +14,7 @@ from sqlalchemy import func from sqlmodel import col, select from sqlmodel.ext.asyncio.session import AsyncSession -from app.core.agent_tokens import ( - generate_agent_token, - hash_agent_token, - verify_agent_token, -) +from app.core.agent_tokens import generate_agent_token, hash_agent_token, verify_agent_token from app.core.time import utcnow from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig from app.integrations.openclaw_gateway import OpenClawGatewayError, openclaw_call @@ -108,10 +104,7 @@ def _is_transient_gateway_error(exc: Exception) -> bool: def _gateway_timeout_message(exc: OpenClawGatewayError) -> str: - return ( - "Gateway unreachable after 10 minutes (template sync timeout). " - f"Last error: {exc}" - ) + return "Gateway unreachable after 10 minutes (template sync timeout). " f"Last error: {exc}" class _GatewayBackoff: @@ -375,6 +368,7 @@ async def _rotate_agent_token(session: AsyncSession, agent: Agent) -> str: async def _ping_gateway(ctx: _SyncContext, result: GatewayTemplatesSyncResult) -> bool: try: + async def _do_ping() -> object: return await openclaw_call("agents.list", config=ctx.config) @@ -486,6 +480,7 @@ async def _sync_one_agent( if not auth_token: return False try: + async def _do_provision() -> None: await provision_agent( agent, @@ -533,10 +528,7 @@ async def _sync_main_agent( if main_agent is None: _append_sync_error( result, - message=( - "Gateway main agent record not found; " - "skipping main agent template sync." - ), + message=("Gateway main agent record not found; " "skipping main agent template sync."), ) return True try: @@ -574,6 +566,7 @@ async def _sync_main_agent( return True stop_sync = False try: + async def _do_provision_main() -> None: await provision_main_agent( main_agent, diff --git a/backend/scripts/sync_gateway_templates.py b/backend/scripts/sync_gateway_templates.py index fe07bff1..9e1e8a16 100644 --- a/backend/scripts/sync_gateway_templates.py +++ b/backend/scripts/sync_gateway_templates.py @@ -32,17 +32,13 @@ def _parse_args() -> argparse.Namespace: parser.add_argument( "--reset-sessions", action="store_true", - help=( - "Reset agent sessions after syncing files " - "(forces agents to re-read workspace)" - ), + help=("Reset agent sessions after syncing files " "(forces agents to re-read workspace)"), ) parser.add_argument( "--rotate-tokens", action="store_true", help=( - "Rotate agent tokens when TOOLS.md is missing/unreadable " - "or token drift is detected" + "Rotate agent tokens when TOOLS.md is missing/unreadable " "or token drift is detected" ), ) parser.add_argument( @@ -56,10 +52,7 @@ def _parse_args() -> argparse.Namespace: async def _run() -> int: from app.db.session import async_session_maker from app.models.gateways import Gateway - from app.services.template_sync import ( - GatewayTemplateSyncOptions, - sync_gateway_templates, - ) + from app.services.template_sync import GatewayTemplateSyncOptions, sync_gateway_templates args = _parse_args() gateway_id = UUID(args.gateway_id) @@ -86,8 +79,7 @@ async def _run() -> int: sys.stdout.write(f"gateway_id={result.gateway_id}\n") sys.stdout.write( - f"include_main={result.include_main} " - f"reset_sessions={result.reset_sessions}\n", + f"include_main={result.include_main} " f"reset_sessions={result.reset_sessions}\n", ) sys.stdout.write( f"agents_updated={result.agents_updated} " diff --git a/backend/tests/test_error_handling.py b/backend/tests/test_error_handling.py index 774c541f..03150806 100644 --- a/backend/tests/test_error_handling.py +++ b/backend/tests/test_error_handling.py @@ -2,6 +2,7 @@ from __future__ import annotations +import pytest from fastapi import FastAPI, HTTPException from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -11,6 +12,9 @@ from app.core.error_handling import ( REQUEST_ID_HEADER, _error_payload, _get_request_id, + _http_exception_exception_handler, + _request_validation_exception_handler, + _response_validation_exception_handler, install_error_handling, ) @@ -123,3 +127,24 @@ def test_get_request_id_returns_none_for_missing_or_invalid_state() -> None: def test_error_payload_omits_request_id_when_none() -> None: assert _error_payload(detail="x", request_id=None) == {"detail": "x"} + + +@pytest.mark.asyncio +async def test_request_validation_exception_wrapper_rejects_wrong_exception() -> None: + req = Request({"type": "http", "headers": [], "state": {}}) + with pytest.raises(TypeError, match="Expected RequestValidationError"): + await _request_validation_exception_handler(req, Exception("x")) + + +@pytest.mark.asyncio +async def test_response_validation_exception_wrapper_rejects_wrong_exception() -> None: + req = Request({"type": "http", "headers": [], "state": {}}) + with pytest.raises(TypeError, match="Expected ResponseValidationError"): + await _response_validation_exception_handler(req, Exception("x")) + + +@pytest.mark.asyncio +async def test_http_exception_wrapper_rejects_wrong_exception() -> None: + req = Request({"type": "http", "headers": [], "state": {}}) + with pytest.raises(TypeError, match="Expected StarletteHTTPException"): + await _http_exception_exception_handler(req, Exception("x")) diff --git a/backend/tests/test_organizations_service.py b/backend/tests/test_organizations_service.py index daecd10a..14d8ac06 100644 --- a/backend/tests/test_organizations_service.py +++ b/backend/tests/test_organizations_service.py @@ -16,10 +16,7 @@ from app.models.organization_invites import OrganizationInvite from app.models.organization_members import OrganizationMember from app.models.organizations import Organization from app.models.users import User -from app.schemas.organizations import ( - OrganizationBoardAccessSpec, - OrganizationMemberAccessUpdate, -) +from app.schemas.organizations import OrganizationBoardAccessSpec, OrganizationMemberAccessUpdate from app.services import organizations @@ -87,10 +84,7 @@ class _FakeSession: def test_normalize_invited_email_strips_and_lowercases() -> None: - assert ( - organizations.normalize_invited_email(" Foo@Example.com ") - == "foo@example.com" - ) + assert organizations.normalize_invited_email(" Foo@Example.com ") == "foo@example.com" @pytest.mark.parametrize( @@ -128,7 +122,9 @@ async def test_ensure_member_for_user_returns_existing_membership( ) -> None: user = User(clerk_user_id="u1") existing = OrganizationMember( - organization_id=uuid4(), user_id=user.id, role="member", + organization_id=uuid4(), + user_id=user.id, + role="member", ) async def _fake_get_active(_session: Any, _user: User) -> OrganizationMember: @@ -161,11 +157,15 @@ async def test_ensure_member_for_user_accepts_pending_invite( return invite accepted = OrganizationMember( - organization_id=org_id, user_id=user.id, role="member", + organization_id=org_id, + user_id=user.id, + role="member", ) async def _fake_accept( - _session: Any, _invite: OrganizationInvite, _user: User, + _session: Any, + _invite: OrganizationInvite, + _user: User, ) -> OrganizationMember: assert _invite is invite assert _user is user @@ -216,7 +216,10 @@ async def test_has_board_access_denies_cross_org() -> None: board = Board(id=uuid4(), organization_id=uuid4(), name="b", slug="b") assert ( await organizations.has_board_access( - session, member=member, board=board, write=False, + session, + member=member, + board=board, + write=False, ) is False ) @@ -226,7 +229,10 @@ async def test_has_board_access_denies_cross_org() -> None: async def test_has_board_access_uses_org_board_access_row_read_and_write() -> None: org_id = uuid4() member = OrganizationMember( - id=uuid4(), organization_id=org_id, user_id=uuid4(), role="member", + id=uuid4(), + organization_id=org_id, + user_id=uuid4(), + role="member", ) board = Board(id=uuid4(), organization_id=org_id, name="b", slug="b") @@ -239,7 +245,10 @@ async def test_has_board_access_uses_org_board_access_row_read_and_write() -> No session = _FakeSession(exec_results=[_FakeExecResult(first_value=access)]) assert ( await organizations.has_board_access( - session, member=member, board=board, write=False, + session, + member=member, + board=board, + write=False, ) is True ) @@ -253,7 +262,10 @@ async def test_has_board_access_uses_org_board_access_row_read_and_write() -> No session2 = _FakeSession(exec_results=[_FakeExecResult(first_value=access2)]) assert ( await organizations.has_board_access( - session2, member=member, board=board, write=False, + session2, + member=member, + board=board, + write=False, ) is True ) @@ -267,7 +279,10 @@ async def test_has_board_access_uses_org_board_access_row_read_and_write() -> No session3 = _FakeSession(exec_results=[_FakeExecResult(first_value=access3)]) assert ( await organizations.has_board_access( - session3, member=member, board=board, write=True, + session3, + member=member, + board=board, + write=True, ) is False ) @@ -288,7 +303,10 @@ async def test_require_board_access_raises_when_no_member( session = _FakeSession(exec_results=[]) with pytest.raises(HTTPException) as exc: await organizations.require_board_access( - session, user=user, board=board, write=False, + session, + user=user, + board=board, + write=False, ) assert exc.value.status_code == 403 @@ -298,24 +316,33 @@ async def test_apply_member_access_update_deletes_existing_and_adds_rows_when_no None ): member = OrganizationMember( - id=uuid4(), organization_id=uuid4(), user_id=uuid4(), role="member", + id=uuid4(), + organization_id=uuid4(), + user_id=uuid4(), + role="member", ) update = OrganizationMemberAccessUpdate( all_boards_read=False, all_boards_write=False, board_access=[ OrganizationBoardAccessSpec( - board_id=uuid4(), can_read=True, can_write=False, + board_id=uuid4(), + can_read=True, + can_write=False, ), OrganizationBoardAccessSpec( - board_id=uuid4(), can_read=True, can_write=True, + board_id=uuid4(), + can_read=True, + can_write=True, ), ], ) session = _FakeSession(exec_results=[]) await organizations.apply_member_access_update( - session, member=member, update=update, + session, + member=member, + update=update, ) # delete statement executed once