增加web_api
This commit is contained in:
228
src/gasflux/blueprints/task_pool.py
Normal file
228
src/gasflux/blueprints/task_pool.py
Normal file
@ -0,0 +1,228 @@
|
||||
"""
|
||||
Task Pool Blueprint
|
||||
Handles task pool management endpoints: listing tasks with pagination, pool statistics.
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request
|
||||
from pathlib import Path
|
||||
|
||||
from ..shared import (
|
||||
get_task_list,
|
||||
get_task_pool_stats,
|
||||
_format_response,
|
||||
log_performance,
|
||||
logger,
|
||||
task_status,
|
||||
TASK_STATUS_PENDING,
|
||||
TASK_STATUS_PROCESSING,
|
||||
TASK_STATUS_COMPLETED,
|
||||
TASK_STATUS_FAILED
|
||||
)
|
||||
|
||||
# Create blueprint
|
||||
task_pool_bp = Blueprint('task_pool', __name__, url_prefix='/tasks')
|
||||
|
||||
|
||||
def _build_simple_downloads_from_results(results: list[dict]) -> dict:
|
||||
"""
|
||||
Build direct download shortcuts for common files, based on task results.
|
||||
This is intentionally minimal and frontend-friendly.
|
||||
"""
|
||||
downloads: dict = {}
|
||||
|
||||
def set_once(key: str, url: str):
|
||||
if key not in downloads and url:
|
||||
downloads[key] = url
|
||||
|
||||
for item in results or []:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
rel_path = item.get('rel_path')
|
||||
if not rel_path:
|
||||
continue
|
||||
|
||||
name_l = (item.get('name') or '').lower()
|
||||
url = f"/download/{rel_path}"
|
||||
|
||||
if name_l.endswith('.xlsx'):
|
||||
set_once('data_xlsx', url)
|
||||
elif name_l.endswith('.xls'):
|
||||
set_once('data_xls', url)
|
||||
elif name_l.endswith('ch4_report.html'):
|
||||
set_once('report_ch4', url)
|
||||
elif name_l.endswith('co2_report.html'):
|
||||
set_once('report_co2', url)
|
||||
elif name_l.endswith(('.yaml', '.yml')):
|
||||
set_once('config', url)
|
||||
elif name_l.endswith('.json') and 'output_vars' in name_l:
|
||||
set_once('metadata', url)
|
||||
elif name_l.endswith('.html'):
|
||||
# fallback: any html report
|
||||
set_once('report_html', url)
|
||||
|
||||
return downloads
|
||||
|
||||
|
||||
def _lean_task_summary(task_summary: dict) -> dict:
|
||||
"""Return a minimal task representation for frontend consumption."""
|
||||
task_id = task_summary.get('task_id')
|
||||
status = task_summary.get('status')
|
||||
|
||||
lean = {
|
||||
'task_id': task_id,
|
||||
'status': status,
|
||||
'message': task_summary.get('message'),
|
||||
'updated_at': task_summary.get('updated_at'),
|
||||
}
|
||||
|
||||
if status == TASK_STATUS_COMPLETED and task_id:
|
||||
full_task_info = task_status.get(task_id, {})
|
||||
results = full_task_info.get('results', []) or []
|
||||
downloads = _build_simple_downloads_from_results(results)
|
||||
if downloads:
|
||||
lean['downloads'] = downloads
|
||||
|
||||
return lean
|
||||
|
||||
|
||||
@task_pool_bp.route('', methods=['GET'])
|
||||
@log_performance
|
||||
def list_tasks():
|
||||
"""Get paginated list of tasks with optional filtering."""
|
||||
logger.debug(f"Task list request from IP {request.remote_addr}")
|
||||
|
||||
try:
|
||||
# Parse query parameters
|
||||
status_filter = request.args.get('status')
|
||||
if status_filter:
|
||||
# Support comma-separated status values
|
||||
status_filter = status_filter.split(',')
|
||||
|
||||
page = int(request.args.get('page', 1))
|
||||
page_size = int(request.args.get('page_size', 20))
|
||||
sort_by = request.args.get('sort_by', 'updated_at')
|
||||
sort_order = request.args.get('sort_order', 'desc')
|
||||
|
||||
# Validate parameters
|
||||
if page < 1:
|
||||
return _format_response(400, "页码必须大于0")
|
||||
|
||||
if page_size < 1 or page_size > 100:
|
||||
return _format_response(400, "每页数量必须在1-100之间")
|
||||
|
||||
valid_sort_fields = ['created_at', 'updated_at', 'status']
|
||||
if sort_by not in valid_sort_fields:
|
||||
return _format_response(400, f"排序字段必须是以下之一: {', '.join(valid_sort_fields)}")
|
||||
|
||||
if sort_order.lower() not in ['asc', 'desc']:
|
||||
return _format_response(400, "排序顺序必须是 'asc' 或 'desc'")
|
||||
|
||||
# Get task list
|
||||
result = get_task_list(
|
||||
status_filter=status_filter,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
sort_by=sort_by,
|
||||
sort_order=sort_order,
|
||||
cleanup=False
|
||||
)
|
||||
|
||||
# Slim response: only task status + downloads (completed only)
|
||||
result['tasks'] = [_lean_task_summary(t) for t in result.get('tasks', [])]
|
||||
|
||||
logger.debug(f"Returning {len(result['tasks'])} tasks (page {page} of {result['total_pages']})")
|
||||
|
||||
return _format_response(200, "任务列表查询成功", result)
|
||||
|
||||
except ValueError as e:
|
||||
logger.warning(f"Invalid parameter in task list request: {str(e)}")
|
||||
return _format_response(400, "参数格式错误")
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing tasks: {str(e)}", exc_info=True)
|
||||
return _format_response(500, "内部服务器错误")
|
||||
|
||||
|
||||
@task_pool_bp.route('/stats', methods=['GET'])
|
||||
@log_performance
|
||||
def get_pool_stats():
|
||||
"""Get task pool statistics."""
|
||||
logger.debug(f"Task pool stats request from IP {request.remote_addr}")
|
||||
|
||||
try:
|
||||
stats = get_task_pool_stats()
|
||||
|
||||
logger.debug(f"Pool stats: {stats['total_tasks']} total tasks, "
|
||||
f"{stats['active_tasks']} active, {stats['queued_tasks']} queued")
|
||||
|
||||
return _format_response(200, "任务池统计信息查询成功", stats)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting pool stats: {str(e)}", exc_info=True)
|
||||
return _format_response(500, "内部服务器错误")
|
||||
|
||||
|
||||
@task_pool_bp.route('/active', methods=['GET'])
|
||||
@log_performance
|
||||
def get_active_tasks():
|
||||
"""Get list of currently active (processing) tasks."""
|
||||
logger.debug(f"Active tasks request from IP {request.remote_addr}")
|
||||
|
||||
try:
|
||||
# Get all processing tasks, no pagination needed for active tasks
|
||||
result = get_task_list(
|
||||
status_filter=TASK_STATUS_PROCESSING,
|
||||
page=1,
|
||||
page_size=1000, # Large page size to get all active tasks
|
||||
sort_by='updated_at',
|
||||
sort_order='asc', # Oldest first
|
||||
cleanup=False
|
||||
)
|
||||
|
||||
active_tasks = result['tasks']
|
||||
|
||||
active_tasks = [_lean_task_summary(t) for t in active_tasks]
|
||||
|
||||
logger.debug(f"Returning {len(active_tasks)} active tasks")
|
||||
|
||||
return _format_response(200, "活跃任务查询成功", {
|
||||
'active_tasks': active_tasks,
|
||||
'count': len(active_tasks)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting active tasks: {str(e)}", exc_info=True)
|
||||
return _format_response(500, "内部服务器错误")
|
||||
|
||||
|
||||
@task_pool_bp.route('/queue', methods=['GET'])
|
||||
@log_performance
|
||||
def get_queued_tasks():
|
||||
"""Get list of queued (pending) tasks."""
|
||||
logger.debug(f"Queued tasks request from IP {request.remote_addr}")
|
||||
|
||||
try:
|
||||
# Get all pending tasks, sorted by creation time
|
||||
result = get_task_list(
|
||||
status_filter=TASK_STATUS_PENDING,
|
||||
page=1,
|
||||
page_size=1000, # Large page size to get all queued tasks
|
||||
sort_by='created_at',
|
||||
sort_order='asc', # Oldest first (FIFO)
|
||||
cleanup=False
|
||||
)
|
||||
|
||||
queued_tasks = result['tasks']
|
||||
|
||||
queued_tasks = [_lean_task_summary(t) for t in queued_tasks]
|
||||
|
||||
logger.debug(f"Returning {len(queued_tasks)} queued tasks")
|
||||
|
||||
return _format_response(200, "队列任务查询成功", {
|
||||
'queued_tasks': queued_tasks,
|
||||
'count': len(queued_tasks),
|
||||
'queue_position_info': "任务按创建时间排序,较早的任务优先处理"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting queued tasks: {str(e)}", exc_info=True)
|
||||
return _format_response(500, "内部服务器错误")
|
||||
Reference in New Issue
Block a user