chore: fix all ruff lint errors + format — 0 errors, 306 tests pass
This commit is contained in:
+12
-7
@@ -5,20 +5,20 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import secrets
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
import redis.asyncio as aioredis
|
||||
from passlib.context import CryptContext
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.config import get_settings
|
||||
from app.models.user import User, UserTenant
|
||||
from app.models.role import Role
|
||||
from app.models.session import Session as SessionModel
|
||||
from app.models.user import User
|
||||
|
||||
_pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto", bcrypt__rounds=get_settings().bcrypt_rounds)
|
||||
_pwd_context = CryptContext(
|
||||
schemes=["bcrypt"], deprecated="auto", bcrypt__rounds=get_settings().bcrypt_rounds
|
||||
)
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
@@ -63,7 +63,7 @@ async def create_session(
|
||||
settings = get_settings()
|
||||
session_id = str(uuid.uuid4())
|
||||
csrf_token = generate_csrf_token()
|
||||
expires_at = datetime.now(timezone.utc) + timedelta(seconds=settings.session_ttl_seconds)
|
||||
expires_at = datetime.now(UTC) + timedelta(seconds=settings.session_ttl_seconds)
|
||||
|
||||
# Redis runtime session
|
||||
session_data: dict[str, Any] = {
|
||||
@@ -76,6 +76,7 @@ async def create_session(
|
||||
"is_active": user.is_active,
|
||||
}
|
||||
import json
|
||||
|
||||
await redis.setex(
|
||||
f"session:{session_id}",
|
||||
settings.session_ttl_seconds,
|
||||
@@ -99,6 +100,7 @@ async def create_session(
|
||||
async def get_session_data(redis: aioredis.Redis, session_id: str) -> dict[str, Any] | None:
|
||||
"""Retrieve session data from Redis."""
|
||||
import json
|
||||
|
||||
raw = await redis.get(f"session:{session_id}")
|
||||
if raw is None:
|
||||
return None
|
||||
@@ -117,6 +119,7 @@ async def update_session_tenant(
|
||||
) -> dict[str, Any] | None:
|
||||
"""Update the active tenant in a Redis session."""
|
||||
import json
|
||||
|
||||
settings = get_settings()
|
||||
raw = await redis.get(f"session:{session_id}")
|
||||
if raw is None:
|
||||
@@ -130,7 +133,9 @@ async def update_session_tenant(
|
||||
return data
|
||||
|
||||
|
||||
def check_permission(role_name: str, module: str, action: str, permissions: dict | None = None) -> bool:
|
||||
def check_permission(
|
||||
role_name: str, module: str, action: str, permissions: dict | None = None
|
||||
) -> bool:
|
||||
"""Check if a role has permission for a module+action.
|
||||
Built-in roles: admin (all), editor (read+write), viewer (read only).
|
||||
Custom roles use the permissions dict.
|
||||
|
||||
@@ -9,7 +9,6 @@ import redis.asyncio as aioredis
|
||||
|
||||
from app.config import get_settings
|
||||
|
||||
|
||||
_cache_redis: aioredis.Redis | None = None
|
||||
|
||||
|
||||
|
||||
+13
-15
@@ -3,10 +3,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import uuid
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any
|
||||
from datetime import datetime
|
||||
from typing import Any # noqa: F401
|
||||
|
||||
from sqlalchemy import event as sa_event
|
||||
from sqlalchemy import DateTime, String, func, text # noqa: F401
|
||||
from sqlalchemy import event as sa_event # noqa: F401
|
||||
from sqlalchemy.dialects.postgresql import UUID as PGUUID
|
||||
from sqlalchemy.ext.asyncio import (
|
||||
AsyncEngine,
|
||||
AsyncSession,
|
||||
@@ -14,10 +18,6 @@ from sqlalchemy.ext.asyncio import (
|
||||
create_async_engine,
|
||||
)
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, declared_attr, mapped_column
|
||||
from sqlalchemy import text, String, DateTime, func
|
||||
from sqlalchemy.dialects.postgresql import UUID as PGUUID
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from app.config import get_settings
|
||||
|
||||
@@ -26,8 +26,8 @@ class Base(DeclarativeBase):
|
||||
"""Declarative base with shared columns and tenant-scoping support."""
|
||||
|
||||
@declared_attr.directive
|
||||
def __tablename__(cls) -> str:
|
||||
return cls.__name__.lower() + "s"
|
||||
def __tablename__(self) -> str:
|
||||
return self.__name__.lower() + "s"
|
||||
|
||||
|
||||
class TimestampMixin:
|
||||
@@ -44,9 +44,7 @@ class TimestampMixin:
|
||||
class TenantMixin(TimestampMixin):
|
||||
"""Adds tenant_id column and enables ORM-level auto-filtering."""
|
||||
|
||||
tenant_id: Mapped[uuid.UUID] = mapped_column(
|
||||
PGUUID(as_uuid=True), nullable=False, index=True
|
||||
)
|
||||
tenant_id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False, index=True)
|
||||
|
||||
|
||||
# Global engine and session factory
|
||||
@@ -101,7 +99,9 @@ async def set_tenant_context(session: AsyncSession, tenant_id: uuid.UUID | str)
|
||||
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
async def create_db_session(tenant_id: uuid.UUID | str | None = None) -> AsyncGenerator[AsyncSession, None]:
|
||||
async def create_db_session(
|
||||
tenant_id: uuid.UUID | str | None = None,
|
||||
) -> AsyncGenerator[AsyncSession, None]:
|
||||
"""Create a standalone session outside FastAPI (e.g. for tests/workers)."""
|
||||
factory = get_session_factory()
|
||||
async with factory() as session:
|
||||
@@ -128,7 +128,5 @@ def reset_engine_for_testing(engine: AsyncEngine) -> async_sessionmaker[AsyncSes
|
||||
"""Replace the global engine with a test engine. Returns a session factory."""
|
||||
global _engine, _session_factory
|
||||
_engine = engine
|
||||
_session_factory = async_sessionmaker(
|
||||
bind=engine, expire_on_commit=False, class_=AsyncSession
|
||||
)
|
||||
_session_factory = async_sessionmaker(bind=engine, expire_on_commit=False, class_=AsyncSession)
|
||||
return _session_factory
|
||||
|
||||
@@ -4,7 +4,8 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections import defaultdict
|
||||
from typing import Any, Callable, Coroutine
|
||||
from collections.abc import Callable, Coroutine
|
||||
from typing import Any
|
||||
|
||||
EventHandler = Callable[[dict[str, Any]], Coroutine[Any, Any, None]]
|
||||
|
||||
@@ -48,4 +49,5 @@ def register_workflow_event_handlers() -> None:
|
||||
Should be called during application startup.
|
||||
"""
|
||||
from app.workflows.engine import register_workflow_event_handlers as _register
|
||||
|
||||
_register()
|
||||
|
||||
+20
-10
@@ -3,9 +3,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import UTC
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import select, func, update
|
||||
from sqlalchemy import func, select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.notification import Notification
|
||||
@@ -42,9 +43,13 @@ async def list_notifications(
|
||||
"""List notifications for a user, unread first, then by created_at desc."""
|
||||
offset = (page - 1) * page_size
|
||||
# Count total
|
||||
count_q = select(func.count()).select_from(Notification).where(
|
||||
Notification.tenant_id == tenant_id,
|
||||
Notification.user_id == user_id,
|
||||
count_q = (
|
||||
select(func.count())
|
||||
.select_from(Notification)
|
||||
.where(
|
||||
Notification.tenant_id == tenant_id,
|
||||
Notification.user_id == user_id,
|
||||
)
|
||||
)
|
||||
total = (await db.execute(count_q)).scalar() or 0
|
||||
|
||||
@@ -80,7 +85,8 @@ async def mark_notification_read(
|
||||
notification_id: uuid.UUID,
|
||||
) -> Notification | None:
|
||||
"""Mark a notification as read."""
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime
|
||||
|
||||
q = (
|
||||
update(Notification)
|
||||
.where(
|
||||
@@ -88,7 +94,7 @@ async def mark_notification_read(
|
||||
Notification.tenant_id == tenant_id,
|
||||
Notification.user_id == user_id,
|
||||
)
|
||||
.values(read_at=datetime.now(timezone.utc))
|
||||
.values(read_at=datetime.now(UTC))
|
||||
.returning(Notification)
|
||||
)
|
||||
result = await db.execute(q)
|
||||
@@ -102,10 +108,14 @@ async def get_unread_count(
|
||||
user_id: uuid.UUID,
|
||||
) -> int:
|
||||
"""Get unread notification count for a user."""
|
||||
q = select(func.count()).select_from(Notification).where(
|
||||
Notification.tenant_id == tenant_id,
|
||||
Notification.user_id == user_id,
|
||||
Notification.read_at.is_(None),
|
||||
q = (
|
||||
select(func.count())
|
||||
.select_from(Notification)
|
||||
.where(
|
||||
Notification.tenant_id == tenant_id,
|
||||
Notification.user_id == user_id,
|
||||
Notification.read_at.is_(None),
|
||||
)
|
||||
)
|
||||
result = await db.execute(q)
|
||||
return result.scalar() or 0
|
||||
|
||||
@@ -5,7 +5,6 @@ from __future__ import annotations
|
||||
from fastapi import HTTPException, Request, status
|
||||
|
||||
from app.core.auth import get_redis
|
||||
from app.config import get_settings
|
||||
|
||||
|
||||
async def check_rate_limit(
|
||||
|
||||
+1
-3
@@ -3,10 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import event as sa_event
|
||||
from sqlalchemy.orm import Session, with_loader_criteria
|
||||
from sqlalchemy.sql import Select
|
||||
|
||||
from app.core.db import TenantMixin
|
||||
@@ -21,4 +18,5 @@ def apply_tenant_filter(query: Select, tenant_id: uuid.UUID) -> Select:
|
||||
async def set_rls_context(session, tenant_id: uuid.UUID | str) -> None:
|
||||
"""Set PostgreSQL RLS session variable."""
|
||||
from app.core.db import set_tenant_context
|
||||
|
||||
await set_tenant_context(session, tenant_id)
|
||||
|
||||
Reference in New Issue
Block a user