From e07e18d0770c3259c86c3bc9247021d71f1645a2 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Sat, 8 Apr 2023 12:29:06 -0500 Subject: [PATCH 1/5] initial commit passes all tests --- custom_components/adaptive_lighting/switch.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index 884f74cd..a0894881 100644 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -298,7 +298,6 @@ def _get_switches_with_lights( continue switch = data[config.entry_id]["instance"] all_check_lights = _expand_light_groups(hass, lights) - switch._expand_light_groups() # Check if any of the lights are in the switch's lights if set(switch._lights) & set(all_check_lights): switches.append(switch) @@ -613,8 +612,8 @@ def _expand_light_groups(hass: HomeAssistant, lights: list[str]) -> list[str]: if state is None: _LOGGER.debug("State of %s is None", light) all_lights.add(light) - elif "entity_id" in state.attributes: # it's a light group - group = state.attributes["entity_id"] + elif ATTR_ENTITY_ID in state.attributes: # it's a light group + group = state.attributes[ATTR_ENTITY_ID] turn_on_off_listener.lights.discard(light) all_lights.update(group) _LOGGER.debug("Expanded %s to %s", light, group) @@ -838,6 +837,11 @@ def __init__( data, ) + def __getattribute__(self, name): + if name == "_lights": + self._expand_light_groups() + return object.__getattribute__(self, name) + def _set_changeable_settings( self, data: dict, @@ -957,12 +961,14 @@ async def async_will_remove_from_hass(self): self._remove_listeners() def _expand_light_groups(self) -> None: - all_lights = _expand_light_groups(self.hass, self._lights) + all_lights = _expand_light_groups( + self.hass, object.__getattribute__(self, "_lights") + ) self.turn_on_off_listener.lights.update(all_lights) self.turn_on_off_listener.set_auto_reset_manual_control_times( all_lights, self._auto_reset_manual_control_time ) - self._lights = list(all_lights) + object.__setattr__(self, "_lights", list(all_lights)) async def _setup_listeners(self, _=None) -> None: _LOGGER.debug("%s: Called '_setup_listeners'", self._name) @@ -984,7 +990,6 @@ async def _setup_listeners(self, _=None) -> None: self.remove_listeners.extend([remove_interval, remove_sleep]) if self._lights: - self._expand_light_groups() remove_state = async_track_state_change_event( self.hass, self._lights, self._light_event ) From cd3f7608deaacc5ad04c60eddce2c4cab086c5cd Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Sat, 8 Apr 2023 12:52:09 -0500 Subject: [PATCH 2/5] only access `_lights` class variable when necessary --- custom_components/adaptive_lighting/switch.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index a0894881..1793a116 100644 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -299,7 +299,9 @@ def _get_switches_with_lights( switch = data[config.entry_id]["instance"] all_check_lights = _expand_light_groups(hass, lights) # Check if any of the lights are in the switch's lights - if set(switch._lights) & set(all_check_lights): + if set(switch._lights) & set( + all_check_lights + ): # pylint: disable=protected-access switches.append(switch) return switches @@ -483,7 +485,9 @@ async def handle_apply(service_call: ServiceCall): lights = data[CONF_LIGHTS] for switch in switches: if not lights: - all_lights = switch._lights # pylint: disable=protected-access + all_lights = ( + lights or switch._lights + ) # pylint: disable=protected-access else: all_lights = _expand_light_groups(switch.hass, lights) switch.turn_on_off_listener.lights.update(all_lights) @@ -989,9 +993,10 @@ async def _setup_listeners(self, _=None) -> None: self.remove_listeners.extend([remove_interval, remove_sleep]) - if self._lights: + lights = self._lights + if lights: remove_state = async_track_state_change_event( - self.hass, self._lights, self._light_event + self.hass, lights, self._light_event ) self.remove_listeners.append(remove_state) @@ -1013,16 +1018,17 @@ def extra_state_attributes(self) -> dict[str, Any]: for key in self._settings: extra_state_attributes[key] = None return extra_state_attributes + lights = self._lights extra_state_attributes["manual_control"] = [ light - for light in self._lights + for light in lights if self.turn_on_off_listener.manual_control.get(light) ] extra_state_attributes.update(self._settings) timers = self.turn_on_off_listener.auto_reset_manual_control_timers extra_state_attributes["autoreset_time_remaining"] = { light: time - for light in self._lights + for light in lights if (timer := timers.get(light)) and (time := timer.remaining_time()) > 0 } return extra_state_attributes @@ -1673,7 +1679,7 @@ def _handle_timer( timer.cancel() timers_dict.pop(light) else: # Timer object already exists, just update the delay and restart it - timer.delay = delay + timer.delay = delay # pylint: disable=protected-access timer.start() elif delay is not None: # Timer object does not exist, create it timer = _AsyncSingleShotTimer(delay, reset_coroutine) @@ -1696,8 +1702,8 @@ def start_transition_timer(self, light: str) -> None: "Start transition timer of %s seconds for light %s", last_transition, light ) - async def reset(): - ValueError("TEST") + def reset(): + raise ValueError("TEST") _LOGGER.debug( "Transition finished for light %s", light, From 4defd7315601ec9c0e083c44b3718cd2bb9b16d8 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Sat, 8 Apr 2023 12:56:15 -0500 Subject: [PATCH 3/5] don't modify things unrelated to the PR --- custom_components/adaptive_lighting/switch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index 1793a116..50b26470 100644 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -1702,8 +1702,8 @@ def start_transition_timer(self, light: str) -> None: "Start transition timer of %s seconds for light %s", last_transition, light ) - def reset(): - raise ValueError("TEST") + async def reset(): + ValueError("TEST") _LOGGER.debug( "Transition finished for light %s", light, From 6cfe8bff93a7bce89dd75ca258e02409f01079ca Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Mon, 10 Apr 2023 12:27:43 -0500 Subject: [PATCH 4/5] requested change --- custom_components/adaptive_lighting/switch.py | 56 +++++++++---------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index 92bbf83c..50a8ba6c 100644 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -297,9 +297,11 @@ def _get_switches_with_lights( if entry is None: # entry might be disabled and therefore missing continue switch = data[config.entry_id]["instance"] - all_check_lights = _expand_light_groups(hass, lights) + all_check_lights = switch.get_lights( + hass, lights + ) # pylint: disable=protected-access # Check if any of the lights are in the switch's lights - if set(switch._lights) & set( + if set(switch.get_lights()) & set( all_check_lights ): # pylint: disable=protected-access switches.append(switch) @@ -402,7 +404,7 @@ async def handle_change_switch_settings( data, ) - all_lights = switch._lights # pylint: disable=protected-access + all_lights = switch.get_lights() # pylint: disable=protected-access switch.turn_on_off_listener.reset(*all_lights, reset_manual_control=False) if switch.is_on: await switch._update_attrs_and_maybe_adapt_lights( # pylint: disable=protected-access @@ -485,12 +487,9 @@ async def handle_apply(service_call: ServiceCall): switches = _get_switches_from_service_call(hass, service_call) lights = data[CONF_LIGHTS] for switch in switches: - if not lights: - all_lights = ( - lights or switch._lights - ) # pylint: disable=protected-access - else: - all_lights = _expand_light_groups(switch.hass, lights) + all_lights = ( + lights or switch.get_lights() + ) # pylint: disable=protected-access switch.turn_on_off_listener.lights.update(all_lights) for light in all_lights: if data[CONF_TURN_ON_LIGHTS] or is_on(hass, light): @@ -517,10 +516,9 @@ async def handle_set_manual_control(service_call: ServiceCall): switches = _get_switches_from_service_call(hass, service_call) lights = data[CONF_LIGHTS] for switch in switches: - if not lights: - all_lights = switch._lights # pylint: disable=protected-access - else: - all_lights = _expand_light_groups(switch.hass, lights) + all_lights = ( + lights or switch.get_lights() + ) # pylint: disable=protected-access if service_call.data[CONF_MANUAL_CONTROL]: for light in all_lights: _fire_manual_control_event(switch, light, service_call.context) @@ -841,11 +839,6 @@ def __init__( data, ) - def __getattribute__(self, name): - if name == "_lights": - self._expand_light_groups() - return object.__getattribute__(self, name) - def _set_changeable_settings( self, data: dict, @@ -892,7 +885,7 @@ def _set_changeable_settings( ) self._take_over_control = True self._auto_reset_manual_control_time = data[CONF_AUTORESET_CONTROL] - self._expand_light_groups() # updates manual control timers + self._update_lights() # updates manual control timers _loc = get_astral_location(self.hass) if isinstance(_loc, tuple): # Astral v2.2 @@ -944,6 +937,11 @@ def is_on(self) -> bool | None: """Return true if adaptive lighting is on.""" return self._state + @property + def get_lights(self): + self._update_lights() + return self._lights + async def async_added_to_hass(self) -> None: """Call when entity about to be added to hass.""" if self.hass.is_running: @@ -964,15 +962,13 @@ async def async_will_remove_from_hass(self): """Remove the listeners upon removing the component.""" self._remove_listeners() - def _expand_light_groups(self) -> None: - all_lights = _expand_light_groups( - self.hass, object.__getattribute__(self, "_lights") - ) + def _update_lights(self) -> None: + all_lights = _expand_light_groups(self.hass, self._lights) self.turn_on_off_listener.lights.update(all_lights) self.turn_on_off_listener.set_auto_reset_manual_control_times( all_lights, self._auto_reset_manual_control_time ) - object.__setattr__(self, "_lights", list(all_lights)) + self._lights = list(all_lights) async def _setup_listeners(self, _=None) -> None: _LOGGER.debug("%s: Called '_setup_listeners'", self._name) @@ -993,7 +989,7 @@ async def _setup_listeners(self, _=None) -> None: self.remove_listeners.extend([remove_interval, remove_sleep]) - lights = self._lights + lights = self.get_lights() if lights: remove_state = async_track_state_change_event( self.hass, lights, self._light_event @@ -1018,7 +1014,7 @@ def extra_state_attributes(self) -> dict[str, Any]: for key in self._settings: extra_state_attributes[key] = None return extra_state_attributes - lights = self._lights + lights = self.get_lights() extra_state_attributes["manual_control"] = [ light for light in lights @@ -1061,7 +1057,7 @@ async def async_turn_on( # pylint: disable=arguments-differ if self.is_on: return self._state = True - self.turn_on_off_listener.reset(*self._lights) + self.turn_on_off_listener.reset(*self.get_lights()) await self._setup_listeners() if adapt_lights: await self._update_attrs_and_maybe_adapt_lights( @@ -1076,7 +1072,7 @@ async def async_turn_off(self, **kwargs) -> None: return self._state = False self._remove_listeners() - self.turn_on_off_listener.reset(*self._lights) + self.turn_on_off_listener.reset(*self.get_lights()) async def _async_update_at_interval(self, now=None) -> None: await self._update_attrs_and_maybe_adapt_lights( @@ -1212,7 +1208,7 @@ async def _update_attrs_and_maybe_adapt_lights( self.async_write_ha_state() if lights is None: - lights = self._lights + lights = self.get_lights() filtered_lights = [] if not force: @@ -1304,7 +1300,7 @@ async def _sleep_mode_switch_state_event(self, event: Event) -> None: "%s: _sleep_mode_switch_state_event, event: '%s'", self._name, event ) # Reset the manually controlled status when the "sleep mode" changes - self.turn_on_off_listener.reset(*self._lights) + self.turn_on_off_listener.reset(*self.get_lights()) await self._update_attrs_and_maybe_adapt_lights( transition=self._sleep_transition, force=True, From 7106a775433ac86f5775c2c669b48e927fa500ef Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Mon, 10 Apr 2023 12:34:24 -0500 Subject: [PATCH 5/5] fix the last commit --- custom_components/adaptive_lighting/switch.py | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index 50a8ba6c..4b7202e9 100644 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -297,11 +297,9 @@ def _get_switches_with_lights( if entry is None: # entry might be disabled and therefore missing continue switch = data[config.entry_id]["instance"] - all_check_lights = switch.get_lights( - hass, lights - ) # pylint: disable=protected-access + all_check_lights = _expand_light_groups(hass, lights) # Check if any of the lights are in the switch's lights - if set(switch.get_lights()) & set( + if set(switch.all_lights) & set( all_check_lights ): # pylint: disable=protected-access switches.append(switch) @@ -404,7 +402,7 @@ async def handle_change_switch_settings( data, ) - all_lights = switch.get_lights() # pylint: disable=protected-access + all_lights = switch.all_lights # pylint: disable=protected-access switch.turn_on_off_listener.reset(*all_lights, reset_manual_control=False) if switch.is_on: await switch._update_attrs_and_maybe_adapt_lights( # pylint: disable=protected-access @@ -487,9 +485,7 @@ async def handle_apply(service_call: ServiceCall): switches = _get_switches_from_service_call(hass, service_call) lights = data[CONF_LIGHTS] for switch in switches: - all_lights = ( - lights or switch.get_lights() - ) # pylint: disable=protected-access + all_lights = lights or switch.all_lights # pylint: disable=protected-access switch.turn_on_off_listener.lights.update(all_lights) for light in all_lights: if data[CONF_TURN_ON_LIGHTS] or is_on(hass, light): @@ -516,9 +512,7 @@ async def handle_set_manual_control(service_call: ServiceCall): switches = _get_switches_from_service_call(hass, service_call) lights = data[CONF_LIGHTS] for switch in switches: - all_lights = ( - lights or switch.get_lights() - ) # pylint: disable=protected-access + all_lights = lights or switch.all_lights # pylint: disable=protected-access if service_call.data[CONF_MANUAL_CONTROL]: for light in all_lights: _fire_manual_control_event(switch, light, service_call.context) @@ -938,7 +932,7 @@ def is_on(self) -> bool | None: return self._state @property - def get_lights(self): + def all_lights(self): self._update_lights() return self._lights @@ -989,7 +983,7 @@ async def _setup_listeners(self, _=None) -> None: self.remove_listeners.extend([remove_interval, remove_sleep]) - lights = self.get_lights() + lights = self.all_lights if lights: remove_state = async_track_state_change_event( self.hass, lights, self._light_event @@ -1014,7 +1008,7 @@ def extra_state_attributes(self) -> dict[str, Any]: for key in self._settings: extra_state_attributes[key] = None return extra_state_attributes - lights = self.get_lights() + lights = self.all_lights extra_state_attributes["manual_control"] = [ light for light in lights @@ -1057,7 +1051,7 @@ async def async_turn_on( # pylint: disable=arguments-differ if self.is_on: return self._state = True - self.turn_on_off_listener.reset(*self.get_lights()) + self.turn_on_off_listener.reset(*self.all_lights) await self._setup_listeners() if adapt_lights: await self._update_attrs_and_maybe_adapt_lights( @@ -1072,7 +1066,7 @@ async def async_turn_off(self, **kwargs) -> None: return self._state = False self._remove_listeners() - self.turn_on_off_listener.reset(*self.get_lights()) + self.turn_on_off_listener.reset(*self.all_lights) async def _async_update_at_interval(self, now=None) -> None: await self._update_attrs_and_maybe_adapt_lights( @@ -1208,7 +1202,7 @@ async def _update_attrs_and_maybe_adapt_lights( self.async_write_ha_state() if lights is None: - lights = self.get_lights() + lights = self.all_lights filtered_lights = [] if not force: @@ -1300,7 +1294,7 @@ async def _sleep_mode_switch_state_event(self, event: Event) -> None: "%s: _sleep_mode_switch_state_event, event: '%s'", self._name, event ) # Reset the manually controlled status when the "sleep mode" changes - self.turn_on_off_listener.reset(*self.get_lights()) + self.turn_on_off_listener.reset(*self.all_lights) await self._update_attrs_and_maybe_adapt_lights( transition=self._sleep_transition, force=True,