Files
FugitiveExpert01 303f2529e3 Update routes.py
.fe-info.json sidecar gone — routes.py no longer writes cache files into the loras folder. The cache path is now constructed via _info_cache_path(file_hash) which resolves to {tempfile.gettempdir()}/fenodes_lora_cache/{sha256}.fe-info.json. Keyed on SHA256 rather than filename so it stays valid if a file is renamed or moved.
2026-03-17 00:20:23 +01:00

133 lines
5.2 KiB
Python

"""
FEnodes -- API route registrations
Author: FugitiveExpert01
Each route is registered in its own try/except so a failure in one
does not affect the other, and the error message is specific.
"""
import json
import logging
import os
import folder_paths
from .lora_utils import (
read_safetensors_metadata,
fetch_civitai_info,
normalize_lora_name,
_info_cache_path,
)
logger = logging.getLogger("FEnodes")
def register_routes():
"""
Register all FEnodes API routes with ComfyUI's PromptServer.
Called once from __init__.py at startup.
"""
try:
from server import PromptServer
from aiohttp import web
except ImportError as e:
logger.error(
f"[FEnodes/routes] Could not import server or aiohttp -- "
f"API routes will be unavailable. ({e})"
)
return
# -- GET /fenodes/loras ------------------------------------------------
# Returns the flat list of lora filenames for the JS browser widget.
try:
@PromptServer.instance.routes.get("/fenodes/loras")
async def _get_loras(request):
loras = folder_paths.get_filename_list("loras")
return web.json_response({"loras": loras})
logger.info("[FEnodes/routes] Registered GET /fenodes/loras")
except Exception as e:
logger.error(f"[FEnodes/routes] Failed to register /fenodes/loras: {e}")
# -- GET /fenodes/lora_info --------------------------------------------
# Returns CivitAI info + safetensors metadata for a single LoRA.
# Query params:
# name (required) -- LoRA filename as returned by /fenodes/loras
# refresh (optional) -- "true" to bypass cache and re-fetch
try:
@PromptServer.instance.routes.get("/fenodes/lora_info")
async def _get_lora_info(request):
lora_name = request.rel_url.query.get("name", "")
refresh = request.rel_url.query.get("refresh", "false").lower() == "true"
if not lora_name:
logger.warning("[FEnodes/routes] /fenodes/lora_info called with no name")
return web.json_response({"error": "missing 'name' query parameter"}, status=400)
lora_name = normalize_lora_name(lora_name)
lora_path = folder_paths.get_full_path("loras", lora_name)
if not lora_path:
logger.warning(f"[FEnodes/routes] /fenodes/lora_info: not found in loras folder: {lora_name}")
return web.json_response({"error": f"LoRA not found: {lora_name}"}, status=404)
if not os.path.isfile(lora_path):
logger.warning(f"[FEnodes/routes] /fenodes/lora_info: path exists in index but file missing on disk: {lora_path}")
return web.json_response({"error": f"File missing on disk: {lora_path}"}, status=404)
# Cache lives in the system temp directory, not alongside the model file.
# Keyed on SHA256 so it remains valid if the file is renamed or moved.
from .lora_utils import _sha256_full
file_hash = _sha256_full(lora_path)
cache_path = _info_cache_path(file_hash)
# Serve from cache unless refresh requested
if os.path.isfile(cache_path) and not refresh:
try:
with open(cache_path, "r", encoding="utf-8") as f:
cached = json.load(f)
logger.info(f"[FEnodes/routes] Cache hit for: {lora_name}")
return web.json_response(cached)
except (json.JSONDecodeError, OSError) as e:
logger.warning(
f"[FEnodes/routes] Cache file corrupt or unreadable for {lora_name}, "
f"rebuilding. ({e})"
)
logger.info(f"[FEnodes/routes] Building info for: {lora_name}")
info = {"file": lora_name}
# Safetensors header metadata
if lora_path.endswith(".safetensors"):
meta = read_safetensors_metadata(lora_path)
if meta:
info["metadata"] = meta
for field in (
"ss_sd_model_name", "ss_base_model_version",
"ss_network_module", "ss_num_train_images",
"modelspec.title", "modelspec.architecture",
):
if field in meta:
info[field] = meta[field]
# CivitAI lookup
civitai = fetch_civitai_info(lora_path)
info["civitai"] = civitai
if "sha256" in civitai:
info["sha256"] = civitai["sha256"]
# Write cache
try:
with open(cache_path, "w", encoding="utf-8") as f:
json.dump(info, f, indent=2)
except OSError as e:
logger.warning(
f"[FEnodes/routes] Could not write cache for {lora_name}: {e}"
)
return web.json_response(info)
logger.info("[FEnodes/routes] Registered GET /fenodes/lora_info")
except Exception as e:
logger.error(f"[FEnodes/routes] Failed to register /fenodes/lora_info: {e}")