Backend performance and code quality improvements

Performance (hot path):
- Fix double brightness: removed duplicate scaling from 9 device clients
  (wled, adalight, ambiled, openrgb, hue, spi, chroma, gamesense, usbhid,
  espnow) — processor loop is now the single source of brightness
- Bounded send_timestamps deque with maxlen, removed 3 cleanup loops
- Running FPS sum O(1) instead of sum()/len() O(n) per frame
- datetime.now(timezone.utc) → time.monotonic() with lazy conversion
- Device info refresh interval 30 → 300 iterations
- Composite: gate layer_snapshots copy on preview client flag
- Composite: versioned sub_streams snapshot (copy only on change)
- Composite: pre-resolved blend methods (dict lookup vs getattr)
- ApiInput: np.copyto in-place instead of astype allocation

Code quality:
- BaseJsonStore: RLock on get/delete/get_all/count (was created but unused)
- EntityNotFoundError → proper 404 responses across 15 route files
- Remove 21 defensive getattr(x,'tags',[]) — field guaranteed on all models
- Fix Dict[str,any] → Dict[str,Any] in template/audio_template stores
- Log 4 silenced exceptions (automation engine, metrics, system)
- ValueStream.get_value() now @abstractmethod
- Config.from_yaml: add encoding="utf-8"
- OutputTargetStore: remove 25-line _load override, use _legacy_json_keys
- BaseJsonStore: add _legacy_json_keys for migration support
- Remove unnecessary except Exception→500 from postprocessing list endpoint

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-18 15:06:29 +03:00
parent 1f047d6561
commit cdba98813b
37 changed files with 296 additions and 137 deletions

View File

@@ -41,6 +41,7 @@ from wled_controller.storage.postprocessing_template_store import Postprocessing
from wled_controller.storage.picture_source_store import PictureSourceStore
from wled_controller.storage.picture_source import ScreenCapturePictureSource, StaticImagePictureSource, VideoCaptureSource
from wled_controller.utils import get_logger
from wled_controller.storage.base_store import EntityNotFoundError
logger = get_logger(__name__)
@@ -62,7 +63,7 @@ def _stream_to_response(s) -> PictureSourceResponse:
created_at=s.created_at,
updated_at=s.updated_at,
description=s.description,
tags=getattr(s, 'tags', []),
tags=s.tags,
# Video fields
url=getattr(s, "url", None),
loop=getattr(s, "loop", None),
@@ -228,6 +229,9 @@ async def create_picture_source(
return _stream_to_response(stream)
except HTTPException:
raise
except EntityNotFoundError as e:
raise HTTPException(status_code=404, detail=str(e))
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
@@ -280,6 +284,9 @@ async def update_picture_source(
)
fire_entity_event("picture_source", "updated", stream_id)
return _stream_to_response(stream)
except EntityNotFoundError as e:
raise HTTPException(status_code=404, detail=str(e))
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
@@ -309,6 +316,9 @@ async def delete_picture_source(
fire_entity_event("picture_source", "deleted", stream_id)
except HTTPException:
raise
except EntityNotFoundError as e:
raise HTTPException(status_code=404, detail=str(e))
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
@@ -383,6 +393,9 @@ async def test_picture_source(
# Resolve stream chain
try:
chain = store.resolve_stream_chain(stream_id)
except EntityNotFoundError as e:
raise HTTPException(status_code=404, detail=str(e))
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@@ -541,6 +554,9 @@ async def test_picture_source(
except HTTPException:
raise
except EntityNotFoundError as e:
raise HTTPException(status_code=404, detail=str(e))
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except RuntimeError as e: