refactor: extract shared file-status query logic into utility function
- Created app/utils/file_queries.py with apply_status_filter function - Updated app/api/files.py to use shared function - Updated app/views/files.py to use shared function - Removed unused 'or_' import from app/api/files.py - Added comprehensive tests in tests/test_file_queries.py - All tests pass (10 new tests, 14 existing tests verified) Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
+3
-31
@@ -9,7 +9,7 @@ import uuid
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, Query, Request, UploadFile
|
||||
from sqlalchemy import asc, desc, or_
|
||||
from sqlalchemy import asc, desc
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.auth import require_login
|
||||
@@ -18,6 +18,7 @@ from app.database import get_db
|
||||
from app.models import FileRecord, ProcessingLog
|
||||
from app.tasks.convert_to_pdf import convert_to_pdf
|
||||
from app.tasks.process_document import process_document
|
||||
from app.utils.file_queries import apply_status_filter
|
||||
from app.utils.file_status import get_files_processing_status
|
||||
from app.utils.filename_utils import sanitize_filename
|
||||
|
||||
@@ -85,36 +86,7 @@ def list_files_api(
|
||||
query = query.filter(FileRecord.mime_type == mime_type)
|
||||
|
||||
# Apply status filter (before pagination for correct counts)
|
||||
if status:
|
||||
# Subquery to get file IDs matching the status
|
||||
if status == "pending":
|
||||
# Files with no logs
|
||||
subq = db.query(ProcessingLog.file_id).distinct()
|
||||
query = query.filter(~FileRecord.id.in_(subq))
|
||||
elif status == "processing":
|
||||
# Files with in_progress logs
|
||||
subq = db.query(ProcessingLog.file_id).filter(ProcessingLog.status == "in_progress").distinct()
|
||||
query = query.filter(FileRecord.id.in_(subq))
|
||||
elif status == "failed":
|
||||
# Files with failure logs
|
||||
subq = db.query(ProcessingLog.file_id).filter(ProcessingLog.status == "failure").distinct()
|
||||
query = query.filter(FileRecord.id.in_(subq))
|
||||
elif status == "completed":
|
||||
# Files with success logs but no failures or in_progress
|
||||
success_files = (
|
||||
db.query(ProcessingLog.file_id).filter(ProcessingLog.status == "success").distinct().subquery()
|
||||
)
|
||||
|
||||
failed_files = (
|
||||
db.query(ProcessingLog.file_id)
|
||||
.filter(or_(ProcessingLog.status == "failure", ProcessingLog.status == "in_progress"))
|
||||
.distinct()
|
||||
.subquery()
|
||||
)
|
||||
|
||||
query = query.filter(FileRecord.id.in_(db.query(success_files.c.file_id))).filter(
|
||||
~FileRecord.id.in_(db.query(failed_files.c.file_id))
|
||||
)
|
||||
query = apply_status_filter(query, db, status)
|
||||
|
||||
# Get total count before pagination (after all filters)
|
||||
total_items = query.count()
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
Shared file query utilities for filtering files by processing status.
|
||||
|
||||
This module contains reusable query logic for filtering FileRecord objects
|
||||
based on their processing status (pending, processing, failed, completed).
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy.orm import Query, Session
|
||||
|
||||
from app.models import FileRecord, ProcessingLog
|
||||
|
||||
|
||||
def apply_status_filter(query: Query, db: Session, status: Optional[str]) -> Query:
|
||||
"""
|
||||
Apply status filter to a FileRecord query.
|
||||
|
||||
This function modifies a SQLAlchemy query to filter files based on their
|
||||
processing status by examining associated ProcessingLog entries.
|
||||
|
||||
Args:
|
||||
query: The base SQLAlchemy query for FileRecord objects
|
||||
db: Database session for creating subqueries
|
||||
status: Status filter to apply. Valid values:
|
||||
- "pending": Files with no ProcessingLog entries
|
||||
- "processing": Files with in_progress logs
|
||||
- "failed": Files with failure logs
|
||||
- "completed": Files with success logs but no failures or in_progress
|
||||
- None: No filter applied (returns query unchanged)
|
||||
|
||||
Returns:
|
||||
Modified query with status filter applied
|
||||
|
||||
Examples:
|
||||
>>> query = db.query(FileRecord)
|
||||
>>> query = apply_status_filter(query, db, "completed")
|
||||
>>> files = query.all()
|
||||
"""
|
||||
if not status:
|
||||
return query
|
||||
|
||||
# Subquery to get file IDs matching the status
|
||||
if status == "pending":
|
||||
# Files with no logs
|
||||
subq = db.query(ProcessingLog.file_id).distinct()
|
||||
query = query.filter(~FileRecord.id.in_(subq))
|
||||
elif status == "processing":
|
||||
# Files with in_progress logs
|
||||
subq = db.query(ProcessingLog.file_id).filter(ProcessingLog.status == "in_progress").distinct()
|
||||
query = query.filter(FileRecord.id.in_(subq))
|
||||
elif status == "failed":
|
||||
# Files with failure logs
|
||||
subq = db.query(ProcessingLog.file_id).filter(ProcessingLog.status == "failure").distinct()
|
||||
query = query.filter(FileRecord.id.in_(subq))
|
||||
elif status == "completed":
|
||||
# Files with success logs but no failures or in_progress
|
||||
success_files = db.query(ProcessingLog.file_id).filter(ProcessingLog.status == "success").distinct().subquery()
|
||||
|
||||
failed_files = (
|
||||
db.query(ProcessingLog.file_id)
|
||||
.filter(or_(ProcessingLog.status == "failure", ProcessingLog.status == "in_progress"))
|
||||
.distinct()
|
||||
.subquery()
|
||||
)
|
||||
|
||||
query = query.filter(FileRecord.id.in_(db.query(success_files.c.file_id))).filter(
|
||||
~FileRecord.id.in_(db.query(failed_files.c.file_id))
|
||||
)
|
||||
|
||||
return query
|
||||
+2
-30
@@ -8,6 +8,7 @@ from fastapi import Depends, Query, Request
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.config import settings
|
||||
from app.utils.file_queries import apply_status_filter
|
||||
from app.utils.file_status import get_files_processing_status
|
||||
from app.views.base import APIRouter, get_db, logger, require_login, templates
|
||||
|
||||
@@ -48,36 +49,7 @@ def files_page(
|
||||
query = query.filter(FileRecord.mime_type == mime_type)
|
||||
|
||||
# Apply status filter (before pagination for correct counts)
|
||||
if status:
|
||||
# Subquery to get file IDs matching the status
|
||||
if status == "pending":
|
||||
# Files with no logs
|
||||
subq = db.query(ProcessingLog.file_id).distinct()
|
||||
query = query.filter(~FileRecord.id.in_(subq))
|
||||
elif status == "processing":
|
||||
# Files with in_progress logs
|
||||
subq = db.query(ProcessingLog.file_id).filter(ProcessingLog.status == "in_progress").distinct()
|
||||
query = query.filter(FileRecord.id.in_(subq))
|
||||
elif status == "failed":
|
||||
# Files with failure logs
|
||||
subq = db.query(ProcessingLog.file_id).filter(ProcessingLog.status == "failure").distinct()
|
||||
query = query.filter(FileRecord.id.in_(subq))
|
||||
elif status == "completed":
|
||||
# Files with success logs but no failures or in_progress
|
||||
success_files = (
|
||||
db.query(ProcessingLog.file_id).filter(ProcessingLog.status == "success").distinct().subquery()
|
||||
)
|
||||
|
||||
failed_files = (
|
||||
db.query(ProcessingLog.file_id)
|
||||
.filter(or_(ProcessingLog.status == "failure", ProcessingLog.status == "in_progress"))
|
||||
.distinct()
|
||||
.subquery()
|
||||
)
|
||||
|
||||
query = query.filter(FileRecord.id.in_(db.query(success_files.c.file_id))).filter(
|
||||
~FileRecord.id.in_(db.query(failed_files.c.file_id))
|
||||
)
|
||||
query = apply_status_filter(query, db, status)
|
||||
|
||||
# Get total count before pagination
|
||||
total_items = query.count()
|
||||
|
||||
Reference in New Issue
Block a user