187 lines
6.1 KiB
Python
187 lines
6.1 KiB
Python
"""Shared helpers used by the HTTP route modules."""
|
|
|
|
import asyncio
|
|
import threading
|
|
from functools import wraps
|
|
|
|
from ..request_utils import read_optional_object_payload, read_text_field
|
|
|
|
|
|
def register_service_route(
|
|
context,
|
|
*,
|
|
path,
|
|
methods=("post",),
|
|
error_prefix,
|
|
operation,
|
|
return_success_on_error=False,
|
|
):
|
|
"""Register JSON routes for a bound service operation."""
|
|
json_api_endpoint = context.get("json_api_endpoint")
|
|
routes = context.get("routes")
|
|
|
|
async def service_route(request):
|
|
return await operation(request)
|
|
|
|
service_route.__name__ = getattr(operation, "__name__", "service_route")
|
|
endpoint = (
|
|
json_api_endpoint(
|
|
error_prefix,
|
|
return_success_on_error=True,
|
|
)
|
|
if return_success_on_error
|
|
else json_api_endpoint(error_prefix)
|
|
)
|
|
decorated_route = endpoint(service_route)
|
|
method_names = (methods,) if isinstance(methods, str) else methods
|
|
for method in method_names:
|
|
getattr(routes, method.lower())(path)(decorated_route)
|
|
|
|
|
|
def create_route_helpers(web, logger, load_settings, hash_calculation_cancelled):
|
|
"""Create route helpers bound to the current ComfyUI server context."""
|
|
|
|
def json_api_endpoint(error_prefix, return_success_on_error=False):
|
|
def decorator(func):
|
|
@wraps(func)
|
|
async def wrapper(request, *args, **kwargs):
|
|
try:
|
|
return await func(request, *args, **kwargs)
|
|
except Exception as exc:
|
|
logger.error(
|
|
f"Model Resolver {error_prefix} error: {exc}",
|
|
exc_info=True,
|
|
)
|
|
status = 500
|
|
error_message = str(exc)
|
|
if isinstance(exc, web.HTTPException):
|
|
status = exc.status
|
|
error_message = exc.reason or error_message
|
|
response_data = {"error": error_message}
|
|
if return_success_on_error:
|
|
response_data["success"] = False
|
|
return web.json_response(response_data, status=status)
|
|
|
|
return wrapper
|
|
|
|
return decorator
|
|
|
|
def _get_required_match_param(request, param_name):
|
|
value = request.match_info.get(param_name, "").strip()
|
|
return value or None
|
|
|
|
def get_progress_response(
|
|
tracker,
|
|
request,
|
|
param_name="progress_id",
|
|
not_found_payload=None,
|
|
not_found_status=200,
|
|
found_wrapper=None,
|
|
):
|
|
job_id = _get_required_match_param(request, param_name)
|
|
if not job_id:
|
|
return web.json_response(
|
|
{"error": f"{param_name} is required"},
|
|
status=400,
|
|
)
|
|
tracker.cleanup()
|
|
progress = tracker.get(job_id)
|
|
if not progress:
|
|
payload = not_found_payload or {"error": "progress not found"}
|
|
return web.json_response(payload, status=not_found_status)
|
|
if found_wrapper:
|
|
return web.json_response(found_wrapper(progress))
|
|
return web.json_response(progress)
|
|
|
|
def cancel_progress_response(
|
|
tracker,
|
|
request,
|
|
param_name="progress_id",
|
|
cancel_message="Cancelled",
|
|
):
|
|
job_id = _get_required_match_param(request, param_name)
|
|
if not job_id:
|
|
return web.json_response(
|
|
{"error": f"{param_name} is required"},
|
|
status=400,
|
|
)
|
|
cancelled = tracker.mark_cancelled(job_id, cancel_message)
|
|
return web.json_response(
|
|
{
|
|
"success": True,
|
|
"cancelled": cancelled,
|
|
"progress_id": job_id,
|
|
}
|
|
)
|
|
|
|
def run_in_background_thread(
|
|
tracker,
|
|
progress_id,
|
|
task_func,
|
|
on_success,
|
|
on_cancel=None,
|
|
on_error=None,
|
|
error_log_msg="Background task failed",
|
|
):
|
|
def wrapper():
|
|
try:
|
|
result = task_func()
|
|
if tracker.is_cancelled(progress_id):
|
|
if on_cancel:
|
|
on_cancel(result)
|
|
else:
|
|
tracker.update(
|
|
progress_id,
|
|
status="cancelled",
|
|
stage="cancelled",
|
|
message="Task cancelled",
|
|
)
|
|
return
|
|
on_success(result)
|
|
except (hash_calculation_cancelled, asyncio.CancelledError):
|
|
if on_cancel:
|
|
on_cancel()
|
|
else:
|
|
tracker.update(
|
|
progress_id,
|
|
status="cancelled",
|
|
stage="cancelled",
|
|
message="Task cancelled",
|
|
)
|
|
except Exception as exc:
|
|
logger.exception(f"{error_log_msg}: {exc}")
|
|
if on_error:
|
|
on_error(exc)
|
|
else:
|
|
tracker.update(
|
|
progress_id,
|
|
status="error",
|
|
stage="error",
|
|
message=str(exc) or "Task failed",
|
|
percent=100,
|
|
error=str(exc) or "Task failed",
|
|
)
|
|
|
|
threading.Thread(target=wrapper, daemon=True).start()
|
|
|
|
async def get_override_settings_from_request(request):
|
|
settings = await asyncio.to_thread(load_settings)
|
|
if request.method == "POST":
|
|
payload = await read_optional_object_payload(request)
|
|
if "aria2c_path" in payload:
|
|
settings = dict(settings)
|
|
settings["aria2c_path"] = read_text_field(
|
|
payload,
|
|
"aria2c_path",
|
|
contract_name="Aria2 settings request",
|
|
)
|
|
return settings
|
|
|
|
return (
|
|
json_api_endpoint,
|
|
get_progress_response,
|
|
cancel_progress_response,
|
|
run_in_background_thread,
|
|
get_override_settings_from_request,
|
|
)
|