Skip to content

Commit 320e050

Browse files
authored
Refactor models to improve performance (#1266)
1 parent 7d517d3 commit 320e050

File tree

1 file changed

+77
-76
lines changed

1 file changed

+77
-76
lines changed

src/wled/models.py

Lines changed: 77 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,22 @@
44

55
from dataclasses import dataclass
66
from enum import IntEnum, IntFlag
7+
from functools import lru_cache
8+
from operator import attrgetter
79
from typing import Any
810

911
from awesomeversion import AwesomeVersion
1012

1113
from .exceptions import WLEDError
1214

15+
NAME_GETTER = attrgetter("name")
16+
17+
18+
@lru_cache
19+
def get_awesome_version(version: str) -> AwesomeVersion:
20+
"""Return a cached AwesomeVersion object."""
21+
return AwesomeVersion(version)
22+
1323

1424
@dataclass
1525
class Nightlight:
@@ -140,8 +150,8 @@ def from_dict( # noqa: PLR0913
140150
segment_id: int,
141151
data: dict[str, Any],
142152
*,
143-
effects: list[Effect],
144-
palettes: list[Palette],
153+
effects: dict[int, Effect],
154+
palettes: dict[int, Palette],
145155
state_on: bool,
146156
state_brightness: int,
147157
) -> Segment:
@@ -151,8 +161,8 @@ def from_dict( # noqa: PLR0913
151161
----
152162
segment_id: The ID of the LED strip segment.
153163
data: The segment data received from the WLED device.
154-
effects: A list of Effect objects.
155-
palettes: A list of Palette objects.
164+
effects: An indexed dict of Effect objects.
165+
palettes: An indexed dict of Palette objects.
156166
state_on: Boolean the represents the on/off state of this segment.
157167
state_brightness: The brightness level of this segment.
158168
@@ -174,13 +184,9 @@ def from_dict( # noqa: PLR0913
174184
except IndexError:
175185
pass
176186

177-
effect = next(
178-
(item for item in effects if item.effect_id == data.get("fx", 0)),
179-
Effect(effect_id=0, name="Unknown"),
180-
)
181-
palette = next(
182-
(item for item in palettes if item.palette_id == data.get("pal", 0)),
183-
Palette(palette_id=0, name="Unknown"),
187+
effect = effects.get(data.get("fx", 0)) or Effect(effect_id=0, name="Unknown")
188+
palette = palettes.get(data.get("pal", 0)) or Palette(
189+
palette_id=0, name="Unknown"
184190
)
185191

186192
return Segment(
@@ -389,15 +395,15 @@ def from_dict(data: dict[str, Any]) -> Info:
389395
websocket = None
390396

391397
if version := data.get("ver"):
392-
version = AwesomeVersion(version)
398+
version = get_awesome_version(version)
393399
if not version.valid:
394400
version = None
395401

396402
if version_latest_stable := data.get("version_latest_stable"):
397-
version_latest_stable = AwesomeVersion(version_latest_stable)
403+
version_latest_stable = get_awesome_version(version_latest_stable)
398404

399405
if version_latest_beta := data.get("version_latest_beta"):
400-
version_latest_beta = AwesomeVersion(version_latest_beta)
406+
version_latest_beta = get_awesome_version(version_latest_beta)
401407

402408
arch = data.get("arch", "Unknown")
403409
if (
@@ -477,20 +483,20 @@ def preset_active(self) -> bool:
477483
@staticmethod
478484
def from_dict(
479485
data: dict[str, Any],
480-
effects: list[Effect],
481-
palettes: list[Palette],
482-
presets: list[Preset],
483-
playlists: list[Playlist],
486+
effects: dict[int, Effect],
487+
palettes: dict[int, Palette],
488+
presets: dict[int, Preset],
489+
playlists: dict[int, Playlist],
484490
) -> State:
485491
"""Return State object from WLED API response.
486492
487493
Args:
488494
----
489495
data: The state response received from the WLED device API.
490-
effects: A list of effect objects.
491-
palettes: A list of palette objects.
492-
presets: A list of preset objects.
493-
playlists: A list of playlist objects.
496+
effects: A dict index of effect objects.
497+
palettes: A dict index of palette objects.
498+
presets: A dict index of preset objects.
499+
playlists: A dict index of playlist objects.
494500
495501
Returns:
496502
-------
@@ -516,15 +522,8 @@ def from_dict(
516522
playlist = data.get("pl", -1)
517523
preset = data.get("ps", -1)
518524
if presets:
519-
preset = next(
520-
(item for item in presets if item.preset_id == data.get("ps")),
521-
None,
522-
)
523-
524-
playlist = next(
525-
(item for item in playlists if item.playlist_id == data.get("pl")),
526-
None,
527-
)
525+
playlist = playlists.get(playlist)
526+
preset = presets.get(preset)
528527

529528
return State(
530529
brightness=brightness,
@@ -556,17 +555,17 @@ class Preset:
556555
def from_dict(
557556
preset_id: int,
558557
data: dict[str, Any],
559-
effects: list[Effect],
560-
palettes: list[Palette],
558+
effects: dict[int, Effect],
559+
palettes: dict[int, Palette],
561560
) -> Preset:
562561
"""Return Preset object from WLED API response.
563562
564563
Args:
565564
----
566565
preset_id: The ID of the preset.
567566
data: The data from the WLED device API.
568-
effects: A list of effect objects.
569-
palettes: A list of palette object.
567+
effects: A indexed dict of effect objects.
568+
palettes: A indexed dict of palette object.
570569
571570
Returns:
572571
-------
@@ -591,10 +590,10 @@ def from_dict(
591590
for segment_id, segment in enumerate(segment_data)
592591
]
593592

594-
main_segment = next(
595-
(item for item in segments if item.segment_id == data.get("mainseg", 0)),
596-
None,
597-
)
593+
try:
594+
main_segment = segments[data.get("mainseg", 0)]
595+
except IndexError:
596+
main_segment = None
598597

599598
return Preset(
600599
main_segment=main_segment,
@@ -632,7 +631,7 @@ class Playlist:
632631
def from_dict(
633632
playlist_id: int,
634633
data: dict[str, Any],
635-
presets: list[Preset],
634+
presets: dict[int, Preset],
636635
) -> Playlist:
637636
"""Return Playlist object from WLED API response.
638637
@@ -657,18 +656,12 @@ def from_dict(
657656
entry_id=entry_id,
658657
duration=entries_durations[entry_id],
659658
transition=entries_transitions[entry_id],
660-
preset=next(
661-
(item for item in presets if item.preset_id == preset_id),
662-
None,
663-
),
659+
preset=presets.get(preset_id),
664660
)
665661
for entry_id, preset_id in enumerate(entries_presets)
666662
]
667663

668-
end = next(
669-
(item for item in presets if item.preset_id == playlist.get("end")),
670-
None,
671-
)
664+
end = presets.get(playlist.get("end"))
672665

673666
return Playlist(
674667
playlist_id=playlist_id,
@@ -703,6 +696,11 @@ def __init__(self, data: dict[str, Any]) -> None:
703696
that a Device object cannot be constructed from it.
704697
705698
"""
699+
self._indexed_effects: dict[int, Effect] = {}
700+
self._indexed_palettes: dict[int, Palette] = {}
701+
self._indexed_presets: dict[int, Preset] = {}
702+
self._indexed_playlists: dict[int, Playlist] = {}
703+
706704
self.effects = []
707705
self.palettes = []
708706
self.playlists = []
@@ -731,57 +729,60 @@ def update_from_dict(self, data: dict[str, Any]) -> Device:
731729
732730
"""
733731
if _effects := data.get("effects"):
734-
effects = [
735-
Effect(effect_id=effect_id, name=effect)
732+
self._indexed_effects = {
733+
effect_id: Effect(effect_id=effect_id, name=effect)
736734
for effect_id, effect in enumerate(_effects)
737-
]
738-
effects.sort(key=lambda x: x.name)
739-
self.effects = effects
735+
}
736+
self.effects = sorted(self._indexed_effects.values(), key=NAME_GETTER)
740737

741738
if _palettes := data.get("palettes"):
742-
palettes = [
743-
Palette(palette_id=palette_id, name=palette)
739+
self._indexed_palettes = {
740+
palette_id: Palette(palette_id=palette_id, name=palette)
744741
for palette_id, palette in enumerate(_palettes)
745-
]
746-
palettes.sort(key=lambda x: x.name)
747-
self.palettes = palettes
742+
}
743+
self.palettes = sorted(self._indexed_palettes.values(), key=NAME_GETTER)
748744

749745
if _presets := data.get("presets"):
750746
# The preset data contains both presets and playlists,
751747
# we split those out, so we can handle those correctly.
752-
753-
# Nobody cares about 0.
754-
_presets.pop("0", None)
755-
756-
presets = [
757-
Preset.from_dict(int(preset_id), preset, self.effects, self.palettes)
748+
self._indexed_presets = {
749+
int(preset_id): Preset.from_dict(
750+
int(preset_id),
751+
preset,
752+
self._indexed_effects,
753+
self._indexed_palettes,
754+
)
758755
for preset_id, preset in _presets.items()
759756
if "playlist" not in preset
760757
or not ("ps" in preset["playlist"] and preset["playlist"]["ps"])
761-
]
762-
presets.sort(key=lambda x: x.name)
763-
self.presets = presets
758+
}
759+
# Nobody cares about 0.
760+
self._indexed_presets.pop(0, None)
761+
self.presets = sorted(self._indexed_presets.values(), key=NAME_GETTER)
764762

765-
playlists = [
766-
Playlist.from_dict(int(playlist_id), playlist, self.presets)
763+
self._indexed_playlists = {
764+
int(playlist_id): Playlist.from_dict(
765+
int(playlist_id), playlist, self._indexed_presets
766+
)
767767
for playlist_id, playlist in _presets.items()
768768
if "playlist" in playlist
769769
and "ps" in playlist["playlist"]
770770
and playlist["playlist"]["ps"]
771-
]
772-
playlists.sort(key=lambda x: x.name)
773-
self.playlists = playlists
771+
}
772+
# Nobody cares about 0.
773+
self._indexed_playlists.pop(0, None)
774+
self.playlists = sorted(self._indexed_playlists.values(), key=NAME_GETTER)
774775

775776
if _info := data.get("info"):
776777
self.info = Info.from_dict(_info)
777778

778779
if _state := data.get("state"):
779780
self.state = State.from_dict(
780781
_state,
781-
self.effects,
782-
self.palettes,
783-
self.presets,
784-
self.playlists,
782+
self._indexed_effects,
783+
self._indexed_palettes,
784+
self._indexed_presets,
785+
self._indexed_playlists,
785786
)
786787

787788
return self

0 commit comments

Comments
 (0)