Files
Artificial-Sweetener-Simple…/simple_syrup/runtime/settings_routes.py
T

137 lines
4.6 KiB
Python

# SimpleSyrup - workflow-focused ComfyUI extensions for image generation
# Copyright (C) 2026 Artificial Sweetener and contributors
# SPDX-License-Identifier: AGPL-3.0-or-later
"""HTTP route registration for SimpleSyrup backend settings."""
from __future__ import annotations
import sys
from collections.abc import Callable, Coroutine
from typing import Any, Protocol, cast
from aiohttp import web
from ..shared.logging import get_logger
from .settings import (
SimpleSyrupSettings,
SimpleSyrupSettingsError,
)
from .settings_repository import SimpleSyrupSettingsRepository
LOGGER = get_logger(__name__)
SETTINGS_ROUTE = "/simple-syrup/settings"
Handler = Callable[[Any], Coroutine[Any, Any, web.Response]]
_REGISTERED_PROMPT_SERVERS: set[int] = set()
class RoutesProtocol(Protocol):
"""Subset of Comfy's route table needed for settings registration."""
def get(self, path: str) -> Callable[[Handler], Handler]:
"""Return a GET route decorator."""
def post(self, path: str) -> Callable[[Handler], Handler]:
"""Return a POST route decorator."""
class PromptServerProtocol(Protocol):
"""Subset of Comfy's PromptServer needed for route registration."""
routes: RoutesProtocol
class SettingsHandlers:
"""Handle SimpleSyrup settings HTTP requests."""
def __init__(self, repository: SimpleSyrupSettingsRepository) -> None:
"""Create handlers backed by the settings repository."""
self._repository = repository
async def get_settings(self, _request: Any) -> web.Response:
"""Return current SimpleSyrup settings."""
return web.json_response(self._repository.load().to_payload())
async def post_settings(self, request: Any) -> web.Response:
"""Validate and persist SimpleSyrup settings."""
try:
payload = await request.json()
settings = self._settings_from_request_payload(payload)
except SimpleSyrupSettingsError as error:
return web.json_response({"error": str(error)}, status=400)
except Exception as error:
LOGGER.warning(
"invalid settings request body",
extra={"route": SETTINGS_ROUTE, "reason": str(error)},
)
return web.json_response(
{"error": "SimpleSyrup settings request body must be valid JSON."},
status=400,
)
saved = self._repository.save(settings)
return web.json_response(saved.to_payload())
def _settings_from_request_payload(self, payload: object) -> SimpleSyrupSettings:
"""Return validated settings while preserving omitted nested config."""
settings = SimpleSyrupSettings.from_payload(payload)
if not isinstance(payload, dict):
return settings
current = self._repository.load()
return SimpleSyrupSettings(
show_downloadable_models=settings.show_downloadable_models,
quant_cache_limit_gib=(
settings.quant_cache_limit_gib
if "quant_cache_limit_gib" in payload
else current.quant_cache_limit_gib
),
external_llm=(
settings.external_llm
if "external_llm" in payload
else current.external_llm
),
)
def register_settings_routes(
repository: SimpleSyrupSettingsRepository | None = None,
prompt_server: PromptServerProtocol | None = None,
) -> bool:
"""Register SimpleSyrup settings routes with Comfy's PromptServer."""
server_instance = prompt_server or _prompt_server_instance()
if server_instance is None:
return False
server_key = id(server_instance)
if prompt_server is None and server_key in _REGISTERED_PROMPT_SERVERS:
return True
handlers = SettingsHandlers(repository or SimpleSyrupSettingsRepository())
server_instance.routes.get(SETTINGS_ROUTE)(handlers.get_settings)
server_instance.routes.post(SETTINGS_ROUTE)(handlers.post_settings)
if prompt_server is None:
_REGISTERED_PROMPT_SERVERS.add(server_key)
return True
def _prompt_server_instance() -> PromptServerProtocol | None:
"""Return Comfy's PromptServer instance when available."""
try:
server_module = sys.modules["server"]
prompt_server = server_module.PromptServer
instance = prompt_server.instance
except (KeyError, AttributeError) as error:
LOGGER.debug(
"PromptServer unavailable for settings routes",
extra={"reason": str(error)},
)
return None
return cast(PromptServerProtocol, instance)