chore(quality): apply ruff autofixes and formatting (223 fixes, regression-free: 118/118 tests pass)

This commit is contained in:
Agent Zero
2026-06-10 21:24:24 +00:00
parent c53af91d74
commit aec40d03ac
56 changed files with 459 additions and 528 deletions
+9 -5
View File
@@ -11,7 +11,7 @@ router layer passes `current_user.org_id`.
from __future__ import annotations
from typing import Any, Optional
from typing import Any
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -44,7 +44,7 @@ class OrgScopedQuery:
self,
skip: int = 0,
limit: int = 20,
order_by: Optional[Any] = None,
order_by: Any | None = None,
**filters: Any,
) -> list[Any]:
"""List records scoped to org, with optional filters and pagination."""
@@ -65,9 +65,13 @@ class OrgScopedQuery:
"""Count records scoped to org, with optional filters."""
from sqlalchemy import func as sa_func
stmt = select(sa_func.count()).select_from(self.model).where(
self.model.org_id == self.org_id,
self.model.deleted_at.is_(None),
stmt = (
select(sa_func.count())
.select_from(self.model)
.where(
self.model.org_id == self.org_id,
self.model.deleted_at.is_(None),
)
)
for field, value in filters.items():
if value is not None:
+6 -12
View File
@@ -3,7 +3,6 @@
from __future__ import annotations
from datetime import UTC, datetime
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
@@ -19,7 +18,6 @@ async def create_account(
db: AsyncSession, payload: AccountCreate, *, org_id: int, owner_id: int
) -> Account:
"""Create a new account in the given org."""
from app.services._base import OrgScopedQuery
account = Account(
org_id=org_id,
@@ -36,9 +34,7 @@ async def create_account(
return account
async def get_account(
db: AsyncSession, account_id: int, *, org_id: int
) -> Optional[Account]:
async def get_account(db: AsyncSession, account_id: int, *, org_id: int) -> Account | None:
"""Fetch a single account by id (org-scoped, not soft-deleted)."""
from app.services._base import OrgScopedQuery
@@ -52,10 +48,10 @@ async def list_accounts(
org_id: int,
skip: int = 0,
limit: int = 20,
industry: Optional[str] = None,
size: Optional[str] = None,
owner_id: Optional[int] = None,
q: Optional[str] = None,
industry: str | None = None,
size: str | None = None,
owner_id: int | None = None,
q: str | None = None,
) -> list[Account]:
"""List accounts with filters and search."""
from app.services._base import OrgScopedQuery
@@ -76,9 +72,7 @@ async def list_accounts(
return base
async def update_account(
db: AsyncSession, account: Account, payload: AccountUpdate
) -> Account:
async def update_account(db: AsyncSession, account: Account, payload: AccountUpdate) -> Account:
"""Apply partial updates to an account."""
data = payload.model_dump(exclude_unset=True)
for field, value in data.items():
+14 -15
View File
@@ -3,7 +3,6 @@
from __future__ import annotations
from datetime import UTC, datetime
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
@@ -24,9 +23,9 @@ async def _validate_parents(
db: AsyncSession,
*,
org_id: int,
account_id: Optional[int],
contact_id: Optional[int],
deal_id: Optional[int],
account_id: int | None,
contact_id: int | None,
deal_id: int | None,
) -> None:
"""Ensure at least one parent is set and each provided id exists in org."""
if account_id is None and contact_id is None and deal_id is None:
@@ -35,14 +34,17 @@ async def _validate_parents(
)
if account_id is not None:
from app.models.account import Account
if await OrgScopedQuery(Account, db, org_id=org_id).get(account_id) is None:
raise NoParentException(f"Account {account_id} not found in this org")
if contact_id is not None:
from app.models.contact import Contact
if await OrgScopedQuery(Contact, db, org_id=org_id).get(contact_id) is None:
raise NoParentException(f"Contact {contact_id} not found in this org")
if deal_id is not None:
from app.models.deal import Deal
if await OrgScopedQuery(Deal, db, org_id=org_id).get(deal_id) is None:
raise NoParentException(f"Deal {deal_id} not found in this org")
@@ -79,9 +81,7 @@ async def create_activity(
return activity
async def get_activity(
db: AsyncSession, activity_id: int, *, org_id: int
) -> Optional[Activity]:
async def get_activity(db: AsyncSession, activity_id: int, *, org_id: int) -> Activity | None:
"""Fetch a single activity by id."""
q = OrgScopedQuery(Activity, db, org_id=org_id)
return await q.get(activity_id)
@@ -93,10 +93,10 @@ async def list_activities(
org_id: int,
skip: int = 0,
limit: int = 20,
type: Optional[ActivityType] = None,
owner_id: Optional[int] = None,
overdue: Optional[bool] = None,
completed: Optional[bool] = None,
type: ActivityType | None = None,
owner_id: int | None = None,
overdue: bool | None = None,
completed: bool | None = None,
) -> list[Activity]:
"""List activities with optional filters."""
scoped = OrgScopedQuery(Activity, db, org_id=org_id)
@@ -110,7 +110,8 @@ async def list_activities(
now = datetime.now(UTC).replace(tzinfo=None) # SQLite returns naive datetimes
if overdue is True:
base = [
a for a in base
a
for a in base
if a.due_date is not None and a.due_date < now and a.completed_at is None
]
if completed is True:
@@ -162,9 +163,7 @@ async def complete_activity(
return activity
async def soft_delete_activity(
db: AsyncSession, activity: Activity
) -> Activity:
async def soft_delete_activity(db: AsyncSession, activity: Activity) -> Activity:
"""Soft-delete an activity."""
activity.deleted_at = datetime.now(UTC)
await db.commit()
+4 -14
View File
@@ -2,8 +2,6 @@
from __future__ import annotations
from typing import Optional
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession
@@ -35,15 +33,11 @@ class InvalidCredentials(AuthError):
async def count_users(db: AsyncSession) -> int:
"""Count active (non-soft-deleted) users. Used to gate bootstrap registration."""
result = await db.execute(
select(User).where(User.deleted_at.is_(None))
)
result = await db.execute(select(User).where(User.deleted_at.is_(None)))
return len(result.scalars().all())
async def register_user(
db: AsyncSession, payload: UserRegisterRequest
) -> tuple[User, str]:
async def register_user(db: AsyncSession, payload: UserRegisterRequest) -> tuple[User, str]:
"""Bootstrap registration.
Creates a new Org and the first user (or a new user in the existing org
@@ -81,9 +75,7 @@ async def register_user(
await db.commit()
except IntegrityError as e:
await db.rollback()
raise EmailAlreadyExists(
f"A user with email {payload.email!r} already exists."
) from e
raise EmailAlreadyExists(f"A user with email {payload.email!r} already exists.") from e
await db.refresh(user)
@@ -92,9 +84,7 @@ async def register_user(
return user, token
async def authenticate_user(
db: AsyncSession, email: str, password: str
) -> Optional[tuple[User, str]]:
async def authenticate_user(db: AsyncSession, email: str, password: str) -> tuple[User, str] | None:
"""Verify credentials and return (user, token) on success, None on failure.
The endpoint wraps this and returns 401 on None — keeping the
+6 -13
View File
@@ -3,7 +3,6 @@
from __future__ import annotations
from datetime import UTC, datetime
from typing import Optional
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession
@@ -22,9 +21,7 @@ class InvalidAccount(Exception):
"""Raised when account_id points to a non-existent account."""
async def _validate_account(
db: AsyncSession, account_id: int, *, org_id: int
) -> None:
async def _validate_account(db: AsyncSession, account_id: int, *, org_id: int) -> None:
"""Raise if account_id does not exist within the org."""
if account_id is None:
return
@@ -62,9 +59,7 @@ async def create_contact(
return contact
async def get_contact(
db: AsyncSession, contact_id: int, *, org_id: int
) -> Optional[Contact]:
async def get_contact(db: AsyncSession, contact_id: int, *, org_id: int) -> Contact | None:
"""Fetch a single contact by id."""
q = OrgScopedQuery(Contact, db, org_id=org_id)
return await q.get(contact_id)
@@ -76,9 +71,9 @@ async def list_contacts(
org_id: int,
skip: int = 0,
limit: int = 20,
account_id: Optional[int] = None,
owner_id: Optional[int] = None,
q: Optional[str] = None,
account_id: int | None = None,
owner_id: int | None = None,
q: str | None = None,
) -> list[Contact]:
"""List contacts with filters and search."""
scoped = OrgScopedQuery(Contact, db, org_id=org_id)
@@ -101,9 +96,7 @@ async def list_contacts(
return base
async def update_contact(
db: AsyncSession, contact: Contact, payload: ContactUpdate
) -> Contact:
async def update_contact(db: AsyncSession, contact: Contact, payload: ContactUpdate) -> Contact:
"""Apply partial updates to a contact."""
data = payload.model_dump(exclude_unset=True)
if "account_id" in data and data["account_id"] is not None:
+3 -8
View File
@@ -22,16 +22,13 @@ async def get_kpis(db: AsyncSession, *, org_id: int) -> dict[str, object]:
open_deals = [d for d in all_deals if d.stage in open_stages]
open_deals_count = len(open_deals)
pipeline_value = float(
sum((d.value for d in open_deals), Decimal("0"))
)
pipeline_value = float(sum((d.value for d in open_deals), Decimal("0")))
now = datetime.now(UTC)
# SQLite returns naive datetimes; strip tz for comparison
month_start = now.replace(tzinfo=None, day=1, hour=0, minute=0, second=0, microsecond=0)
won_this_month = [
d for d in all_deals
if d.stage == DealStage.won and d.created_at >= month_start
d for d in all_deals if d.stage == DealStage.won and d.created_at >= month_start
]
won_count = len(won_this_month)
@@ -50,9 +47,7 @@ async def get_kpis(db: AsyncSession, *, org_id: int) -> dict[str, object]:
}
async def get_activity_feed(
db: AsyncSession, *, org_id: int, limit: int = 20
) -> list[Activity]:
async def get_activity_feed(db: AsyncSession, *, org_id: int, limit: int = 20) -> list[Activity]:
"""Return the most recent activities, ordered by created_at desc."""
result = await db.execute(
select(Activity)
+7 -14
View File
@@ -5,7 +5,6 @@ from __future__ import annotations
from collections import defaultdict
from datetime import UTC, datetime
from decimal import Decimal
from typing import Optional
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession
@@ -75,9 +74,7 @@ async def create_deal(
return deal
async def get_deal(
db: AsyncSession, deal_id: int, *, org_id: int
) -> Optional[Deal]:
async def get_deal(db: AsyncSession, deal_id: int, *, org_id: int) -> Deal | None:
"""Fetch a single deal by id."""
q = OrgScopedQuery(Deal, db, org_id=org_id)
return await q.get(deal_id)
@@ -89,9 +86,9 @@ async def list_deals(
org_id: int,
skip: int = 0,
limit: int = 20,
stage: Optional[str] = None,
owner_id: Optional[int] = None,
account_id: Optional[int] = None,
stage: str | None = None,
owner_id: int | None = None,
account_id: int | None = None,
) -> list[Deal]:
"""List deals with optional filters."""
scoped = OrgScopedQuery(Deal, db, org_id=org_id)
@@ -105,9 +102,7 @@ async def list_deals(
)
async def update_deal(
db: AsyncSession, deal: Deal, payload: DealUpdate
) -> Deal:
async def update_deal(db: AsyncSession, deal: Deal, payload: DealUpdate) -> Deal:
"""Apply partial updates to a deal. Does NOT change stage (use update_stage)."""
data = payload.model_dump(exclude_unset=True)
# Disallow direct stage changes via update endpoint
@@ -126,7 +121,7 @@ async def update_stage(
new_stage: DealStage,
*,
changed_by: int,
reason: Optional[str] = None,
reason: str | None = None,
) -> Deal:
"""Change a deal's stage and append a DealStageHistory record."""
from_stage_str = _stage_value(deal.stage)
@@ -151,9 +146,7 @@ async def update_stage(
return deal
async def get_pipeline(
db: AsyncSession, *, org_id: int
) -> list[dict[str, object]]:
async def get_pipeline(db: AsyncSession, *, org_id: int) -> list[dict[str, object]]:
"""Return deals grouped by stage for the pipeline view."""
scoped = OrgScopedQuery(Deal, db, org_id=org_id)
all_deals = await scoped.list(skip=0, limit=10_000, order_by=Deal.id)
+7 -18
View File
@@ -3,7 +3,6 @@
from __future__ import annotations
from datetime import UTC, datetime
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
@@ -37,19 +36,13 @@ async def _validate_parent(
"""Verify the (parent_type, parent_id) tuple points to an existing entity in org."""
if parent_type == NoteParentType.account:
if await OrgScopedQuery(Account, db, org_id=org_id).get(parent_id) is None:
raise InvalidParent(
f"Account {parent_id} not found in this org (R-2 validation)"
)
raise InvalidParent(f"Account {parent_id} not found in this org (R-2 validation)")
elif parent_type == NoteParentType.contact:
if await OrgScopedQuery(Contact, db, org_id=org_id).get(parent_id) is None:
raise InvalidParent(
f"Contact {parent_id} not found in this org (R-2 validation)"
)
raise InvalidParent(f"Contact {parent_id} not found in this org (R-2 validation)")
elif parent_type == NoteParentType.deal:
if await OrgScopedQuery(Deal, db, org_id=org_id).get(parent_id) is None:
raise InvalidParent(
f"Deal {parent_id} not found in this org (R-2 validation)"
)
raise InvalidParent(f"Deal {parent_id} not found in this org (R-2 validation)")
async def create_note(
@@ -84,9 +77,7 @@ async def create_note(
return note
async def get_note(
db: AsyncSession, note_id: int, *, org_id: int
) -> Optional[Note]:
async def get_note(db: AsyncSession, note_id: int, *, org_id: int) -> Note | None:
"""Fetch a single note by id."""
q = OrgScopedQuery(Note, db, org_id=org_id)
return await q.get(note_id)
@@ -98,8 +89,8 @@ async def list_notes(
org_id: int,
skip: int = 0,
limit: int = 20,
parent_type: Optional[str] = None,
parent_id: Optional[int] = None,
parent_type: str | None = None,
parent_id: int | None = None,
) -> list[Note]:
"""List notes with optional parent filters."""
scoped = OrgScopedQuery(Note, db, org_id=org_id)
@@ -112,9 +103,7 @@ async def list_notes(
)
async def update_note(
db: AsyncSession, note: Note, payload: NoteUpdate
) -> Note:
async def update_note(db: AsyncSession, note: Note, payload: NoteUpdate) -> Note:
"""Apply partial updates to a note. Does NOT change parent (notes are pinned)."""
data = payload.model_dump(exclude_unset=True)
data.pop("parent_type", None)
+6 -17
View File
@@ -3,7 +3,6 @@
from __future__ import annotations
from datetime import UTC, datetime
from typing import Optional
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession
@@ -39,24 +38,16 @@ async def _validate_parent(
"""Verify (parent_type, parent_id) points to an existing entity in org."""
if parent_type == TagLinkParentType.account:
if await OrgScopedQuery(Account, db, org_id=org_id).get(parent_id) is None:
raise InvalidParent(
f"Account {parent_id} not found in this org (R-2 validation)"
)
raise InvalidParent(f"Account {parent_id} not found in this org (R-2 validation)")
elif parent_type == TagLinkParentType.contact:
if await OrgScopedQuery(Contact, db, org_id=org_id).get(parent_id) is None:
raise InvalidParent(
f"Contact {parent_id} not found in this org (R-2 validation)"
)
raise InvalidParent(f"Contact {parent_id} not found in this org (R-2 validation)")
elif parent_type == TagLinkParentType.deal:
if await OrgScopedQuery(Deal, db, org_id=org_id).get(parent_id) is None:
raise InvalidParent(
f"Deal {parent_id} not found in this org (R-2 validation)"
)
raise InvalidParent(f"Deal {parent_id} not found in this org (R-2 validation)")
async def create_tag(
db: AsyncSession, payload: TagCreate, *, org_id: int
) -> Tag:
async def create_tag(db: AsyncSession, payload: TagCreate, *, org_id: int) -> Tag:
"""Create a new tag in the given org."""
tag = Tag(
org_id=org_id,
@@ -75,14 +66,12 @@ async def list_tags(db: AsyncSession, *, org_id: int) -> list[Tag]:
return await scoped.list(skip=0, limit=1000, order_by=Tag.id)
async def get_tag(db: AsyncSession, tag_id: int, *, org_id: int) -> Optional[Tag]:
async def get_tag(db: AsyncSession, tag_id: int, *, org_id: int) -> Tag | None:
"""Fetch a single tag by id."""
return await OrgScopedQuery(Tag, db, org_id=org_id).get(tag_id)
async def link_tag(
db: AsyncSession, payload: TagLinkCreate, *, org_id: int
) -> TagLink:
async def link_tag(db: AsyncSession, payload: TagLinkCreate, *, org_id: int) -> TagLink:
"""Link a tag to an entity. R-2: validates parent exists."""
parent_type = (
payload.parent_type
+7 -18
View File
@@ -2,8 +2,7 @@
from __future__ import annotations
from datetime import datetime, UTC
from typing import Optional
from datetime import UTC, datetime
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -21,17 +20,13 @@ class EmailAlreadyTaken(Exception):
"""Raised when attempting to create/update a user with an existing email."""
async def get_user_by_id(db: AsyncSession, user_id: int) -> Optional[User]:
async def get_user_by_id(db: AsyncSession, user_id: int) -> User | None:
"""Fetch a user by ID (active only, soft-deleted excluded)."""
result = await db.execute(
select(User).where(User.id == user_id, User.deleted_at.is_(None))
)
result = await db.execute(select(User).where(User.id == user_id, User.deleted_at.is_(None)))
return result.scalar_one_or_none()
async def get_user_by_email(
db: AsyncSession, email: str, org_id: Optional[int] = None
) -> Optional[User]:
async def get_user_by_email(db: AsyncSession, email: str, org_id: int | None = None) -> User | None:
"""Fetch a user by email, optionally scoped to an org."""
stmt = select(User).where(
User.email == email.lower(),
@@ -73,15 +68,11 @@ async def soft_delete_user(db: AsyncSession, user: User) -> User:
return user
async def create_user_as_admin(
db: AsyncSession, payload: UserCreateRequest, org_id: int
) -> User:
async def create_user_as_admin(db: AsyncSession, payload: UserCreateRequest, org_id: int) -> User:
"""Create a new user in the given org (admin-only flow)."""
existing = await get_user_by_email(db, payload.email, org_id=org_id)
if existing is not None:
raise EmailAlreadyTaken(
f"A user with email {payload.email!r} already exists in this org."
)
raise EmailAlreadyTaken(f"A user with email {payload.email!r} already exists in this org.")
user = User(
org_id=org_id,
@@ -96,9 +87,7 @@ async def create_user_as_admin(
return user
async def list_users(
db: AsyncSession, org_id: int, skip: int = 0, limit: int = 50
) -> list[User]:
async def list_users(db: AsyncSession, org_id: int, skip: int = 0, limit: int = 50) -> list[User]:
"""List active users in an org, paginated."""
result = await db.execute(
select(User)