Source code for app.services.quota_service
"""Quota enforcement hooks (NOOP unless `auth_mode=api_key`)."""
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import get_settings
from app.core.errors import QuotaExceededError
from app.db.models import GpuUsage, TenantQuota
[docs]
async def get_or_create_quota(session: AsyncSession, *, tenant_id: str) -> TenantQuota:
q = await session.get(TenantQuota, tenant_id)
if q is None:
q = TenantQuota(tenant_id=tenant_id)
session.add(q)
await session.flush()
return q
[docs]
async def check_storage(session: AsyncSession, *, tenant_id: str, additional: int) -> None:
if get_settings().auth_mode == "none":
return
q = await get_or_create_quota(session, tenant_id=tenant_id)
if q.storage_bytes_max is None:
return
if q.storage_bytes_used + additional > q.storage_bytes_max:
raise QuotaExceededError(
f"storage quota: used={q.storage_bytes_used}, max={q.storage_bytes_max}"
)
[docs]
async def check_gpu_seconds(session: AsyncSession, *, tenant_id: str) -> None:
if get_settings().auth_mode == "none":
return
q = await get_or_create_quota(session, tenant_id=tenant_id)
if q.gpu_seconds_per_day_max is None:
return
since = datetime.now(UTC) - timedelta(days=1)
used = (
await session.execute(
select(func.coalesce(func.sum(GpuUsage.gpu_seconds), 0)).where(
GpuUsage.tenant_id == tenant_id, GpuUsage.started_at >= since
)
)
).scalar_one()
if int(used) >= q.gpu_seconds_per_day_max:
raise QuotaExceededError(f"gpu seconds quota: used={used}, max={q.gpu_seconds_per_day_max}")