chore(backend): add black/isort/flake8 + pre-commit

This commit is contained in:
Abhimanyu Saharan
2026-02-02 20:15:38 +05:30
parent 002bd08f33
commit a8f097817d
26 changed files with 299 additions and 67 deletions

View File

@@ -13,7 +13,9 @@ router = APIRouter(prefix="/activities", tags=["activities"])
@router.get("")
def list_activities(limit: int = 50, session: Session = Depends(get_session)):
items = session.exec(select(Activity).order_by(Activity.id.desc()).limit(max(1, min(limit, 200)))).all()
items = session.exec(
select(Activity).order_by(Activity.id.desc()).limit(max(1, min(limit, 200)))
).all()
out = []
for a in items:
out.append(

View File

@@ -7,14 +7,14 @@ from sqlmodel import Session, select
from app.api.utils import get_actor_employee_id, log_activity
from app.db.session import get_session
from app.integrations.openclaw import OpenClawClient
from app.models.org import Department, Team, Employee
from app.models.org import Department, Employee, Team
from app.schemas.org import (
DepartmentCreate,
DepartmentUpdate,
TeamCreate,
TeamUpdate,
EmployeeCreate,
EmployeeUpdate,
TeamCreate,
TeamUpdate,
)
router = APIRouter(tags=["org"])
@@ -31,15 +31,14 @@ def _public_api_base_url() -> str:
Never returns localhost/<avoid-loopback> because agents may run on another machine."""
import os, re, subprocess
import os
import re
import subprocess
explicit = os.environ.get("MISSION_CONTROL_BASE_URL")
if explicit:
return explicit.rstrip("/")
try:
out = subprocess.check_output(["bash", "-lc", "hostname -I"], text=True).strip()
# pick first RFC1918-ish IPv4, skip docker/loopback
@@ -49,12 +48,16 @@ def _public_api_base_url() -> str:
continue
if ip.startswith("172.17."):
continue
if ip.startswith("192.168.") or ip.startswith("10.") or ip.startswith("172.16.") or ip.startswith("172."):
if (
ip.startswith("192.168.")
or ip.startswith("10.")
or ip.startswith("172.16.")
or ip.startswith("172.")
):
return f"http://{ip}:8000"
except Exception:
pass
# Fallback placeholder (should be overridden by env var)
return "http://<dev-machine-ip>:8000"
@@ -202,7 +205,11 @@ def create_team(
entity_type="team",
entity_id=team.id,
verb="created",
payload={"name": team.name, "department_id": team.department_id, "lead_employee_id": team.lead_employee_id},
payload={
"name": team.name,
"department_id": team.department_id,
"lead_employee_id": team.lead_employee_id,
},
)
session.commit()
except IntegrityError:
@@ -231,7 +238,14 @@ def update_team(
session.add(team)
try:
session.flush()
log_activity(session, actor_employee_id=actor_employee_id, entity_type="team", entity_id=team.id, verb="updated", payload=data)
log_activity(
session,
actor_employee_id=actor_employee_id,
entity_type="team",
entity_id=team.id,
verb="updated",
payload=data,
)
session.commit()
except IntegrityError:
session.rollback()
@@ -241,7 +255,6 @@ def update_team(
return team
@router.post("/departments", response_model=Department)
def create_department(
payload: DepartmentCreate,
@@ -270,7 +283,9 @@ def create_department(
session.commit()
except IntegrityError:
session.rollback()
raise HTTPException(status_code=409, detail="Department already exists or violates constraints")
raise HTTPException(
status_code=409, detail="Department already exists or violates constraints"
)
session.refresh(dept)
return dept
@@ -294,7 +309,14 @@ def update_department(
session.add(dept)
session.commit()
session.refresh(dept)
log_activity(session, actor_employee_id=actor_employee_id, entity_type="department", entity_id=dept.id, verb="updated", payload=data)
log_activity(
session,
actor_employee_id=actor_employee_id,
entity_type="department",
entity_id=dept.id,
verb="updated",
payload=data,
)
session.commit()
return dept
@@ -354,7 +376,14 @@ def update_employee(
session.add(emp)
try:
session.flush()
log_activity(session, actor_employee_id=actor_employee_id, entity_type="employee", entity_id=emp.id, verb="updated", payload=data)
log_activity(
session,
actor_employee_id=actor_employee_id,
entity_type="employee",
entity_id=emp.id,
verb="updated",
payload=data,
)
session.commit()
except IntegrityError:
session.rollback()
@@ -401,7 +430,10 @@ def deprovision_employee_agent(
try:
client.tools_invoke(
"sessions_send",
{"sessionKey": emp.openclaw_session_key, "message": "You are being deprovisioned. Stop all work and ignore future messages."},
{
"sessionKey": emp.openclaw_session_key,
"message": "You are being deprovisioned. Stop all work and ignore future messages.",
},
timeout_s=5.0,
)
except Exception:

View File

@@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.exc import IntegrityError
from sqlmodel import Session, select
from app.api.utils import log_activity, get_actor_employee_id
from app.api.utils import get_actor_employee_id, log_activity
from app.db.session import get_session
from app.models.projects import Project, ProjectMember
from app.schemas.projects import ProjectCreate, ProjectUpdate
@@ -45,15 +45,21 @@ def create_project(
session.commit()
except IntegrityError:
session.rollback()
raise HTTPException(status_code=409, detail="Project already exists or violates constraints")
raise HTTPException(
status_code=409, detail="Project already exists or violates constraints"
)
session.refresh(proj)
return proj
@router.patch("/{project_id}", response_model=Project)
def update_project(project_id: int, payload: ProjectUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
def update_project(
project_id: int,
payload: ProjectUpdate,
session: Session = Depends(get_session),
actor_employee_id: int = Depends(get_actor_employee_id),
):
proj = session.get(Project, project_id)
if not proj:
raise HTTPException(status_code=404, detail="Project not found")
@@ -65,7 +71,14 @@ def update_project(project_id: int, payload: ProjectUpdate, session: Session = D
session.add(proj)
session.commit()
session.refresh(proj)
log_activity(session, actor_employee_id=actor_employee_id, entity_type="project", entity_id=proj.id, verb="updated", payload=data)
log_activity(
session,
actor_employee_id=actor_employee_id,
entity_type="project",
entity_id=proj.id,
verb="updated",
payload=data,
)
session.commit()
return proj
@@ -73,16 +86,29 @@ def update_project(project_id: int, payload: ProjectUpdate, session: Session = D
@router.get("/{project_id}/members", response_model=list[ProjectMember])
def list_project_members(project_id: int, session: Session = Depends(get_session)):
return session.exec(
select(ProjectMember).where(ProjectMember.project_id == project_id).order_by(ProjectMember.id.asc())
select(ProjectMember)
.where(ProjectMember.project_id == project_id)
.order_by(ProjectMember.id.asc())
).all()
@router.post("/{project_id}/members", response_model=ProjectMember)
def add_project_member(project_id: int, payload: ProjectMember, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
existing = session.exec(select(ProjectMember).where(ProjectMember.project_id == project_id, ProjectMember.employee_id == payload.employee_id)).first()
def add_project_member(
project_id: int,
payload: ProjectMember,
session: Session = Depends(get_session),
actor_employee_id: int = Depends(get_actor_employee_id),
):
existing = session.exec(
select(ProjectMember).where(
ProjectMember.project_id == project_id, ProjectMember.employee_id == payload.employee_id
)
).first()
if existing:
raise HTTPException(status_code=409, detail="Member already added")
member = ProjectMember(project_id=project_id, employee_id=payload.employee_id, role=payload.role)
member = ProjectMember(
project_id=project_id, employee_id=payload.employee_id, role=payload.role
)
session.add(member)
session.commit()
session.refresh(member)
@@ -99,7 +125,12 @@ def add_project_member(project_id: int, payload: ProjectMember, session: Session
@router.delete("/{project_id}/members/{member_id}")
def remove_project_member(project_id: int, member_id: int, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
def remove_project_member(
project_id: int,
member_id: int,
session: Session = Depends(get_session),
actor_employee_id: int = Depends(get_actor_employee_id),
):
member = session.get(ProjectMember, member_id)
if not member or member.project_id != project_id:
raise HTTPException(status_code=404, detail="Project member not found")
@@ -118,7 +149,13 @@ def remove_project_member(project_id: int, member_id: int, session: Session = De
@router.patch("/{project_id}/members/{member_id}", response_model=ProjectMember)
def update_project_member(project_id: int, member_id: int, payload: ProjectMember, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
def update_project_member(
project_id: int,
member_id: int,
payload: ProjectMember,
session: Session = Depends(get_session),
actor_employee_id: int = Depends(get_actor_employee_id),
):
member = session.get(ProjectMember, member_id)
if not member or member.project_id != project_id:
raise HTTPException(status_code=404, detail="Project member not found")

View File

@@ -2,16 +2,16 @@ from __future__ import annotations
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
from sqlmodel import Session, select
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
from sqlalchemy.exc import IntegrityError
from sqlmodel import Session, select
from app.api.utils import log_activity, get_actor_employee_id
from app.api.utils import get_actor_employee_id, log_activity
from app.db.session import get_session
from app.integrations.notify import NotifyContext, notify_openclaw
from app.models.org import Employee
from app.models.work import Task, TaskComment
from app.schemas.work import TaskCommentCreate, TaskCreate, TaskUpdate
from app.integrations.notify import NotifyContext, notify_openclaw
router = APIRouter(tags=["work"])
@@ -33,7 +33,9 @@ def _validate_task_assignee(session: Session, assignee_employee_id: int) -> None
if emp.status != "active":
raise HTTPException(status_code=400, detail="Cannot assign task to inactive agent")
if not emp.notify_enabled:
raise HTTPException(status_code=400, detail="Cannot assign task to agent with notifications disabled")
raise HTTPException(
status_code=400, detail="Cannot assign task to agent with notifications disabled"
)
if not emp.openclaw_session_key:
raise HTTPException(status_code=400, detail="Cannot assign task to unprovisioned agent")
@@ -47,9 +49,16 @@ def list_tasks(project_id: int | None = None, session: Session = Depends(get_ses
@router.post("/tasks", response_model=Task)
def create_task(payload: TaskCreate, background: BackgroundTasks, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
def create_task(
payload: TaskCreate,
background: BackgroundTasks,
session: Session = Depends(get_session),
actor_employee_id: int = Depends(get_actor_employee_id),
):
if payload.created_by_employee_id is None:
payload = TaskCreate(**{**payload.model_dump(), "created_by_employee_id": actor_employee_id})
payload = TaskCreate(
**{**payload.model_dump(), "created_by_employee_id": actor_employee_id}
)
if payload.assignee_employee_id is not None:
_validate_task_assignee(session, payload.assignee_employee_id)
@@ -58,7 +67,9 @@ def create_task(payload: TaskCreate, background: BackgroundTasks, session: Sessi
if payload.reviewer_employee_id is None and payload.assignee_employee_id is not None:
assignee = session.get(Employee, payload.assignee_employee_id)
if assignee is not None and assignee.manager_id is not None:
payload = TaskCreate(**{**payload.model_dump(), "reviewer_employee_id": assignee.manager_id})
payload = TaskCreate(
**{**payload.model_dump(), "reviewer_employee_id": assignee.manager_id}
)
task = Task(**payload.model_dump())
if task.status not in ALLOWED_STATUSES:
@@ -82,18 +93,32 @@ def create_task(payload: TaskCreate, background: BackgroundTasks, session: Sessi
raise HTTPException(status_code=409, detail="Task create violates constraints")
session.refresh(task)
background.add_task(notify_openclaw, session, NotifyContext(event="task.created", actor_employee_id=actor_employee_id, task=task))
background.add_task(
notify_openclaw,
session,
NotifyContext(event="task.created", actor_employee_id=actor_employee_id, task=task),
)
# Explicitly return a serializable payload (guards against empty {} responses)
return Task.model_validate(task)
@router.patch("/tasks/{task_id}", response_model=Task)
def update_task(task_id: int, payload: TaskUpdate, background: BackgroundTasks, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
def update_task(
task_id: int,
payload: TaskUpdate,
background: BackgroundTasks,
session: Session = Depends(get_session),
actor_employee_id: int = Depends(get_actor_employee_id),
):
task = session.get(Task, task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
before = {"assignee_employee_id": task.assignee_employee_id, "reviewer_employee_id": task.reviewer_employee_id, "status": task.status}
before = {
"assignee_employee_id": task.assignee_employee_id,
"reviewer_employee_id": task.reviewer_employee_id,
"status": task.status,
}
data = payload.model_dump(exclude_unset=True)
if "assignee_employee_id" in data and data["assignee_employee_id"] is not None:
@@ -108,7 +133,14 @@ def update_task(task_id: int, payload: TaskUpdate, background: BackgroundTasks,
try:
session.flush()
log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=task.id, verb="updated", payload=data)
log_activity(
session,
actor_employee_id=actor_employee_id,
entity_type="task",
entity_id=task.id,
verb="updated",
payload=data,
)
session.commit()
except IntegrityError:
session.rollback()
@@ -119,19 +151,53 @@ def update_task(task_id: int, payload: TaskUpdate, background: BackgroundTasks,
# notify based on meaningful changes
changed = {}
if before.get("assignee_employee_id") != task.assignee_employee_id:
changed["assignee_employee_id"] = {"from": before.get("assignee_employee_id"), "to": task.assignee_employee_id}
background.add_task(notify_openclaw, session, NotifyContext(event="task.assigned", actor_employee_id=actor_employee_id, task=task, changed_fields=changed))
changed["assignee_employee_id"] = {
"from": before.get("assignee_employee_id"),
"to": task.assignee_employee_id,
}
background.add_task(
notify_openclaw,
session,
NotifyContext(
event="task.assigned",
actor_employee_id=actor_employee_id,
task=task,
changed_fields=changed,
),
)
if before.get("status") != task.status:
changed["status"] = {"from": before.get("status"), "to": task.status}
background.add_task(notify_openclaw, session, NotifyContext(event="status.changed", actor_employee_id=actor_employee_id, task=task, changed_fields=changed))
background.add_task(
notify_openclaw,
session,
NotifyContext(
event="status.changed",
actor_employee_id=actor_employee_id,
task=task,
changed_fields=changed,
),
)
if not changed and data:
background.add_task(notify_openclaw, session, NotifyContext(event="task.updated", actor_employee_id=actor_employee_id, task=task, changed_fields=data))
background.add_task(
notify_openclaw,
session,
NotifyContext(
event="task.updated",
actor_employee_id=actor_employee_id,
task=task,
changed_fields=data,
),
)
return Task.model_validate(task)
@router.delete("/tasks/{task_id}")
def delete_task(task_id: int, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
def delete_task(
task_id: int,
session: Session = Depends(get_session),
actor_employee_id: int = Depends(get_actor_employee_id),
):
task = session.get(Task, task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
@@ -139,7 +205,13 @@ def delete_task(task_id: int, session: Session = Depends(get_session), actor_emp
session.delete(task)
try:
session.flush()
log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=task_id, verb="deleted")
log_activity(
session,
actor_employee_id=actor_employee_id,
entity_type="task",
entity_id=task_id,
verb="deleted",
)
session.commit()
except IntegrityError:
session.rollback()
@@ -150,20 +222,35 @@ def delete_task(task_id: int, session: Session = Depends(get_session), actor_emp
@router.get("/task-comments", response_model=list[TaskComment])
def list_task_comments(task_id: int, session: Session = Depends(get_session)):
return session.exec(select(TaskComment).where(TaskComment.task_id == task_id).order_by(TaskComment.id.asc())).all()
return session.exec(
select(TaskComment).where(TaskComment.task_id == task_id).order_by(TaskComment.id.asc())
).all()
@router.post("/task-comments", response_model=TaskComment)
def create_task_comment(payload: TaskCommentCreate, background: BackgroundTasks, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
def create_task_comment(
payload: TaskCommentCreate,
background: BackgroundTasks,
session: Session = Depends(get_session),
actor_employee_id: int = Depends(get_actor_employee_id),
):
if payload.author_employee_id is None:
payload = TaskCommentCreate(**{**payload.model_dump(), "author_employee_id": actor_employee_id})
payload = TaskCommentCreate(
**{**payload.model_dump(), "author_employee_id": actor_employee_id}
)
c = TaskComment(**payload.model_dump())
session.add(c)
try:
session.flush()
log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=c.task_id, verb="commented")
log_activity(
session,
actor_employee_id=actor_employee_id,
entity_type="task",
entity_id=c.task_id,
verb="commented",
)
session.commit()
except IntegrityError:
session.rollback()
@@ -172,5 +259,11 @@ def create_task_comment(payload: TaskCommentCreate, background: BackgroundTasks,
session.refresh(c)
task = session.get(Task, c.task_id)
if task is not None:
background.add_task(notify_openclaw, session, NotifyContext(event="comment.created", actor_employee_id=actor_employee_id, task=task, comment=c))
background.add_task(
notify_openclaw,
session,
NotifyContext(
event="comment.created", actor_employee_id=actor_employee_id, task=task, comment=c
),
)
return TaskComment.model_validate(c)