Chantier F - Typage Python - ruff S101 déplacé en per-file-ignores (pyproject.toml) - pyrightconfig.json : mode standard confirmé Chantier G - Assertions & bare-except - 16 blocs except documentés dans config.py - --cov-branch activé, seuil coverage 60→68% Chantier H - Cache settings - Stratégie documentée dans settings.py - _resolve_index_dir() logguée dans config.py Chantier I - Split gros fichiers JS - client.js 1509L → client.js 406L + clientAuth.js + clientOps.js + fetchUtils.js - toolbar.js 927L → toolbar.js 458L + toolbarActions.js + toolbarControls.js - FloatingViewer.js → FloatingViewer.impl.js + floatingViewer*(8 modules) - Viewer_impl.js → ViewerRuntime.js + viewerShell.js + viewerThemeStyles.js + viewerOverlayDismiss.js - model3dRenderer.js → model3dRenderer.impl.js + model3dCore.js + model3dLoaders.js + model3dSupport.js - dialogs.js → dialogs.js + DialogTemplates.js Chantier J - Tests frontend (Phases 1-2) - use_drag_drop.vitest.mjs : 5 tests - use_grid_state.vitest.mjs : 18 tests - security_client_headers, security_deep_merge, security_runtime_state, runtime_store, dialog_templates - Total: 80 fichiers test / 350 tests vitest Chantier K - Pinia migration - runtimeState.js entièrement migré vers Pinia - getOptionalRuntimeStore.js + runtimeEnrichmentState.js ajoutés Chantier L - Durcissement sécurité - tests/security/test_rate_limit.py : 8 tests rate-limit (state machine complète) - clientAuth.js : docstring sessionStorage security boundary - three.js 0.183.2 audité : 0 CVE (Snyk) Chantier M - Simplification config - deps.py, tool_detect.py, utils.py nettoyés docs: PLAN_REFACTO_V2_LONG_TERME.md ajouté et clôturé .gitignore: coverage/ ajouté
81 lines
2.3 KiB
Python
81 lines
2.3 KiB
Python
"""
|
|
Utility helpers shared across backend modules.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
import os
|
|
from typing import Any
|
|
|
|
from .shared import get_logger
|
|
|
|
BOOL_TRUE_VALUES = frozenset({"1", "true", "yes", "on", "enabled"})
|
|
BOOL_FALSE_VALUES = frozenset({"0", "false", "no", "off", "disabled"})
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
def parse_bool(value: Any, default: bool = False) -> bool:
|
|
if isinstance(value, bool):
|
|
return value
|
|
if isinstance(value, (int, float)):
|
|
return bool(value)
|
|
if isinstance(value, str):
|
|
normalized = value.strip().lower()
|
|
if normalized in BOOL_TRUE_VALUES:
|
|
return True
|
|
if normalized in BOOL_FALSE_VALUES:
|
|
return False
|
|
try:
|
|
return bool(float(normalized))
|
|
except (ValueError, TypeError, OverflowError):
|
|
logger.debug("parse_bool could not coerce %r via float fallback", value)
|
|
pass
|
|
return default
|
|
|
|
|
|
def env_bool(name: str, default: bool) -> bool:
|
|
if not name:
|
|
return default
|
|
try:
|
|
raw = os.environ.get(name)
|
|
except Exception:
|
|
raw = None
|
|
if raw is None:
|
|
return default
|
|
return parse_bool(raw, default)
|
|
|
|
|
|
def env_float(name: str, default: float) -> float:
|
|
if not name:
|
|
return default
|
|
try:
|
|
raw = os.environ.get(name)
|
|
except Exception:
|
|
raw = None
|
|
if raw is None:
|
|
return default
|
|
try:
|
|
return float(raw)
|
|
except (ValueError, TypeError):
|
|
return default
|
|
|
|
|
|
def sanitize_for_json(obj: Any) -> Any:
|
|
"""Recursively replace NaN/Infinity float values with ``None`` so the
|
|
result is valid JSON. ComfyUI nodes routinely store ``float('nan')`` in
|
|
``is_changed`` to signal *always re-execute*; Python's ``json.dumps``
|
|
emits that as the bare token ``NaN`` which browsers reject.
|
|
"""
|
|
if isinstance(obj, float):
|
|
return None if (math.isnan(obj) or math.isinf(obj)) else obj
|
|
if isinstance(obj, bytes):
|
|
try:
|
|
return obj.decode("utf-8")
|
|
except (UnicodeDecodeError, ValueError):
|
|
return obj.hex()
|
|
if isinstance(obj, dict):
|
|
return {k: sanitize_for_json(v) for k, v in obj.items()}
|
|
if isinstance(obj, list):
|
|
return [sanitize_for_json(v) for v in obj]
|
|
return obj
|