From 2ec29121158e5ceef07ddcfb4c872396165f2176 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Sat, 8 Apr 2023 11:52:33 -0500 Subject: [PATCH] passes pytest fails pre-commit custom_components/adaptive_lighting/switch.py:393:1: C901 'async_setup_entry' is too complex (22) --- custom_components/adaptive_lighting/const.py | 46 +++-- custom_components/adaptive_lighting/switch.py | 168 +++++++++--------- tests/test_switch.py | 33 +++- 3 files changed, 139 insertions(+), 108 deletions(-) diff --git a/custom_components/adaptive_lighting/const.py b/custom_components/adaptive_lighting/const.py index 64620927..3455f298 100644 --- a/custom_components/adaptive_lighting/const.py +++ b/custom_components/adaptive_lighting/const.py @@ -196,9 +196,9 @@ DOCS[CONF_TURN_ON_LIGHTS] = "Whether to turn on lights that are currently off. SERVICE_CHANGE_SWITCH_SETTINGS = "change_switch_settings" CONF_USE_DEFAULTS = "use_defaults" DOCS[CONF_USE_DEFAULTS] = ( - "Sets the default values not specified in this service call. Options: " + "Where to autofill config options that are not passed to this service. Options: " '"current" (default, retains current values), "factory" (resets to ' - 'documented defaults), or "configuration" (reverts to switch config defaults). ⚙️' + 'documented defaults), or "configuration" (reverts to original user config). ⚙️' ) TURNING_OFF_DELAY = 5 @@ -321,28 +321,36 @@ _DOMAIN_SCHEMA = vol.Schema( ) -def apply_service_schema(initial_transition: int = 1): - """Return the schema for the apply service.""" - return vol.Schema( - { - vol.Optional(CONF_ENTITY_ID): cv.entity_ids, - vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids, - vol.Optional( - CONF_TRANSITION, - default=initial_transition, - ): VALID_TRANSITION, - vol.Optional(ATTR_ADAPT_BRIGHTNESS, default=True): cv.boolean, - vol.Optional(ATTR_ADAPT_COLOR, default=True): cv.boolean, - vol.Optional(CONF_PREFER_RGB_COLOR, default=False): cv.boolean, - vol.Optional(CONF_TURN_ON_LIGHTS, default=False): cv.boolean, - } - ) +SCHEMA_APPLY = vol.Schema( + { + vol.Optional(CONF_ENTITY_ID): cv.entity_ids, + vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids, + vol.Optional(CONF_TRANSITION): VALID_TRANSITION, + vol.Optional(ATTR_ADAPT_BRIGHTNESS, default=True): cv.boolean, + vol.Optional(ATTR_ADAPT_COLOR, default=True): cv.boolean, + vol.Optional(CONF_PREFER_RGB_COLOR, default=False): cv.boolean, + vol.Optional(CONF_TURN_ON_LIGHTS, default=False): cv.boolean, + } +) -SET_MANUAL_CONTROL_SCHEMA = vol.Schema( +SCHEMA_SET_MANUAL_CONTROL = vol.Schema( { vol.Optional(CONF_ENTITY_ID): cv.entity_ids, vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids, vol.Optional(CONF_MANUAL_CONTROL, default=True): cv.boolean, } ) + +SCHEMA_CHANGE_SWITCH_SETTINGS = vol.Schema( + { + vol.Optional(CONF_USE_DEFAULTS): cv.string, + vol.Optional(CONF_ENTITY_ID): cv.entity_ids, + vol.Required(CONF_LIGHTS, default=[]): [], + **{ + vol.Optional(k): valid + for k, _, valid in VALIDATION_TUPLES + if k not in [CONF_INTERVAL, CONF_NAME, CONF_LIGHTS] + }, + } +) diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index 884f74cd..bc64fbff 100644 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -71,7 +71,7 @@ from homeassistant.core import ( State, callback, ) -from homeassistant.helpers import entity_platform, entity_registry +from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import ( async_track_state_change_event, @@ -89,7 +89,6 @@ from homeassistant.util.color import ( ) import homeassistant.util.dt as dt_util import ulid_transform -import voluptuous as vol from .const import ( ADAPT_BRIGHTNESS_SWITCH, @@ -135,16 +134,17 @@ from .const import ( ICON_COLOR_TEMP, ICON_MAIN, ICON_SLEEP, + SCHEMA_APPLY, + SCHEMA_CHANGE_SWITCH_SETTINGS, + SCHEMA_SET_MANUAL_CONTROL, SERVICE_APPLY, SERVICE_CHANGE_SWITCH_SETTINGS, SERVICE_SET_MANUAL_CONTROL, - SET_MANUAL_CONTROL_SCHEMA, SLEEP_MODE_SWITCH, SUN_EVENT_MIDNIGHT, SUN_EVENT_NOON, TURNING_OFF_DELAY, VALIDATION_TUPLES, - apply_service_schema, replace_none_str, ) @@ -286,9 +286,9 @@ def _split_service_data(service_data, adapt_brightness, adapt_color): def _get_switches_with_lights( - hass: HomeAssistant, lights: list[str] + hass: HomeAssistant, lights: list[str] | None = None ) -> list[AdaptiveSwitch]: - """Get all switches that control at least one of the lights passed.""" + """Get all switches. If lights is defined, return only switches found with these lights.""" config_entries = hass.config_entries.async_entries(DOMAIN) data = hass.data[DOMAIN] switches = [] @@ -297,10 +297,13 @@ 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) - switch._expand_light_groups() - # Check if any of the lights are in the switch's lights - if set(switch._lights) & set(all_check_lights): + if lights: + 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) + else: switches.append(switch) return switches @@ -342,13 +345,7 @@ def _get_switches_from_service_call( switch_entity_ids: list[str] | None = data.get("entity_id") if not lights and not switch_entity_ids: - raise ValueError( - "adaptive-lighting: Neither a switch nor a light was provided in the service call." - " If you intend to adapt all lights on all switches, please inform the developers at" - " https://github.com/basnijholt/adaptive-lighting about your use case." - " Currently, you must pass either an adaptive-lighting switch or the lights to an" - " `adaptive_lighting` service call." - ) + return _get_switches_with_lights(hass) if switch_entity_ids is not None: if len(switch_entity_ids) > 1 and lights: @@ -374,44 +371,6 @@ def _get_switches_from_service_call( ) -async def handle_change_switch_settings( - switch: AdaptiveSwitch, service_call: ServiceCall -) -> None: - """Allows HASS to change config values via a service call.""" - data = service_call.data - - which = data.get(CONF_USE_DEFAULTS, "current") - if which == "current": # use whatever we're already using. - defaults = switch._current_settings # pylint: disable=protected-access - elif which == "factory": # use actual defaults listed in the documentation - defaults = {key: default for key, default, _ in VALIDATION_TUPLES} - elif which == "configuration": - # use whatever's in the config flow or configuration.yaml - defaults = switch._config_backup # pylint: disable=protected-access - else: - defaults = None - - switch._set_changeable_settings( - data=data, - defaults=defaults, - ) - - _LOGGER.debug( - "Called 'adaptive_lighting.change_switch_settings' service with '%s'", - data, - ) - - all_lights = switch._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 - all_lights, - transition=switch._initial_transition, - force=True, - context=switch.create_context("service", parent=service_call.context), - ) - - @callback def _fire_manual_control_event( switch: AdaptiveSwitch, light: str, context: Context, is_async=True @@ -489,18 +448,29 @@ async def async_setup_entry( all_lights = _expand_light_groups(switch.hass, lights) 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): - await switch._adapt_light( # pylint: disable=protected-access - light, - data[CONF_TRANSITION], - data[ATTR_ADAPT_BRIGHTNESS], - data[ATTR_ADAPT_COLOR], - data[CONF_PREFER_RGB_COLOR], - force=True, - context=switch.create_context( - "service", parent=service_call.context - ), - ) + transition = data.get(CONF_TRANSITION) + if not data[CONF_TURN_ON_LIGHTS]: + if not is_on(hass, light): + continue + if not transition: + transition = ( + switch._transition + ) # pylint: disable=protected-access + elif not transition: + transition = ( + switch._initial_transition + ) # pylint: disable=protected-access + await switch._adapt_light( # pylint: disable=protected-access + light, + transition, + data[ATTR_ADAPT_BRIGHTNESS], + data[ATTR_ADAPT_COLOR], + data[CONF_PREFER_RGB_COLOR], + force=True, + context=switch.create_context( + "service", parent=service_call.context + ), + ) @callback async def handle_set_manual_control(service_call: ServiceCall): @@ -534,14 +504,54 @@ async def async_setup_entry( ), ) + @callback + async def handle_change_switch_settings(service_call: ServiceCall) -> None: + """Allows HASS to change config values via a service call.""" + data = service_call.data + _LOGGER.debug( + "Called 'adaptive_lighting.change_switch_settings' service with '%s'", + data, + ) + + switches = _get_switches_from_service_call(hass, service_call) + for switch in switches: + # which denotes where to autofill blank config options. + which = data.get(CONF_USE_DEFAULTS, "current") + if which == "current": + # use whatever we're already using. + defaults = switch._current_settings # pylint: disable=protected-access + elif which == "factory": + # use actual defaults listed in the documentation + defaults = {key: default for key, default, _ in VALIDATION_TUPLES} + elif which == "configuration": + # use whatever's in the config flow or configuration.yaml + defaults = switch._config_backup # pylint: disable=protected-access + else: + defaults = None + + switch._set_changeable_settings( + data=data, + defaults=defaults, + ) + + all_lights = switch._lights # pylint: disable=protected-access + switch.turn_on_off_listener.reset(*all_lights, reset_manual_control=False) + + if not switch.is_on: + continue + await switch._update_attrs_and_maybe_adapt_lights( # pylint: disable=protected-access + all_lights, + transition=switch._transition, + force=True, + context=switch.create_context("service", parent=service_call.context), + ) + # Register `apply` service hass.services.async_register( domain=DOMAIN, service=SERVICE_APPLY, service_func=handle_apply, - schema=apply_service_schema( - switch._initial_transition - ), # pylint: disable=protected-access + schema=SCHEMA_APPLY, ) # Register `set_manual_control` service @@ -549,20 +559,14 @@ async def async_setup_entry( domain=DOMAIN, service=SERVICE_SET_MANUAL_CONTROL, service_func=handle_set_manual_control, - schema=SET_MANUAL_CONTROL_SCHEMA, + schema=SCHEMA_SET_MANUAL_CONTROL, ) - args = {vol.Optional(CONF_USE_DEFAULTS, default="current"): cv.string} - # Modifying these after init isn't possible - skip = (CONF_INTERVAL, CONF_NAME, CONF_LIGHTS) - for k, _, valid in VALIDATION_TUPLES: - if k not in skip: - args[vol.Optional(k)] = valid - platform = entity_platform.current_platform.get() - platform.async_register_entity_service( - SERVICE_CHANGE_SWITCH_SETTINGS, - args, - handle_change_switch_settings, + hass.services.async_register( + domain=DOMAIN, + service=SERVICE_CHANGE_SWITCH_SETTINGS, + service_func=handle_change_switch_settings, + schema=SCHEMA_CHANGE_SWITCH_SETTINGS, ) diff --git a/tests/test_switch.py b/tests/test_switch.py index 476e83cc..8b105d44 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -1256,6 +1256,9 @@ async def test_area(hass): async def test_change_switch_settings_service(hass): """Test adaptive_lighting.change_switch_settings service.""" switch, (_, _, light) = await setup_lights_and_switch(hass) + switch2, (_, light2, _) = await setup_lights_and_switch( + hass, {CONF_NAME: "second_switch"} + ) entity_id = light.entity_id assert entity_id not in switch._lights @@ -1264,7 +1267,6 @@ async def test_change_switch_settings_service(hass): DOMAIN, SERVICE_CHANGE_SWITCH_SETTINGS, { - ATTR_ENTITY_ID: ENTITY_SWITCH, **kwargs, }, blocking=True, @@ -1273,12 +1275,16 @@ async def test_change_switch_settings_service(hass): # Test changing sunrise offset assert switch._sun_light_settings.sunrise_offset.total_seconds() == 0 - await change_switch_settings(**{CONF_SUNRISE_OFFSET: 10}) + await change_switch_settings( + **{ATTR_ENTITY_ID: ENTITY_SWITCH, CONF_SUNRISE_OFFSET: 10} + ) assert switch._sun_light_settings.sunrise_offset.total_seconds() == 10 # Test changing max brightness assert switch._sun_light_settings.max_brightness == 100 - await change_switch_settings(**{CONF_MAX_BRIGHTNESS: 50}) + await change_switch_settings( + **{ATTR_ENTITY_ID: ENTITY_SWITCH, CONF_MAX_BRIGHTNESS: 50} + ) assert switch._sun_light_settings.max_brightness == 50 # Test changing to illegal max brightness @@ -1286,20 +1292,33 @@ async def test_change_switch_settings_service(hass): voluptuous.error.MultipleInvalid, match="value must be at most 100 for dictionary", ): - await change_switch_settings(**{CONF_MAX_BRIGHTNESS: 5000}) + await change_switch_settings( + **{ATTR_ENTITY_ID: ENTITY_SWITCH, CONF_MAX_BRIGHTNESS: 5000} + ) # Change CONF_MIN_COLOR_TEMP, the factory default is 2000, but setup_lights_and_switch # sets it to 2500 assert switch._sun_light_settings.min_color_temp == 2500 # testing with "factory" should change it to 2000 - await change_switch_settings(**{CONF_USE_DEFAULTS: "factory"}) + await change_switch_settings( + **{ATTR_ENTITY_ID: ENTITY_SWITCH, CONF_USE_DEFAULTS: "factory"} + ) assert switch._sun_light_settings.min_color_temp == 2000 # testing with "current" should not change things - await change_switch_settings(**{CONF_USE_DEFAULTS: "current"}) + await change_switch_settings( + **{ATTR_ENTITY_ID: ENTITY_SWITCH, CONF_USE_DEFAULTS: "current"} + ) assert switch._sun_light_settings.min_color_temp == 2000 # testing with "configuration" should revert back to 2500 - await change_switch_settings(**{CONF_USE_DEFAULTS: "configuration"}) + await change_switch_settings( + **{ATTR_ENTITY_ID: ENTITY_SWITCH, CONF_USE_DEFAULTS: "configuration"} + ) assert switch._sun_light_settings.min_color_temp == 2500 + + # testing with no switches or lights defined. + assert switch2._sun_light_settings.max_brightness == 100 + await change_switch_settings(**{CONF_MAX_BRIGHTNESS: 50}) + assert switch2._sun_light_settings.max_brightness == 50