refactor(routes): move cleanup domain into routes/cleanup/ subpackage (#5658)

Slice 2g of the route-domain reorganization (#4082/#4071). Moves
cleanup_routes.py into routes/cleanup/, leaving a backward-compat
sys.modules shim at the old path. Pure file reorganization, no behavior
change.

The shim uses sys.modules replacement so string-targeted
monkeypatch.setattr("routes.cleanup_routes.*", ...) in
test_cleanup_owner_scope.py reaches the canonical module.

Canonical module imports only from src/ and stdlib (zero internal
routes/ coupling). Zero source-introspection landmines.

Adds tests/test_cleanup_routes_shim.py to pin the sys.modules shim
contract. Verified: compileall clean; targeted tests pass.
This commit is contained in:
Tal.Yuan
2026-07-21 18:38:32 +08:00
committed by GitHub
parent cc4c7f4263
commit b7d3f2a28d
5 changed files with 104 additions and 57 deletions
+1 -1
View File
@@ -704,7 +704,7 @@ from routes.diagnostics_routes import setup_diagnostics_routes
app.include_router(setup_diagnostics_routes(rag_manager, rag_available, research_handler, memory_vector))
# Cleanup
from routes.cleanup_routes import setup_cleanup_routes
from routes.cleanup.cleanup_routes import setup_cleanup_routes
app.include_router(setup_cleanup_routes(session_manager))
# Personal docs
+5
View File
@@ -0,0 +1,5 @@
"""Cleanup route domain package (slice 2g, #4082/#4071).
Contains cleanup_routes.py, migrated from the flat routes/ directory.
Backward-compat shim at routes/cleanup_routes.py re-exports from here.
"""
+60
View File
@@ -0,0 +1,60 @@
# routes/cleanup_routes.py
"""Routes for cleanup operations."""
import logging
from fastapi import APIRouter, HTTPException, Request
from src.cleanup_service import get_cleanup_preview, cleanup_sessions
from src.auth_helpers import get_current_user
logger = logging.getLogger(__name__)
def setup_cleanup_routes(session_manager):
"""
Setup cleanup-related routes.
Args:
session_manager: SessionManager instance
Returns:
APIRouter instance with cleanup routes
"""
router = APIRouter(prefix="/api/cleanup")
@router.get("/preview")
async def cleanup_preview(request: Request):
"""
Preview what would be cleaned up without making any changes.
Returns:
JSON response with lists of sessions that would be archived/deleted and estimated space savings
"""
user = get_current_user(request)
try:
preview = await get_cleanup_preview(owner=user)
return preview
except Exception as e:
logger.error(f"Cleanup preview failed: {e}")
raise HTTPException(500, "Cleanup preview generation failed")
@router.post("")
async def cleanup_endpoint(request: Request):
"""
Perform cleanup operations:
1. Archive inactive sessions (not accessed for 7 days)
2. Delete old sessions (archived, not important, not accessed for 14+ days, with fewer than 10 messages)
Returns:
JSON response with counts of deleted and archived sessions, and space freed
"""
user = get_current_user(request)
try:
archived_count, deleted_count, space_freed_mb = await cleanup_sessions(session_manager, owner=user)
return {
"archived_count": archived_count,
"deleted_count": deleted_count,
"space_freed_mb": round(space_freed_mb, 2)
}
except Exception as e:
logger.error(f"Cleanup failed: {e}")
raise HTTPException(500, "Cleanup operation failed")
return router
+13 -56
View File
@@ -1,60 +1,17 @@
# routes/cleanup_routes.py
"""Routes for cleanup operations."""
import logging
from fastapi import APIRouter, HTTPException, Request
from src.cleanup_service import get_cleanup_preview, cleanup_sessions
from src.auth_helpers import get_current_user
"""Backward-compat shim — canonical location is routes/cleanup/cleanup_routes.py.
logger = logging.getLogger(__name__)
This module is replaced in ``sys.modules`` by the canonical module object so
that ``import routes.cleanup_routes``, ``from routes.cleanup_routes import X``,
``importlib.import_module("routes.cleanup_routes")``, and the string-targeted
``monkeypatch.setattr("routes.cleanup_routes.get_cleanup_preview", ...)`` /
``"routes.cleanup_routes.get_current_user"`` / ``"routes.cleanup_routes.
cleanup_sessions"`` pattern used by test_cleanup_owner_scope.py all operate
on the *same* object the application actually uses. Keeps existing import
paths working after slice 2g (#4082/#4071).
"""
def setup_cleanup_routes(session_manager):
"""
Setup cleanup-related routes.
import sys as _sys
Args:
session_manager: SessionManager instance
from routes.cleanup import cleanup_routes as _canonical # noqa: F401
Returns:
APIRouter instance with cleanup routes
"""
router = APIRouter(prefix="/api/cleanup")
@router.get("/preview")
async def cleanup_preview(request: Request):
"""
Preview what would be cleaned up without making any changes.
Returns:
JSON response with lists of sessions that would be archived/deleted and estimated space savings
"""
user = get_current_user(request)
try:
preview = await get_cleanup_preview(owner=user)
return preview
except Exception as e:
logger.error(f"Cleanup preview failed: {e}")
raise HTTPException(500, "Cleanup preview generation failed")
@router.post("")
async def cleanup_endpoint(request: Request):
"""
Perform cleanup operations:
1. Archive inactive sessions (not accessed for 7 days)
2. Delete old sessions (archived, not important, not accessed for 14+ days, with fewer than 10 messages)
Returns:
JSON response with counts of deleted and archived sessions, and space freed
"""
user = get_current_user(request)
try:
archived_count, deleted_count, space_freed_mb = await cleanup_sessions(session_manager, owner=user)
return {
"archived_count": archived_count,
"deleted_count": deleted_count,
"space_freed_mb": round(space_freed_mb, 2)
}
except Exception as e:
logger.error(f"Cleanup failed: {e}")
raise HTTPException(500, "Cleanup operation failed")
return router
_sys.modules[__name__] = _canonical
+25
View File
@@ -0,0 +1,25 @@
"""Regression test for the cleanup route shim (slice 2g, #4082/#4071).
The backward-compat shim at ``routes/cleanup_routes.py`` uses ``sys.modules``
replacement so the legacy import path and the canonical ``routes.cleanup.*``
path resolve to the *same* module object. This is required because
``test_cleanup_owner_scope.py`` uses string-targeted
``monkeypatch.setattr("routes.cleanup_routes.get_cleanup_preview", ...)`` and
``monkeypatch.delitem(sys.modules, "routes.cleanup_routes")`` + re-import —
for those patches to take effect at runtime, the legacy module object and
the canonical one must be identical.
"""
import importlib
import routes.cleanup_routes as _shim_cleanup # noqa: F401
def test_legacy_and_canonical_cleanup_module_are_same_object():
"""``import routes.cleanup_routes`` must alias the canonical module."""
legacy = importlib.import_module("routes.cleanup_routes")
canonical = importlib.import_module("routes.cleanup.cleanup_routes")
assert legacy is canonical, (
"routes.cleanup_routes shim must resolve to the canonical "
"routes.cleanup.cleanup_routes module object"
)