Fix batch QC: add Flask app context to ThreadPoolExecutor child threads
ThreadPoolExecutor workers don't inherit the parent thread's Flask app context, causing "Working outside of application context" errors during batch QC execution. Pass the app instance into BatchQCExecutor and wrap each child thread's work with app.app_context(). Also ensure the progress_sessions table is created on fresh databases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d0d7110836
commit
8a7d477c86
4 changed files with 35 additions and 27 deletions
|
|
@ -22,6 +22,7 @@ def init_db(app):
|
|||
from . import qc_report
|
||||
from . import usage_log
|
||||
from . import campaign_presentation
|
||||
from core.utils.progress_tracker import ProgressSession # noqa: F401
|
||||
|
||||
# Create all tables (if they don't exist)
|
||||
# Tables are created automatically on first access
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import json
|
|||
import time
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, Any
|
||||
from flask import session
|
||||
from core.models.database import db
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ class BatchQCExecutor:
|
|||
job_number: str = None,
|
||||
batch_size: int = DEFAULT_BATCH_SIZE,
|
||||
campaign_id: str = None,
|
||||
batch_id: str = None
|
||||
batch_id: str = None,
|
||||
app=None
|
||||
):
|
||||
"""
|
||||
Initialize batch executor.
|
||||
|
|
@ -50,6 +51,7 @@ class BatchQCExecutor:
|
|||
batch_size: Number of files per batch (default 10)
|
||||
campaign_id: Optional campaign ID to load presentation guidelines
|
||||
batch_id: Optional batch ID for grouping reports from the same upload
|
||||
app: Flask app instance (required for ThreadPoolExecutor child threads)
|
||||
"""
|
||||
self.session_id = session_id
|
||||
self.file_paths = file_paths[:MAX_FILES]
|
||||
|
|
@ -58,6 +60,7 @@ class BatchQCExecutor:
|
|||
self.campaign_id = campaign_id
|
||||
self.batch_id = batch_id
|
||||
self.batch_size = batch_size
|
||||
self.app = app
|
||||
self.progress = UnifiedProgressTracker(session_id)
|
||||
self.results = []
|
||||
|
||||
|
|
@ -165,6 +168,9 @@ class BatchQCExecutor:
|
|||
"""
|
||||
Process a single file using QCExecutor.
|
||||
|
||||
Runs inside a ThreadPoolExecutor child thread, so we must push
|
||||
an app context explicitly (child threads don't inherit it).
|
||||
|
||||
Errors are captured and returned, not raised.
|
||||
|
||||
Args:
|
||||
|
|
@ -177,31 +183,32 @@ class BatchQCExecutor:
|
|||
"""
|
||||
filename = os.path.basename(file_path)
|
||||
try:
|
||||
# Create a per-file executor with a sub-session ID
|
||||
# We don't use its progress tracker—we track at the batch level
|
||||
file_session_id = f"{self.session_id}_file_{completed}"
|
||||
with self.app.app_context():
|
||||
# Create a per-file executor with a sub-session ID
|
||||
# We don't use its progress tracker—we track at the batch level
|
||||
file_session_id = f"{self.session_id}_file_{completed}"
|
||||
|
||||
executor = QCExecutor(
|
||||
session_id=file_session_id,
|
||||
file_path=file_path,
|
||||
profile=self.profile,
|
||||
job_number=self.job_number,
|
||||
campaign_id=self.campaign_id,
|
||||
batch_id=self.batch_id
|
||||
)
|
||||
executor = QCExecutor(
|
||||
session_id=file_session_id,
|
||||
file_path=file_path,
|
||||
profile=self.profile,
|
||||
job_number=self.job_number,
|
||||
campaign_id=self.campaign_id,
|
||||
batch_id=self.batch_id
|
||||
)
|
||||
|
||||
result = executor.execute()
|
||||
result = executor.execute()
|
||||
|
||||
return {
|
||||
'filename': filename,
|
||||
'file_path': file_path,
|
||||
'success': result.get('success', False),
|
||||
'score': result.get('overall_score'),
|
||||
'status': result.get('overall_status', 'error'),
|
||||
'report_path': result.get('report_path'),
|
||||
'report_id': result.get('report_id'),
|
||||
'error': result.get('error')
|
||||
}
|
||||
return {
|
||||
'filename': filename,
|
||||
'file_path': file_path,
|
||||
'success': result.get('success', False),
|
||||
'score': result.get('overall_score'),
|
||||
'status': result.get('overall_status', 'error'),
|
||||
'report_path': result.get('report_path'),
|
||||
'report_id': result.get('report_id'),
|
||||
'error': result.get('error')
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing file {filename}: {e}")
|
||||
|
|
|
|||
|
|
@ -317,17 +317,18 @@ def execute_batch():
|
|||
campaign_id = data.get('campaign_id')
|
||||
batch_id = str(uuid.uuid4())
|
||||
|
||||
app = current_app._get_current_object()
|
||||
|
||||
batch_executor = BatchQCExecutor(
|
||||
session_id=session_id,
|
||||
file_paths=file_paths,
|
||||
profile=profile,
|
||||
job_number=job_number,
|
||||
campaign_id=campaign_id,
|
||||
batch_id=batch_id
|
||||
batch_id=batch_id,
|
||||
app=app
|
||||
)
|
||||
|
||||
app = current_app._get_current_object()
|
||||
|
||||
def run_batch():
|
||||
with app.app_context():
|
||||
result = batch_executor.execute()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue