refactor(devices): per-provider typed configs (phases 1-4)

Phase 1 — DeviceConfig hierarchy (device_config.py):
- 17 @dataclass(frozen=True) subclasses (WLEDConfig, AdalightConfig, …) sharing
  BaseDeviceConfig; DeviceConfig = Union[all 17]
- Device.to_config() in device_store.py: single flat→typed dispatch point

Phase 2+3 — Typed provider signatures + call-site migration:
- ProviderDeps(device_store) frozen dataclass in led_client.py
- LEDDeviceProvider.create_client(config, *, deps) abstract signature
- create_led_client(config, *, deps) factory dispatches via config.device_type
- All 17 providers narrowed to their specific config type; drop kwargs.get()
- GroupLEDClient.connect() uses device.to_config() + create_led_client()
- wled_target_processor: replaced 21-field DeviceInfo unpacking with to_config()
  + dataclasses.replace(config, use_ddp=…) for DDP override
- device_test_mode: build typed config via to_config() + ProviderDeps
- Deleted DeviceInfo dataclass, _get_device_info(), _DEVICE_FIELD_DEFAULTS
- TargetContext: replaced get_device_info callback with is_test_mode_active

Phase 4 — Test migration:
- 47-case test suite in tests/core/devices/test_device_config.py (100% coverage)
- test_group_device.py TestGroupLEDClient migrated to GroupConfig + ProviderDeps
- Removed legacy keyword-arg init path from GroupLEDClient
This commit is contained in:
2026-04-18 01:24:27 +03:00
parent 123da1b5c4
commit d3a6416a1d
29 changed files with 1192 additions and 328 deletions
+17 -38
View File
@@ -3,6 +3,8 @@
import numpy as np
import pytest
from ledgrab.core.devices.device_config import GroupConfig
from ledgrab.core.devices.led_client import ProviderDeps
from ledgrab.storage.database import Database
from ledgrab.storage.device_store import Device, DeviceStore
@@ -238,17 +240,22 @@ class TestGroupLEDClient:
d3 = _create_device(store, "d3", 30)
return store, [d1, d2, d3]
@pytest.mark.asyncio
async def test_connect_creates_children(self, mock_store):
def _make_client(self, store, devices, mode="sequence"):
from ledgrab.core.devices.group_client import GroupLEDClient
store, devices = mock_store
client = GroupLEDClient(
device_store=store,
config = GroupConfig(
device_id="test_group",
group_mode="sequence",
device_url="group://test_group",
led_count=sum(d.led_count for d in devices),
group_mode=mode,
group_device_ids=[d.id for d in devices],
)
return GroupLEDClient(config=config, deps=ProviderDeps(device_store=store))
@pytest.mark.asyncio
async def test_connect_creates_children(self, mock_store):
store, devices = mock_store
client = self._make_client(store, devices)
await client.connect()
assert client.is_connected
assert client.device_led_count == 60 # 10+20+30
@@ -257,15 +264,8 @@ class TestGroupLEDClient:
@pytest.mark.asyncio
async def test_sequence_mode_slices(self, mock_store):
from ledgrab.core.devices.group_client import GroupLEDClient
store, devices = mock_store
client = GroupLEDClient(
device_store=store,
device_id="test_group",
group_mode="sequence",
group_device_ids=[d.id for d in devices],
)
client = self._make_client(store, devices)
await client.connect()
# Capture what each child receives
@@ -292,15 +292,8 @@ class TestGroupLEDClient:
@pytest.mark.asyncio
async def test_independent_mode_resamples(self, mock_store):
from ledgrab.core.devices.group_client import GroupLEDClient
store, devices = mock_store
client = GroupLEDClient(
device_store=store,
device_id="test_group",
group_mode="independent",
group_device_ids=[d.id for d in devices],
)
client = self._make_client(store, devices, mode="independent")
await client.connect()
sent_pixels = []
@@ -328,15 +321,8 @@ class TestGroupLEDClient:
@pytest.mark.asyncio
async def test_close_cleans_up(self, mock_store):
from ledgrab.core.devices.group_client import GroupLEDClient
store, devices = mock_store
client = GroupLEDClient(
device_store=store,
device_id="test_group",
group_mode="sequence",
group_device_ids=[d.id for d in devices],
)
client = self._make_client(store, devices)
await client.connect()
assert client.is_connected
await client.close()
@@ -345,15 +331,8 @@ class TestGroupLEDClient:
@pytest.mark.asyncio
async def test_sequence_pads_short_pixels(self, mock_store):
from ledgrab.core.devices.group_client import GroupLEDClient
store, devices = mock_store
client = GroupLEDClient(
device_store=store,
device_id="test_group",
group_mode="sequence",
group_device_ids=[d.id for d in devices],
)
client = self._make_client(store, devices)
await client.connect()
sent_pixels = []