From ffebd30033100acc15371a1a61dc42758dd65258 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 24 Jul 2025 20:26:08 -1000 Subject: [PATCH] [ruff] Enable SIM rules and fix code simplification violations (#9872) --- esphome/__main__.py | 4 +- esphome/components/api/client.py | 6 +- esphome/components/canbus/__init__.py | 5 +- esphome/components/esp32/__init__.py | 18 ++- .../components/esp32_ble_server/__init__.py | 22 +-- esphome/components/esp32_touch/__init__.py | 5 +- esphome/components/esp8266/__init__.py | 2 +- esphome/components/ethernet/__init__.py | 2 +- esphome/components/fastled_spi/light.py | 4 +- esphome/components/graph/__init__.py | 2 +- esphome/components/http_request/__init__.py | 5 +- esphome/components/hydreon_rgxx/sensor.py | 9 +- esphome/components/i2s_audio/__init__.py | 5 +- .../i2s_audio/microphone/__init__.py | 10 +- esphome/components/ili9xxx/display.py | 7 +- esphome/components/image/__init__.py | 10 +- esphome/components/improv_serial/__init__.py | 15 +- esphome/components/ina2xx_base/__init__.py | 7 +- esphome/components/ld2450/text_sensor.py | 9 +- esphome/components/light/effects.py | 2 +- esphome/components/logger/__init__.py | 15 +- esphome/components/lvgl/__init__.py | 5 +- esphome/components/matrix_keypad/__init__.py | 7 +- esphome/components/mixer/speaker/__init__.py | 9 +- esphome/components/mlx90393/sensor.py | 12 +- esphome/components/mpr121/__init__.py | 13 +- .../components/opentherm/number/__init__.py | 4 +- esphome/components/openthread/__init__.py | 5 +- .../components/packet_transport/__init__.py | 7 +- esphome/components/pulse_counter/sensor.py | 15 +- esphome/components/qspi_dbi/display.py | 5 +- esphome/components/qwiic_pir/binary_sensor.py | 5 +- esphome/components/remote_base/__init__.py | 11 +- .../components/resampler/speaker/__init__.py | 9 +- esphome/components/rpi_dpi_rgb/display.py | 4 +- .../speaker/media_player/__init__.py | 13 +- esphome/components/spi/__init__.py | 8 +- esphome/components/sprinkler/__init__.py | 8 +- esphome/components/ssd1306_base/__init__.py | 17 ++- esphome/components/st7701s/display.py | 4 +- esphome/components/substitutions/__init__.py | 17 +-- esphome/components/sun/__init__.py | 5 +- esphome/components/sx1509/__init__.py | 12 +- esphome/components/thermostat/climate.py | 8 +- esphome/components/time/__init__.py | 4 +- esphome/components/tuya/climate/__init__.py | 19 ++- esphome/components/tuya/number/__init__.py | 14 +- esphome/components/wifi/__init__.py | 4 +- esphome/config.py | 14 +- esphome/config_validation.py | 6 +- esphome/cpp_generator.py | 5 +- esphome/dashboard/dashboard.py | 5 +- esphome/espota2.py | 5 +- esphome/helpers.py | 4 +- esphome/log.py | 2 +- esphome/mqtt.py | 5 +- esphome/platformio_api.py | 8 +- esphome/util.py | 2 +- esphome/wizard.py | 5 +- esphome/writer.py | 12 +- esphome/yaml_util.py | 9 +- pyproject.toml | 11 +- script/api_protobuf/api_protobuf.py | 6 +- script/build_language_schema.py | 26 ++-- script/helpers.py | 8 +- tests/integration/conftest.py | 24 ++- tests/integration/test_api_custom_services.py | 144 +++++++++--------- tests/integration/test_device_id_in_state.py | 4 +- .../test_scheduler_defer_cancel.py | 12 +- tests/integration/test_scheduler_null_name.py | 56 +++---- .../test_scheduler_rapid_cancellation.py | 7 +- tests/script/test_determine_jobs.py | 44 +++--- 72 files changed, 400 insertions(+), 432 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 658aef4722..cf0fed2d67 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -119,9 +119,7 @@ def mqtt_logging_enabled(mqtt_config): return False if CONF_TOPIC not in log_topic: return False - if log_topic.get(CONF_LEVEL, None) == "NONE": - return False - return True + return log_topic.get(CONF_LEVEL, None) != "NONE" def get_port_type(port): diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index 2d4bc37c89..5239e07435 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -14,6 +14,8 @@ with warnings.catch_warnings(): from aioesphomeapi import APIClient, parse_log_message from aioesphomeapi.log_runner import async_run +import contextlib + from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__ from esphome.core import CORE @@ -66,7 +68,5 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None: def run_logs(config: dict[str, Any], address: str) -> None: """Run the logs command.""" - try: + with contextlib.suppress(KeyboardInterrupt): asyncio.run(async_run_logs(config, address)) - except KeyboardInterrupt: - pass diff --git a/esphome/components/canbus/__init__.py b/esphome/components/canbus/__init__.py index cdb57fd481..e1de1eb2f2 100644 --- a/esphome/components/canbus/__init__.py +++ b/esphome/components/canbus/__init__.py @@ -22,9 +22,8 @@ def validate_id(config): if CONF_CAN_ID in config: can_id = config[CONF_CAN_ID] id_ext = config[CONF_USE_EXTENDED_ID] - if not id_ext: - if can_id > 0x7FF: - raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)") + if not id_ext and can_id > 0x7FF: + raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)") return config diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 5dd2288076..f0a5517185 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -953,14 +953,16 @@ def _write_idf_component_yml(): # Called by writer.py def copy_files(): - if CORE.using_arduino: - if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]: - write_file_if_changed( - CORE.relative_build_path("partitions.csv"), - get_arduino_partition_csv( - CORE.platformio_options.get("board_upload.flash_size") - ), - ) + if ( + CORE.using_arduino + and "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES] + ): + write_file_if_changed( + CORE.relative_build_path("partitions.csv"), + get_arduino_partition_csv( + CORE.platformio_options.get("board_upload.flash_size") + ), + ) if CORE.using_esp_idf: _write_sdkconfig() _write_idf_component_yml() diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index 773445a1a7..19f466eb7b 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -140,20 +140,22 @@ VALUE_TYPES = { def validate_char_on_write(char_config): - if CONF_ON_WRITE in char_config: - if not char_config[CONF_WRITE] and not char_config[CONF_WRITE_NO_RESPONSE]: - raise cv.Invalid( - f"{CONF_ON_WRITE} requires the {CONF_WRITE} or {CONF_WRITE_NO_RESPONSE} property to be set" - ) + if ( + CONF_ON_WRITE in char_config + and not char_config[CONF_WRITE] + and not char_config[CONF_WRITE_NO_RESPONSE] + ): + raise cv.Invalid( + f"{CONF_ON_WRITE} requires the {CONF_WRITE} or {CONF_WRITE_NO_RESPONSE} property to be set" + ) return char_config def validate_descriptor(desc_config): - if CONF_ON_WRITE in desc_config: - if not desc_config[CONF_WRITE]: - raise cv.Invalid( - f"{CONF_ON_WRITE} requires the {CONF_WRITE} property to be set" - ) + if CONF_ON_WRITE in desc_config and not desc_config[CONF_WRITE]: + raise cv.Invalid( + f"{CONF_ON_WRITE} requires the {CONF_WRITE} property to be set" + ) if CONF_MAX_LENGTH not in desc_config: value = desc_config[CONF_VALUE][CONF_DATA] if cg.is_template(value): diff --git a/esphome/components/esp32_touch/__init__.py b/esphome/components/esp32_touch/__init__.py index 224e301683..b6cb19ebb1 100644 --- a/esphome/components/esp32_touch/__init__.py +++ b/esphome/components/esp32_touch/__init__.py @@ -294,9 +294,8 @@ async def to_code(config): ) ) - if get_esp32_variant() == VARIANT_ESP32: - if CONF_IIR_FILTER in config: - cg.add(touch.set_iir_filter(config[CONF_IIR_FILTER])) + if get_esp32_variant() == VARIANT_ESP32 and CONF_IIR_FILTER in config: + cg.add(touch.set_iir_filter(config[CONF_IIR_FILTER])) if get_esp32_variant() == VARIANT_ESP32S2 or get_esp32_variant() == VARIANT_ESP32S3: if CONF_FILTER_MODE in config: diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 0184c25965..33a4149571 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -245,7 +245,7 @@ async def to_code(config): if ver <= cv.Version(2, 3, 0): # No ld script support ld_script = None - if ver <= cv.Version(2, 4, 2): + elif ver <= cv.Version(2, 4, 2): # Old ld script path ld_script = ld_scripts[0] else: diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 619346b914..7a412a643d 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -112,7 +112,7 @@ def _is_framework_spi_polling_mode_supported(): return True if cv.Version(5, 3, 0) > framework_version >= cv.Version(5, 2, 1): return True - if cv.Version(5, 2, 0) > framework_version >= cv.Version(5, 1, 4): + if cv.Version(5, 2, 0) > framework_version >= cv.Version(5, 1, 4): # noqa: SIM103 return True return False if CORE.using_arduino: diff --git a/esphome/components/fastled_spi/light.py b/esphome/components/fastled_spi/light.py index 81d8c3b32a..e863d33846 100644 --- a/esphome/components/fastled_spi/light.py +++ b/esphome/components/fastled_spi/light.py @@ -55,9 +55,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): var = await fastled_base.new_fastled_light(config) - rgb_order = cg.RawExpression( - config[CONF_RGB_ORDER] if CONF_RGB_ORDER in config else "RGB" - ) + rgb_order = cg.RawExpression(config.get(CONF_RGB_ORDER, "RGB")) data_rate = None if CONF_DATA_RATE in config: diff --git a/esphome/components/graph/__init__.py b/esphome/components/graph/__init__.py index 6e8ba44bec..d72fe40dd2 100644 --- a/esphome/components/graph/__init__.py +++ b/esphome/components/graph/__init__.py @@ -116,7 +116,7 @@ GRAPH_SCHEMA = cv.Schema( def _relocate_fields_to_subfolder(config, subfolder, subschema): - fields = [k.schema for k in subschema.schema.keys()] + fields = [k.schema for k in subschema.schema] fields.remove(CONF_ID) if subfolder in config: # Ensure no ambiguous fields in base of config diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 0d32bc97c2..146458f53b 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -70,9 +70,8 @@ def validate_url(value): def validate_ssl_verification(config): error_message = "" - if CORE.is_esp32: - if not CORE.using_esp_idf and config[CONF_VERIFY_SSL]: - error_message = "ESPHome supports certificate verification only via ESP-IDF" + if CORE.is_esp32 and not CORE.using_esp_idf and config[CONF_VERIFY_SSL]: + error_message = "ESPHome supports certificate verification only via ESP-IDF" if CORE.is_rp2040 and config[CONF_VERIFY_SSL]: error_message = "ESPHome does not support certificate verification on RP2040" diff --git a/esphome/components/hydreon_rgxx/sensor.py b/esphome/components/hydreon_rgxx/sensor.py index 6c9f1e2877..f270b72e24 100644 --- a/esphome/components/hydreon_rgxx/sensor.py +++ b/esphome/components/hydreon_rgxx/sensor.py @@ -66,11 +66,10 @@ PROTOCOL_NAMES = { def _validate(config): for conf, models in SUPPORTED_OPTIONS.items(): - if conf in config: - if config[CONF_MODEL] not in models: - raise cv.Invalid( - f"{conf} is only available on {' and '.join(models)}, not {config[CONF_MODEL]}" - ) + if conf in config and config[CONF_MODEL] not in models: + raise cv.Invalid( + f"{conf} is only available on {' and '.join(models)}, not {config[CONF_MODEL]}" + ) return config diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index 575b914605..aa0a688fa0 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -243,10 +243,7 @@ def _final_validate(_): def use_legacy(): - if CORE.using_esp_idf: - if not _use_legacy_driver: - return False - return True + return not (CORE.using_esp_idf and not _use_legacy_driver) FINAL_VALIDATE_SCHEMA = _final_validate diff --git a/esphome/components/i2s_audio/microphone/__init__.py b/esphome/components/i2s_audio/microphone/__init__.py index 7bbb94f6e3..0f02ba6c3a 100644 --- a/esphome/components/i2s_audio/microphone/__init__.py +++ b/esphome/components/i2s_audio/microphone/__init__.py @@ -44,9 +44,8 @@ PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3] def _validate_esp32_variant(config): variant = esp32.get_esp32_variant() if config[CONF_ADC_TYPE] == "external": - if config[CONF_PDM]: - if variant not in PDM_VARIANTS: - raise cv.Invalid(f"{variant} does not support PDM") + if config[CONF_PDM] and variant not in PDM_VARIANTS: + raise cv.Invalid(f"{variant} does not support PDM") return config if config[CONF_ADC_TYPE] == "internal": if variant not in INTERNAL_ADC_VARIANTS: @@ -122,9 +121,8 @@ CONFIG_SCHEMA = cv.All( def _final_validate(config): - if not use_legacy(): - if config[CONF_ADC_TYPE] == "internal": - raise cv.Invalid("Internal ADC is only compatible with legacy i2s driver.") + if not use_legacy() and config[CONF_ADC_TYPE] == "internal": + raise cv.Invalid("Internal ADC is only compatible with legacy i2s driver.") FINAL_VALIDATE_SCHEMA = _final_validate diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 14b7f15218..9588bccd55 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -138,9 +138,10 @@ def _validate(config): ]: raise cv.Invalid("Selected model can't run on ESP8266.") - if model == "CUSTOM": - if CONF_INIT_SEQUENCE not in config or CONF_DIMENSIONS not in config: - raise cv.Invalid("CUSTOM model requires init_sequence and dimensions") + if model == "CUSTOM" and ( + CONF_INIT_SEQUENCE not in config or CONF_DIMENSIONS not in config + ): + raise cv.Invalid("CUSTOM model requires init_sequence and dimensions") return config diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index f6d8673a08..99646c9f7e 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -1,5 +1,6 @@ from __future__ import annotations +import contextlib import hashlib import io import logging @@ -174,9 +175,8 @@ class ImageGrayscale(ImageEncoder): b = 1 if self.invert_alpha: b ^= 0xFF - if self.transparency == CONF_ALPHA_CHANNEL: - if a != 0xFF: - b = a + if self.transparency == CONF_ALPHA_CHANNEL and a != 0xFF: + b = a self.data[self.index] = b self.index += 1 @@ -672,10 +672,8 @@ async def write_image(config, all_frames=False): invert_alpha = config[CONF_INVERT_ALPHA] frame_count = 1 if all_frames: - try: + with contextlib.suppress(AttributeError): frame_count = image.n_frames - except AttributeError: - pass if frame_count <= 1: _LOGGER.warning("Image file %s has no animation frames", path) diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index 544af212e0..568b200a85 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -27,14 +27,13 @@ def validate_logger(config): logger_conf = fv.full_config.get()[CONF_LOGGER] if logger_conf[CONF_BAUD_RATE] == 0: raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0") - if CORE.using_esp_idf: - if ( - logger_conf[CONF_HARDWARE_UART] == USB_CDC - and get_esp32_variant() == VARIANT_ESP32S3 - ): - raise cv.Invalid( - "improv_serial does not support the selected logger hardware_uart" - ) + if CORE.using_esp_idf and ( + logger_conf[CONF_HARDWARE_UART] == USB_CDC + and get_esp32_variant() == VARIANT_ESP32S3 + ): + raise cv.Invalid( + "improv_serial does not support the selected logger hardware_uart" + ) return config diff --git a/esphome/components/ina2xx_base/__init__.py b/esphome/components/ina2xx_base/__init__.py index 7c3d3c8899..ff70f217ec 100644 --- a/esphome/components/ina2xx_base/__init__.py +++ b/esphome/components/ina2xx_base/__init__.py @@ -78,11 +78,8 @@ def validate_model_config(config): model = config[CONF_MODEL] for key in config: - if key in SENSOR_MODEL_OPTIONS: - if model not in SENSOR_MODEL_OPTIONS[key]: - raise cv.Invalid( - f"Device model '{model}' does not support '{key}' sensor" - ) + if key in SENSOR_MODEL_OPTIONS and model not in SENSOR_MODEL_OPTIONS[key]: + raise cv.Invalid(f"Device model '{model}' does not support '{key}' sensor") tempco = config[CONF_TEMPERATURE_COEFFICIENT] if tempco > 0 and model not in ["INA228", "INA229"]: diff --git a/esphome/components/ld2450/text_sensor.py b/esphome/components/ld2450/text_sensor.py index 6c11024b89..89b6939a29 100644 --- a/esphome/components/ld2450/text_sensor.py +++ b/esphome/components/ld2450/text_sensor.py @@ -56,7 +56,8 @@ async def to_code(config): sens = await text_sensor.new_text_sensor(mac_address_config) cg.add(ld2450_component.set_mac_text_sensor(sens)) for n in range(MAX_TARGETS): - if direction_conf := config.get(f"target_{n + 1}"): - if direction_config := direction_conf.get(CONF_DIRECTION): - sens = await text_sensor.new_text_sensor(direction_config) - cg.add(ld2450_component.set_direction_text_sensor(n, sens)) + if (direction_conf := config.get(f"target_{n + 1}")) and ( + direction_config := direction_conf.get(CONF_DIRECTION) + ): + sens = await text_sensor.new_text_sensor(direction_config) + cg.add(ld2450_component.set_direction_text_sensor(n, sens)) diff --git a/esphome/components/light/effects.py b/esphome/components/light/effects.py index 67c318eb8e..6f878ff5f1 100644 --- a/esphome/components/light/effects.py +++ b/esphome/components/light/effects.py @@ -526,7 +526,7 @@ def validate_effects(allowed_effects): errors = [] names = set() for i, x in enumerate(value): - key = next(it for it in x.keys()) + key = next(it for it in x) if key not in allowed_effects: errors.append( cv.Invalid( diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index e79396da04..cb338df466 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -346,14 +346,13 @@ async def to_code(config): if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH): cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH") - if CORE.using_arduino: - if config[CONF_HARDWARE_UART] == USB_CDC: - cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=1") - if CORE.is_esp32 and get_esp32_variant() in ( - VARIANT_ESP32C3, - VARIANT_ESP32C6, - ): - cg.add_build_flag("-DARDUINO_USB_MODE=1") + if CORE.using_arduino and config[CONF_HARDWARE_UART] == USB_CDC: + cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=1") + if CORE.is_esp32 and get_esp32_variant() in ( + VARIANT_ESP32C3, + VARIANT_ESP32C6, + ): + cg.add_build_flag("-DARDUINO_USB_MODE=1") if CORE.using_esp_idf: if config[CONF_HARDWARE_UART] == USB_CDC: diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index b1879e6314..0cd65d298f 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -201,9 +201,8 @@ def final_validation(configs): multi_conf_validate(configs) global_config = full_config.get() for config in configs: - if pages := config.get(CONF_PAGES): - if all(p[df.CONF_SKIP] for p in pages): - raise cv.Invalid("At least one page must not be skipped") + if (pages := config.get(CONF_PAGES)) and all(p[df.CONF_SKIP] for p in pages): + raise cv.Invalid("At least one page must not be skipped") for display_id in config[df.CONF_DISPLAYS]: path = global_config.get_path_for_id(display_id)[:-1] display = global_config.get_config_for_path(path) diff --git a/esphome/components/matrix_keypad/__init__.py b/esphome/components/matrix_keypad/__init__.py index b2bcde98ec..f7a1d622a1 100644 --- a/esphome/components/matrix_keypad/__init__.py +++ b/esphome/components/matrix_keypad/__init__.py @@ -28,9 +28,10 @@ CONF_HAS_PULLDOWNS = "has_pulldowns" def check_keys(obj): - if CONF_KEYS in obj: - if len(obj[CONF_KEYS]) != len(obj[CONF_ROWS]) * len(obj[CONF_COLUMNS]): - raise cv.Invalid("The number of key codes must equal the number of buttons") + if CONF_KEYS in obj and len(obj[CONF_KEYS]) != len(obj[CONF_ROWS]) * len( + obj[CONF_COLUMNS] + ): + raise cv.Invalid("The number of key codes must equal the number of buttons") return obj diff --git a/esphome/components/mixer/speaker/__init__.py b/esphome/components/mixer/speaker/__init__.py index a451f2b7b4..46729f8eda 100644 --- a/esphome/components/mixer/speaker/__init__.py +++ b/esphome/components/mixer/speaker/__init__.py @@ -124,11 +124,10 @@ async def to_code(config): if task_stack_in_psram := config.get(CONF_TASK_STACK_IN_PSRAM): cg.add(var.set_task_stack_in_psram(task_stack_in_psram)) - if task_stack_in_psram: - if config[CONF_TASK_STACK_IN_PSRAM]: - esp32.add_idf_sdkconfig_option( - "CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True - ) + if task_stack_in_psram and config[CONF_TASK_STACK_IN_PSRAM]: + esp32.add_idf_sdkconfig_option( + "CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True + ) for speaker_config in config[CONF_SOURCE_SPEAKERS]: source_speaker = cg.new_Pvariable(speaker_config[CONF_ID]) diff --git a/esphome/components/mlx90393/sensor.py b/esphome/components/mlx90393/sensor.py index 372bb05bda..d93c379506 100644 --- a/esphome/components/mlx90393/sensor.py +++ b/esphome/components/mlx90393/sensor.py @@ -63,11 +63,13 @@ def _validate(config): raise cv.Invalid( f"{axis}: {CONF_RESOLUTION} cannot be {res} with {CONF_TEMPERATURE_COMPENSATION} enabled" ) - if config[CONF_HALLCONF] == 0xC: - if (config[CONF_OVERSAMPLING], config[CONF_FILTER]) in [(0, 0), (1, 0), (0, 1)]: - raise cv.Invalid( - f"{CONF_OVERSAMPLING}=={config[CONF_OVERSAMPLING]} and {CONF_FILTER}=={config[CONF_FILTER]} not allowed with {CONF_HALLCONF}=={config[CONF_HALLCONF]:#02x}" - ) + if config[CONF_HALLCONF] == 0xC and ( + config[CONF_OVERSAMPLING], + config[CONF_FILTER], + ) in [(0, 0), (1, 0), (0, 1)]: + raise cv.Invalid( + f"{CONF_OVERSAMPLING}=={config[CONF_OVERSAMPLING]} and {CONF_FILTER}=={config[CONF_FILTER]} not allowed with {CONF_HALLCONF}=={config[CONF_HALLCONF]:#02x}" + ) return config diff --git a/esphome/components/mpr121/__init__.py b/esphome/components/mpr121/__init__.py index b736a7e4f0..0bf9377275 100644 --- a/esphome/components/mpr121/__init__.py +++ b/esphome/components/mpr121/__init__.py @@ -56,12 +56,13 @@ def _final_validate(config): for binary_sensor in binary_sensors: if binary_sensor.get(CONF_MPR121_ID) == config[CONF_ID]: max_touch_channel = max(max_touch_channel, binary_sensor[CONF_CHANNEL]) - if max_touch_channel_in_config := config.get(CONF_MAX_TOUCH_CHANNEL): - if max_touch_channel != max_touch_channel_in_config: - raise cv.Invalid( - "Max touch channel must equal the highest binary sensor channel or be removed for auto calculation", - path=[CONF_MAX_TOUCH_CHANNEL], - ) + if ( + max_touch_channel_in_config := config.get(CONF_MAX_TOUCH_CHANNEL) + ) and max_touch_channel != max_touch_channel_in_config: + raise cv.Invalid( + "Max touch channel must equal the highest binary sensor channel or be removed for auto calculation", + path=[CONF_MAX_TOUCH_CHANNEL], + ) path = fconf.get_path_for_id(config[CONF_ID])[:-1] this_config = fconf.get_config_for_path(path) this_config[CONF_MAX_TOUCH_CHANNEL] = max_touch_channel diff --git a/esphome/components/opentherm/number/__init__.py b/esphome/components/opentherm/number/__init__.py index a65864647a..6dbc45f49b 100644 --- a/esphome/components/opentherm/number/__init__.py +++ b/esphome/components/opentherm/number/__init__.py @@ -25,9 +25,9 @@ async def new_openthermnumber(config: dict[str, Any]) -> cg.Pvariable: await cg.register_component(var, config) input.generate_setters(var, config) - if (initial_value := config.get(CONF_INITIAL_VALUE, None)) is not None: + if (initial_value := config.get(CONF_INITIAL_VALUE)) is not None: cg.add(var.set_initial_value(initial_value)) - if (restore_value := config.get(CONF_RESTORE_VALUE, None)) is not None: + if (restore_value := config.get(CONF_RESTORE_VALUE)) is not None: cg.add(var.set_restore_value(restore_value)) return var diff --git a/esphome/components/openthread/__init__.py b/esphome/components/openthread/__init__.py index 25e3153d1b..2f085ebaae 100644 --- a/esphome/components/openthread/__init__.py +++ b/esphome/components/openthread/__init__.py @@ -79,9 +79,8 @@ def set_sdkconfig_options(config): "CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}".lower() ) - if force_dataset := config.get(CONF_FORCE_DATASET): - if force_dataset: - cg.add_define("USE_OPENTHREAD_FORCE_DATASET") + if config.get(CONF_FORCE_DATASET): + cg.add_define("USE_OPENTHREAD_FORCE_DATASET") add_idf_sdkconfig_option("CONFIG_OPENTHREAD_DNS64_CLIENT", True) add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", True) diff --git a/esphome/components/packet_transport/__init__.py b/esphome/components/packet_transport/__init__.py index 99c1d824ca..bfb2bbc4f8 100644 --- a/esphome/components/packet_transport/__init__.py +++ b/esphome/components/packet_transport/__init__.py @@ -89,9 +89,10 @@ def validate_(config): raise cv.Invalid("No sensors or binary sensors to encrypt") elif config[CONF_ROLLING_CODE_ENABLE]: raise cv.Invalid("Rolling code requires an encryption key") - if config[CONF_PING_PONG_ENABLE]: - if not any(CONF_ENCRYPTION in p for p in config.get(CONF_PROVIDERS) or ()): - raise cv.Invalid("Ping-pong requires at least one encrypted provider") + if config[CONF_PING_PONG_ENABLE] and not any( + CONF_ENCRYPTION in p for p in config.get(CONF_PROVIDERS) or () + ): + raise cv.Invalid("Ping-pong requires at least one encrypted provider") return config diff --git a/esphome/components/pulse_counter/sensor.py b/esphome/components/pulse_counter/sensor.py index b330758000..dbf67fd2ad 100644 --- a/esphome/components/pulse_counter/sensor.py +++ b/esphome/components/pulse_counter/sensor.py @@ -49,12 +49,15 @@ def validate_internal_filter(value): [CONF_USE_PCNT], ) - if CORE.is_esp32 and use_pcnt: - if value.get(CONF_INTERNAL_FILTER).total_microseconds > 13: - raise cv.Invalid( - "Maximum internal filter value when using ESP32 hardware PCNT is 13us", - [CONF_INTERNAL_FILTER], - ) + if ( + CORE.is_esp32 + and use_pcnt + and value.get(CONF_INTERNAL_FILTER).total_microseconds > 13 + ): + raise cv.Invalid( + "Maximum internal filter value when using ESP32 hardware PCNT is 13us", + [CONF_INTERNAL_FILTER], + ) return value diff --git a/esphome/components/qspi_dbi/display.py b/esphome/components/qspi_dbi/display.py index 5b01bcc6ca..74d837a794 100644 --- a/esphome/components/qspi_dbi/display.py +++ b/esphome/components/qspi_dbi/display.py @@ -73,9 +73,8 @@ def map_sequence(value): def _validate(config): chip = DriverChip.chips[config[CONF_MODEL]] - if not chip.initsequence: - if CONF_INIT_SEQUENCE not in config: - raise cv.Invalid(f"{chip.name} model requires init_sequence") + if not chip.initsequence and CONF_INIT_SEQUENCE not in config: + raise cv.Invalid(f"{chip.name} model requires init_sequence") return config diff --git a/esphome/components/qwiic_pir/binary_sensor.py b/esphome/components/qwiic_pir/binary_sensor.py index 0a549ccb32..cd3eda5ac8 100644 --- a/esphome/components/qwiic_pir/binary_sensor.py +++ b/esphome/components/qwiic_pir/binary_sensor.py @@ -24,9 +24,8 @@ QwiicPIRComponent = qwiic_pir_ns.class_( def validate_no_debounce_unless_native(config): - if CONF_DEBOUNCE in config: - if config[CONF_DEBOUNCE_MODE] != "NATIVE": - raise cv.Invalid("debounce can only be set if debounce_mode is NATIVE") + if CONF_DEBOUNCE in config and config[CONF_DEBOUNCE_MODE] != "NATIVE": + raise cv.Invalid("debounce can only be set if debounce_mode is NATIVE") return config diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index fc824ef704..8163661c65 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -1062,12 +1062,11 @@ def validate_raw_alternating(value): last_negative = None for i, val in enumerate(value): this_negative = val < 0 - if i != 0: - if this_negative == last_negative: - raise cv.Invalid( - f"Values must alternate between being positive and negative, please see index {i} and {i + 1}", - [i], - ) + if i != 0 and this_negative == last_negative: + raise cv.Invalid( + f"Values must alternate between being positive and negative, please see index {i} and {i + 1}", + [i], + ) last_negative = this_negative return value diff --git a/esphome/components/resampler/speaker/__init__.py b/esphome/components/resampler/speaker/__init__.py index 9e9b32476f..def62547b2 100644 --- a/esphome/components/resampler/speaker/__init__.py +++ b/esphome/components/resampler/speaker/__init__.py @@ -90,11 +90,10 @@ async def to_code(config): if task_stack_in_psram := config.get(CONF_TASK_STACK_IN_PSRAM): cg.add(var.set_task_stack_in_psram(task_stack_in_psram)) - if task_stack_in_psram: - if config[CONF_TASK_STACK_IN_PSRAM]: - esp32.add_idf_sdkconfig_option( - "CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True - ) + if task_stack_in_psram and config[CONF_TASK_STACK_IN_PSRAM]: + esp32.add_idf_sdkconfig_option( + "CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True + ) cg.add(var.set_target_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) cg.add(var.set_target_sample_rate(config[CONF_SAMPLE_RATE])) diff --git a/esphome/components/rpi_dpi_rgb/display.py b/esphome/components/rpi_dpi_rgb/display.py index 556ee5eeb4..513ed8eb58 100644 --- a/esphome/components/rpi_dpi_rgb/display.py +++ b/esphome/components/rpi_dpi_rgb/display.py @@ -140,7 +140,6 @@ async def to_code(config): cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH])) cg.add(var.set_pclk_inverted(config[CONF_PCLK_INVERTED])) cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY])) - index = 0 dpins = [] if CONF_RED in config[CONF_DATA_PINS]: red_pins = config[CONF_DATA_PINS][CONF_RED] @@ -158,10 +157,9 @@ async def to_code(config): dpins = dpins[8:16] + dpins[0:8] else: dpins = config[CONF_DATA_PINS] - for pin in dpins: + for index, pin in enumerate(dpins): data_pin = await cg.gpio_pin_expression(pin) cg.add(var.add_data_pin(data_pin, index)) - index += 1 if enable_pin := config.get(CONF_ENABLE_PIN): enable = await cg.gpio_pin_expression(enable_pin) diff --git a/esphome/components/speaker/media_player/__init__.py b/esphome/components/speaker/media_player/__init__.py index dc2dae2ef1..16dcc855c3 100644 --- a/esphome/components/speaker/media_player/__init__.py +++ b/esphome/components/speaker/media_player/__init__.py @@ -204,13 +204,14 @@ def _validate_pipeline(config): def _validate_repeated_speaker(config): - if (announcement_config := config.get(CONF_ANNOUNCEMENT_PIPELINE)) and ( - media_config := config.get(CONF_MEDIA_PIPELINE) + if ( + (announcement_config := config.get(CONF_ANNOUNCEMENT_PIPELINE)) + and (media_config := config.get(CONF_MEDIA_PIPELINE)) + and announcement_config[CONF_SPEAKER] == media_config[CONF_SPEAKER] ): - if announcement_config[CONF_SPEAKER] == media_config[CONF_SPEAKER]: - raise cv.Invalid( - "The announcement and media pipelines cannot use the same speaker. Use the `mixer` speaker component to create two source speakers." - ) + raise cv.Invalid( + "The announcement and media pipelines cannot use the same speaker. Use the `mixer` speaker component to create two source speakers." + ) return config diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 065ccc2668..a436bc6dab 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -115,9 +115,7 @@ def get_target_platform(): def get_target_variant(): - return ( - CORE.data[KEY_ESP32][KEY_VARIANT] if KEY_VARIANT in CORE.data[KEY_ESP32] else "" - ) + return CORE.data[KEY_ESP32].get(KEY_VARIANT, "") # Get a list of available hardware interfaces based on target and variant. @@ -213,9 +211,7 @@ def validate_hw_pins(spi, index=-1): return False if sdo_pin_no not in pin_set[CONF_MOSI_PIN]: return False - if sdi_pin_no not in pin_set[CONF_MISO_PIN]: - return False - return True + return sdi_pin_no in pin_set[CONF_MISO_PIN] return False diff --git a/esphome/components/sprinkler/__init__.py b/esphome/components/sprinkler/__init__.py index 3c94d97739..2dccb6896a 100644 --- a/esphome/components/sprinkler/__init__.py +++ b/esphome/components/sprinkler/__init__.py @@ -130,11 +130,11 @@ def validate_sprinkler(config): if ( CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY in sprinkler_controller and CONF_VALVE_OPEN_DELAY not in sprinkler_controller + and sprinkler_controller[CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY] ): - if sprinkler_controller[CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY]: - raise cv.Invalid( - f"{CONF_VALVE_OPEN_DELAY} must be defined when {CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY} is enabled" - ) + raise cv.Invalid( + f"{CONF_VALVE_OPEN_DELAY} must be defined when {CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY} is enabled" + ) if ( CONF_REPEAT in sprinkler_controller diff --git a/esphome/components/ssd1306_base/__init__.py b/esphome/components/ssd1306_base/__init__.py index 6633b24607..9d397e396b 100644 --- a/esphome/components/ssd1306_base/__init__.py +++ b/esphome/components/ssd1306_base/__init__.py @@ -42,14 +42,15 @@ SSD1306_MODEL = cv.enum(MODELS, upper=True, space="_") def _validate(value): model = value[CONF_MODEL] - if model not in ("SSD1305_128X32", "SSD1305_128X64"): - # Contrast is default value (1.0) while brightness is not - # Indicates user is using old `brightness` option - if value[CONF_BRIGHTNESS] != 1.0 and value[CONF_CONTRAST] == 1.0: - raise cv.Invalid( - "SSD1306/SH1106 no longer accepts brightness option, " - 'please use "contrast" instead.' - ) + if ( + model not in ("SSD1305_128X32", "SSD1305_128X64") + and value[CONF_BRIGHTNESS] != 1.0 + and value[CONF_CONTRAST] == 1.0 + ): + raise cv.Invalid( + "SSD1306/SH1106 no longer accepts brightness option, " + 'please use "contrast" instead.' + ) return value diff --git a/esphome/components/st7701s/display.py b/esphome/components/st7701s/display.py index e2452a4c55..497740b8d2 100644 --- a/esphome/components/st7701s/display.py +++ b/esphome/components/st7701s/display.py @@ -189,7 +189,6 @@ async def to_code(config): cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH])) cg.add(var.set_pclk_inverted(config[CONF_PCLK_INVERTED])) cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY])) - index = 0 dpins = [] if CONF_RED in config[CONF_DATA_PINS]: red_pins = config[CONF_DATA_PINS][CONF_RED] @@ -207,10 +206,9 @@ async def to_code(config): dpins = dpins[8:16] + dpins[0:8] else: dpins = config[CONF_DATA_PINS] - for pin in dpins: + for index, pin in enumerate(dpins): data_pin = await cg.gpio_pin_expression(pin) cg.add(var.add_data_pin(data_pin, index)) - index += 1 if dc_pin := config.get(CONF_DC_PIN): dc = await cg.gpio_pin_expression(dc_pin) diff --git a/esphome/components/substitutions/__init__.py b/esphome/components/substitutions/__init__.py index e494529b9e..a96f56a045 100644 --- a/esphome/components/substitutions/__init__.py +++ b/esphome/components/substitutions/__init__.py @@ -49,15 +49,14 @@ def _expand_jinja(value, orig_value, path, jinja, ignore_missing): try: # Invoke the jinja engine to evaluate the expression. value, err = jinja.expand(value) - if err is not None: - if not ignore_missing and "password" not in path: - _LOGGER.warning( - "Found '%s' (see %s) which looks like an expression," - " but could not resolve all the variables: %s", - value, - "->".join(str(x) for x in path), - err.message, - ) + if err is not None and not ignore_missing and "password" not in path: + _LOGGER.warning( + "Found '%s' (see %s) which looks like an expression," + " but could not resolve all the variables: %s", + value, + "->".join(str(x) for x in path), + err.message, + ) except ( TemplateError, TemplateRuntimeError, diff --git a/esphome/components/sun/__init__.py b/esphome/components/sun/__init__.py index 5a2a39c427..c065a82958 100644 --- a/esphome/components/sun/__init__.py +++ b/esphome/components/sun/__init__.py @@ -1,3 +1,4 @@ +import contextlib import re from esphome import automation @@ -41,12 +42,10 @@ ELEVATION_MAP = { def elevation(value): if isinstance(value, str): - try: + with contextlib.suppress(cv.Invalid): value = ELEVATION_MAP[ cv.one_of(*ELEVATION_MAP, lower=True, space="_")(value) ] - except cv.Invalid: - pass value = cv.angle(value) return cv.float_range(min=-180, max=180)(value) diff --git a/esphome/components/sx1509/__init__.py b/esphome/components/sx1509/__init__.py index f1b08a505a..67dc924903 100644 --- a/esphome/components/sx1509/__init__.py +++ b/esphome/components/sx1509/__init__.py @@ -41,11 +41,13 @@ SX1509KeyTrigger = sx1509_ns.class_( def check_keys(config): - if CONF_KEYS in config: - if len(config[CONF_KEYS]) != config[CONF_KEY_ROWS] * config[CONF_KEY_COLUMNS]: - raise cv.Invalid( - "The number of key codes must equal the number of rows * columns" - ) + if ( + CONF_KEYS in config + and len(config[CONF_KEYS]) != config[CONF_KEY_ROWS] * config[CONF_KEY_COLUMNS] + ): + raise cv.Invalid( + "The number of key codes must equal the number of rows * columns" + ) return config diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 0314d877a3..5d0d9442e8 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -477,11 +477,11 @@ def validate_thermostat(config): if ( CONF_ON_BOOT_RESTORE_FROM in config and config[CONF_ON_BOOT_RESTORE_FROM] is OnBootRestoreFrom.DEFAULT_PRESET + and CONF_DEFAULT_PRESET not in config ): - if CONF_DEFAULT_PRESET not in config: - raise cv.Invalid( - f"{CONF_DEFAULT_PRESET} must be defined to use {CONF_ON_BOOT_RESTORE_FROM} in DEFAULT_PRESET mode" - ) + raise cv.Invalid( + f"{CONF_DEFAULT_PRESET} must be defined to use {CONF_ON_BOOT_RESTORE_FROM} in DEFAULT_PRESET mode" + ) if config[CONF_FAN_WITH_COOLING] is True and CONF_FAN_ONLY_ACTION not in config: raise cv.Invalid( diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index 58d35c4baf..63d4ba17f2 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -236,7 +236,7 @@ def validate_time_at(value): def validate_cron_keys(value): if CONF_CRON in value: - for key in value.keys(): + for key in value: if key in CRON_KEYS: raise cv.Invalid(f"Cannot use option {key} when cron: is specified.") if CONF_AT in value: @@ -246,7 +246,7 @@ def validate_cron_keys(value): value.update(cron_) return value if CONF_AT in value: - for key in value.keys(): + for key in value: if key in CRON_KEYS: raise cv.Invalid(f"Cannot use option {key} when at: is specified.") at_ = value[CONF_AT] diff --git a/esphome/components/tuya/climate/__init__.py b/esphome/components/tuya/climate/__init__.py index 4dbdf07651..cefe5d5a4b 100644 --- a/esphome/components/tuya/climate/__init__.py +++ b/esphome/components/tuya/climate/__init__.py @@ -46,16 +46,15 @@ TuyaClimate = tuya_ns.class_("TuyaClimate", climate.Climate, cg.Component) def validate_temperature_multipliers(value): - if CONF_TEMPERATURE_MULTIPLIER in value: - if ( - CONF_CURRENT_TEMPERATURE_MULTIPLIER in value - or CONF_TARGET_TEMPERATURE_MULTIPLIER in value - ): - raise cv.Invalid( - f"Cannot have {CONF_TEMPERATURE_MULTIPLIER} at the same time as " - f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} and " - f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" - ) + if CONF_TEMPERATURE_MULTIPLIER in value and ( + CONF_CURRENT_TEMPERATURE_MULTIPLIER in value + or CONF_TARGET_TEMPERATURE_MULTIPLIER in value + ): + raise cv.Invalid( + f"Cannot have {CONF_TEMPERATURE_MULTIPLIER} at the same time as " + f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} and " + f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" + ) if ( CONF_CURRENT_TEMPERATURE_MULTIPLIER in value and CONF_TARGET_TEMPERATURE_MULTIPLIER not in value diff --git a/esphome/components/tuya/number/__init__.py b/esphome/components/tuya/number/__init__.py index bd57c8be14..7a61e69c5c 100644 --- a/esphome/components/tuya/number/__init__.py +++ b/esphome/components/tuya/number/__init__.py @@ -34,12 +34,14 @@ def validate_min_max(config): min_value = config[CONF_MIN_VALUE] if max_value <= min_value: raise cv.Invalid("max_value must be greater than min_value") - if hidden_config := config.get(CONF_DATAPOINT_HIDDEN): - if (initial_value := hidden_config.get(CONF_INITIAL_VALUE, None)) is not None: - if (initial_value > max_value) or (initial_value < min_value): - raise cv.Invalid( - f"{CONF_INITIAL_VALUE} must be a value between {CONF_MAX_VALUE} and {CONF_MIN_VALUE}" - ) + if ( + (hidden_config := config.get(CONF_DATAPOINT_HIDDEN)) + and (initial_value := hidden_config.get(CONF_INITIAL_VALUE, None)) is not None + and ((initial_value > max_value) or (initial_value < min_value)) + ): + raise cv.Invalid( + f"{CONF_INITIAL_VALUE} must be a value between {CONF_MAX_VALUE} and {CONF_MIN_VALUE}" + ) return config diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 61f37556ba..8cb784233f 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -442,9 +442,7 @@ async def to_code(config): if CORE.is_esp8266: cg.add_library("ESP8266WiFi", None) - elif CORE.is_esp32 and CORE.using_arduino: - cg.add_library("WiFi", None) - elif CORE.is_rp2040: + elif (CORE.is_esp32 and CORE.using_arduino) or CORE.is_rp2040: cg.add_library("WiFi", None) if CORE.is_esp32 and CORE.using_esp_idf: diff --git a/esphome/config.py b/esphome/config.py index c4aa9aea24..670cbe7233 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -198,10 +198,7 @@ class Config(OrderedDict, fv.FinalValidateConfig): self.output_paths.remove((path, domain)) def is_in_error_path(self, path: ConfigPath) -> bool: - for err in self.errors: - if _path_begins_with(err.path, path): - return True - return False + return any(_path_begins_with(err.path, path) for err in self.errors) def set_by_path(self, path, value): conf = self @@ -224,7 +221,7 @@ class Config(OrderedDict, fv.FinalValidateConfig): for index, path_item in enumerate(path): try: if path_item in data: - key_data = [x for x in data.keys() if x == path_item][0] + key_data = [x for x in data if x == path_item][0] if isinstance(key_data, ESPHomeDataBase): doc_range = key_data.esp_range if get_key and index == len(path) - 1: @@ -1081,7 +1078,7 @@ def dump_dict( ret += "{}" multiline = False - for k in conf.keys(): + for k in conf: path_ = path + [k] error = config.get_error_for_path(path_) if error is not None: @@ -1097,10 +1094,7 @@ def dump_dict( msg = f"\n{indent(msg)}" if inf is not None: - if m: - msg = f" {inf}{msg}" - else: - msg = f"{msg} {inf}" + msg = f" {inf}{msg}" if m else f"{msg} {inf}" ret += f"{st + msg}\n" elif isinstance(conf, str): if is_secret(conf): diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 756464b563..1a4976e235 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -2,7 +2,7 @@ from __future__ import annotations -from contextlib import contextmanager +from contextlib import contextmanager, suppress from dataclasses import dataclass from datetime import datetime from ipaddress import ( @@ -2113,10 +2113,8 @@ def require_esphome_version(year, month, patch): @contextmanager def suppress_invalid(): - try: + with suppress(vol.Invalid): yield - except vol.Invalid: - pass GIT_SCHEMA = Schema( diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 060dd36f8f..72aadcb139 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -1037,10 +1037,7 @@ class MockObjClass(MockObj): def inherits_from(self, other: "MockObjClass") -> bool: if str(self) == str(other): return True - for parent in self._parents: - if str(parent) == str(other): - return True - return False + return any(str(parent) == str(other) for parent in self._parents) def template(self, *args: SafeExpType) -> "MockObjClass": if len(args) != 1 or not isinstance(args[0], TemplateArguments): diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 9de2d39ce2..81c10763e7 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio from asyncio import events from concurrent.futures import ThreadPoolExecutor +import contextlib import logging import os import socket @@ -125,10 +126,8 @@ def start_dashboard(args) -> None: asyncio.set_event_loop_policy(DashboardEventLoopPolicy(settings.verbose)) - try: + with contextlib.suppress(KeyboardInterrupt): asyncio.run(async_start(args)) - except KeyboardInterrupt: - pass async def async_start(args) -> None: diff --git a/esphome/espota2.py b/esphome/espota2.py index 4f2a08fb4a..279bafee8e 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -88,10 +88,7 @@ def recv_decode(sock, amount, decode=True): def receive_exactly(sock, amount, msg, expect, decode=True): - if decode: - data = [] - else: - data = b"" + data = [] if decode else b"" try: data += recv_decode(sock, 1, decode=decode) diff --git a/esphome/helpers.py b/esphome/helpers.py index bf0e3b5cf7..d1f3080e34 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -96,9 +96,7 @@ def cpp_string_escape(string, encoding="utf-8"): def _should_escape(byte: int) -> bool: if not 32 <= byte < 127: return True - if byte in (ord("\\"), ord('"')): - return True - return False + return byte in (ord("\\"), ord('"')) if isinstance(string, str): string = string.encode(encoding) diff --git a/esphome/log.py b/esphome/log.py index 0e91eb32c2..8831b1b2b3 100644 --- a/esphome/log.py +++ b/esphome/log.py @@ -61,7 +61,7 @@ class ESPHomeLogFormatter(logging.Formatter): }.get(record.levelname, "") message = f"{prefix}{formatted}{AnsiStyle.RESET_ALL.value}" if CORE.dashboard: - try: + try: # noqa: SIM105 message = message.replace("\033", "\\033") except UnicodeEncodeError: pass diff --git a/esphome/mqtt.py b/esphome/mqtt.py index a420b5ba7f..acfa8a0926 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -1,3 +1,4 @@ +import contextlib from datetime import datetime import hashlib import json @@ -52,10 +53,8 @@ def initialize( client = prepare( config, subscriptions, on_message, on_connect, username, password, client_id ) - try: + with contextlib.suppress(KeyboardInterrupt): client.loop_forever() - except KeyboardInterrupt: - pass return 0 diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index e34ac028f8..7415ec9794 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -131,9 +131,11 @@ def _load_idedata(config): temp_idedata = Path(CORE.relative_internal_path("idedata", f"{CORE.name}.json")) changed = False - if not platformio_ini.is_file() or not temp_idedata.is_file(): - changed = True - elif platformio_ini.stat().st_mtime >= temp_idedata.stat().st_mtime: + if ( + not platformio_ini.is_file() + or not temp_idedata.is_file() + or platformio_ini.stat().st_mtime >= temp_idedata.stat().st_mtime + ): changed = True if not changed: diff --git a/esphome/util.py b/esphome/util.py index 79cb630200..3b346371bc 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -59,7 +59,7 @@ def safe_print(message="", end="\n"): from esphome.core import CORE if CORE.dashboard: - try: + try: # noqa: SIM105 message = message.replace("\033", "\\033") except UnicodeEncodeError: pass diff --git a/esphome/wizard.py b/esphome/wizard.py index 6f7cbd1ff4..8602e90222 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -116,10 +116,7 @@ def wizard_file(**kwargs): kwargs["fallback_name"] = ap_name kwargs["fallback_psk"] = "".join(random.choice(letters) for _ in range(12)) - if kwargs.get("friendly_name"): - base = BASE_CONFIG_FRIENDLY - else: - base = BASE_CONFIG + base = BASE_CONFIG_FRIENDLY if kwargs.get("friendly_name") else BASE_CONFIG config = base.format(**kwargs) diff --git a/esphome/writer.py b/esphome/writer.py index d2ec112ec8..2c2e00b513 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -86,21 +86,17 @@ def storage_should_clean(old: StorageJSON, new: StorageJSON) -> bool: if old.src_version != new.src_version: return True - if old.build_path != new.build_path: - return True - - return False + return old.build_path != new.build_path def storage_should_update_cmake_cache(old: StorageJSON, new: StorageJSON) -> bool: if ( old.loaded_integrations != new.loaded_integrations or old.loaded_platforms != new.loaded_platforms - ): - if new.core_platform == PLATFORM_ESP32: - from esphome.components.esp32 import FRAMEWORK_ESP_IDF + ) and new.core_platform == PLATFORM_ESP32: + from esphome.components.esp32 import FRAMEWORK_ESP_IDF - return new.framework == FRAMEWORK_ESP_IDF + return new.framework == FRAMEWORK_ESP_IDF return False diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index e52fc9e788..33a56fc158 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -56,9 +56,12 @@ class ESPHomeDataBase: def from_node(self, node): # pylint: disable=attribute-defined-outside-init self._esp_range = DocumentRange.from_marks(node.start_mark, node.end_mark) - if isinstance(node, yaml.ScalarNode): - if node.style is not None and node.style in "|>": - self._content_offset = 1 + if ( + isinstance(node, yaml.ScalarNode) + and node.style is not None + and node.style in "|>" + ): + self._content_offset = 1 def from_database(self, database): # pylint: disable=attribute-defined-outside-init diff --git a/pyproject.toml b/pyproject.toml index 5d48779ad5..971b9fbf74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,11 +111,12 @@ exclude = ['generated'] [tool.ruff.lint] select = [ - "E", # pycodestyle - "F", # pyflakes/autoflake - "I", # isort - "PL", # pylint - "UP", # pyupgrade + "E", # pycodestyle + "F", # pyflakes/autoflake + "I", # isort + "PL", # pylint + "SIM", # flake8-simplify + "UP", # pyupgrade ] ignore = [ diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 424565a893..92c85d2366 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -61,9 +61,7 @@ def indent_list(text: str, padding: str = " ") -> list[str]: """Indent each line of the given text with the specified padding.""" lines = [] for line in text.splitlines(): - if line == "": - p = "" - elif line.startswith("#ifdef") or line.startswith("#endif"): + if line == "" or line.startswith("#ifdef") or line.startswith("#endif"): p = "" else: p = padding @@ -2385,7 +2383,7 @@ static const char *const TAG = "api.service"; needs_conn = get_opt(m, pb.needs_setup_connection, True) needs_auth = get_opt(m, pb.needs_authentication, True) - ifdef = message_ifdef_map.get(inp, ifdefs.get(inp, None)) + ifdef = message_ifdef_map.get(inp, ifdefs.get(inp)) if ifdef is not None: hpp += f"#ifdef {ifdef}\n" diff --git a/script/build_language_schema.py b/script/build_language_schema.py index 960c35ca0f..c114d15315 100755 --- a/script/build_language_schema.py +++ b/script/build_language_schema.py @@ -71,11 +71,13 @@ def get_component_names(): skip_components = [] for d in os.listdir(CORE_COMPONENTS_PATH): - if not d.startswith("__") and os.path.isdir( - os.path.join(CORE_COMPONENTS_PATH, d) + if ( + not d.startswith("__") + and os.path.isdir(os.path.join(CORE_COMPONENTS_PATH, d)) + and d not in component_names + and d not in skip_components ): - if d not in component_names and d not in skip_components: - component_names.append(d) + component_names.append(d) return sorted(component_names) @@ -139,11 +141,10 @@ def register_module_schemas(key, module, manifest=None): for name, schema in module_schemas(module): register_known_schema(key, name, schema) - if manifest: + if manifest and manifest.multi_conf and S_CONFIG_SCHEMA in output[key][S_SCHEMAS]: # Multi conf should allow list of components # not sure about 2nd part of the if, might be useless config (e.g. as3935) - if manifest.multi_conf and S_CONFIG_SCHEMA in output[key][S_SCHEMAS]: - output[key][S_SCHEMAS][S_CONFIG_SCHEMA]["is_list"] = True + output[key][S_SCHEMAS][S_CONFIG_SCHEMA]["is_list"] = True def register_known_schema(module, name, schema): @@ -230,7 +231,7 @@ def add_module_registries(domain, module): reg_type = attr_name.partition("_")[0].lower() found_registries[repr(attr_obj)] = f"{domain}.{reg_type}" - for name in attr_obj.keys(): + for name in attr_obj: if "." not in name: reg_entry_name = name else: @@ -700,7 +701,7 @@ def is_convertible_schema(schema): if repr(schema) in ejs.registry_schemas: return True if isinstance(schema, dict): - for k in schema.keys(): + for k in schema: if isinstance(k, (cv.Required, cv.Optional)): return True return False @@ -818,7 +819,7 @@ def convert(schema, config_var, path): elif schema_type == "automation": extra_schema = None config_var[S_TYPE] = "trigger" - if automation.AUTOMATION_SCHEMA == ejs.extended_schemas[repr(data)][0]: + if ejs.extended_schemas[repr(data)][0] == automation.AUTOMATION_SCHEMA: extra_schema = ejs.extended_schemas[repr(data)][1] if ( extra_schema is not None and len(extra_schema) > 1 @@ -926,9 +927,8 @@ def convert(schema, config_var, path): config = convert_config(schema_type, path + "/type_" + schema_key) types[schema_key] = config["schema"] - elif DUMP_UNKNOWN: - if S_TYPE not in config_var: - config_var["unknown"] = repr_schema + elif DUMP_UNKNOWN and S_TYPE not in config_var: + config_var["unknown"] = repr_schema if DUMP_PATH: config_var["path"] = path diff --git a/script/helpers.py b/script/helpers.py index 9032451b4f..4903521e2d 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -365,9 +365,11 @@ def load_idedata(environment: str) -> dict[str, Any]: platformio_ini = Path(root_path) / "platformio.ini" temp_idedata = Path(temp_folder) / f"idedata-{environment}.json" changed = False - if not platformio_ini.is_file() or not temp_idedata.is_file(): - changed = True - elif platformio_ini.stat().st_mtime >= temp_idedata.stat().st_mtime: + if ( + not platformio_ini.is_file() + or not temp_idedata.is_file() + or platformio_ini.stat().st_mtime >= temp_idedata.stat().st_mtime + ): changed = True if "idf" in environment: diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 6e2f398f49..46eb6c88e2 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -183,19 +183,17 @@ async def yaml_config(request: pytest.FixtureRequest, unused_tcp_port: int) -> s content = content.replace("api:", f"api:\n port: {unused_tcp_port}") # Add debug build flags for integration tests to enable assertions - if "esphome:" in content: - # Check if platformio_options already exists - if "platformio_options:" not in content: - # Add platformio_options with debug flags after esphome: - content = content.replace( - "esphome:", - "esphome:\n" - " # Enable assertions for integration tests\n" - " platformio_options:\n" - " build_flags:\n" - ' - "-DDEBUG" # Enable assert() statements\n' - ' - "-g" # Add debug symbols', - ) + if "esphome:" in content and "platformio_options:" not in content: + # Add platformio_options with debug flags after esphome: + content = content.replace( + "esphome:", + "esphome:\n" + " # Enable assertions for integration tests\n" + " platformio_options:\n" + " build_flags:\n" + ' - "-DDEBUG" # Enable assert() statements\n' + ' - "-g" # Add debug symbols', + ) return content diff --git a/tests/integration/test_api_custom_services.py b/tests/integration/test_api_custom_services.py index 2862dcc900..9ae4cdcb5d 100644 --- a/tests/integration/test_api_custom_services.py +++ b/tests/integration/test_api_custom_services.py @@ -59,86 +59,86 @@ async def test_api_custom_services( custom_arrays_future.set_result(True) # Run with log monitoring - async with run_compiled(yaml_config, line_callback=check_output): - async with api_client_connected() as client: - # Verify device info - device_info = await client.device_info() - assert device_info is not None - assert device_info.name == "api-custom-services-test" + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "api-custom-services-test" - # List services - _, services = await client.list_entities_services() + # List services + _, services = await client.list_entities_services() - # Should have 4 services: 1 YAML + 3 CustomAPIDevice - assert len(services) == 4, f"Expected 4 services, found {len(services)}" + # Should have 4 services: 1 YAML + 3 CustomAPIDevice + assert len(services) == 4, f"Expected 4 services, found {len(services)}" - # Find our services - yaml_service: UserService | None = None - custom_service: UserService | None = None - custom_args_service: UserService | None = None - custom_arrays_service: UserService | None = None + # Find our services + yaml_service: UserService | None = None + custom_service: UserService | None = None + custom_args_service: UserService | None = None + custom_arrays_service: UserService | None = None - for service in services: - if service.name == "test_yaml_service": - yaml_service = service - elif service.name == "custom_test_service": - custom_service = service - elif service.name == "custom_service_with_args": - custom_args_service = service - elif service.name == "custom_service_with_arrays": - custom_arrays_service = service + for service in services: + if service.name == "test_yaml_service": + yaml_service = service + elif service.name == "custom_test_service": + custom_service = service + elif service.name == "custom_service_with_args": + custom_args_service = service + elif service.name == "custom_service_with_arrays": + custom_arrays_service = service - assert yaml_service is not None, "test_yaml_service not found" - assert custom_service is not None, "custom_test_service not found" - assert custom_args_service is not None, "custom_service_with_args not found" - assert custom_arrays_service is not None, ( - "custom_service_with_arrays not found" - ) + assert yaml_service is not None, "test_yaml_service not found" + assert custom_service is not None, "custom_test_service not found" + assert custom_args_service is not None, "custom_service_with_args not found" + assert custom_arrays_service is not None, "custom_service_with_arrays not found" - # Test YAML service - client.execute_service(yaml_service, {}) - await asyncio.wait_for(yaml_service_future, timeout=5.0) + # Test YAML service + client.execute_service(yaml_service, {}) + await asyncio.wait_for(yaml_service_future, timeout=5.0) - # Test simple CustomAPIDevice service - client.execute_service(custom_service, {}) - await asyncio.wait_for(custom_service_future, timeout=5.0) + # Test simple CustomAPIDevice service + client.execute_service(custom_service, {}) + await asyncio.wait_for(custom_service_future, timeout=5.0) - # Verify custom_args_service arguments - assert len(custom_args_service.args) == 4 - arg_types = {arg.name: arg.type for arg in custom_args_service.args} - assert arg_types["arg_string"] == UserServiceArgType.STRING - assert arg_types["arg_int"] == UserServiceArgType.INT - assert arg_types["arg_bool"] == UserServiceArgType.BOOL - assert arg_types["arg_float"] == UserServiceArgType.FLOAT + # Verify custom_args_service arguments + assert len(custom_args_service.args) == 4 + arg_types = {arg.name: arg.type for arg in custom_args_service.args} + assert arg_types["arg_string"] == UserServiceArgType.STRING + assert arg_types["arg_int"] == UserServiceArgType.INT + assert arg_types["arg_bool"] == UserServiceArgType.BOOL + assert arg_types["arg_float"] == UserServiceArgType.FLOAT - # Test CustomAPIDevice service with arguments - client.execute_service( - custom_args_service, - { - "arg_string": "test_string", - "arg_int": 456, - "arg_bool": True, - "arg_float": 78.9, - }, - ) - await asyncio.wait_for(custom_args_future, timeout=5.0) + # Test CustomAPIDevice service with arguments + client.execute_service( + custom_args_service, + { + "arg_string": "test_string", + "arg_int": 456, + "arg_bool": True, + "arg_float": 78.9, + }, + ) + await asyncio.wait_for(custom_args_future, timeout=5.0) - # Verify array service arguments - assert len(custom_arrays_service.args) == 4 - array_arg_types = {arg.name: arg.type for arg in custom_arrays_service.args} - assert array_arg_types["bool_array"] == UserServiceArgType.BOOL_ARRAY - assert array_arg_types["int_array"] == UserServiceArgType.INT_ARRAY - assert array_arg_types["float_array"] == UserServiceArgType.FLOAT_ARRAY - assert array_arg_types["string_array"] == UserServiceArgType.STRING_ARRAY + # Verify array service arguments + assert len(custom_arrays_service.args) == 4 + array_arg_types = {arg.name: arg.type for arg in custom_arrays_service.args} + assert array_arg_types["bool_array"] == UserServiceArgType.BOOL_ARRAY + assert array_arg_types["int_array"] == UserServiceArgType.INT_ARRAY + assert array_arg_types["float_array"] == UserServiceArgType.FLOAT_ARRAY + assert array_arg_types["string_array"] == UserServiceArgType.STRING_ARRAY - # Test CustomAPIDevice service with arrays - client.execute_service( - custom_arrays_service, - { - "bool_array": [True, False], - "int_array": [1, 2, 3], - "float_array": [1.1, 2.2], - "string_array": ["hello", "world"], - }, - ) - await asyncio.wait_for(custom_arrays_future, timeout=5.0) + # Test CustomAPIDevice service with arrays + client.execute_service( + custom_arrays_service, + { + "bool_array": [True, False], + "int_array": [1, 2, 3], + "float_array": [1.1, 2.2], + "string_array": ["hello", "world"], + }, + ) + await asyncio.wait_for(custom_arrays_future, timeout=5.0) diff --git a/tests/integration/test_device_id_in_state.py b/tests/integration/test_device_id_in_state.py index fb61569e59..51088bcbf7 100644 --- a/tests/integration/test_device_id_in_state.py +++ b/tests/integration/test_device_id_in_state.py @@ -47,9 +47,7 @@ async def test_device_id_in_state( entity_device_mapping[entity.key] = device_ids["Humidity Monitor"] elif entity.name == "Motion Detected": entity_device_mapping[entity.key] = device_ids["Motion Sensor"] - elif entity.name == "Temperature Monitor Power": - entity_device_mapping[entity.key] = device_ids["Temperature Monitor"] - elif entity.name == "Temperature Status": + elif entity.name in {"Temperature Monitor Power", "Temperature Status"}: entity_device_mapping[entity.key] = device_ids["Temperature Monitor"] elif entity.name == "Motion Light": entity_device_mapping[entity.key] = device_ids["Motion Sensor"] diff --git a/tests/integration/test_scheduler_defer_cancel.py b/tests/integration/test_scheduler_defer_cancel.py index 7bce0eda54..34c46bab82 100644 --- a/tests/integration/test_scheduler_defer_cancel.py +++ b/tests/integration/test_scheduler_defer_cancel.py @@ -70,11 +70,13 @@ async def test_scheduler_defer_cancel( test_complete_future.set_result(True) return - if state.key == test_result_entity.key and not test_result_future.done(): - # Event type should be "defer_executed_X" where X is the defer number - if state.event_type.startswith("defer_executed_"): - defer_num = int(state.event_type.split("_")[-1]) - test_result_future.set_result(defer_num) + if ( + state.key == test_result_entity.key + and not test_result_future.done() + and state.event_type.startswith("defer_executed_") + ): + defer_num = int(state.event_type.split("_")[-1]) + test_result_future.set_result(defer_num) client.subscribe_states(on_state) diff --git a/tests/integration/test_scheduler_null_name.py b/tests/integration/test_scheduler_null_name.py index 66e25d4a11..75864ea2d2 100644 --- a/tests/integration/test_scheduler_null_name.py +++ b/tests/integration/test_scheduler_null_name.py @@ -27,33 +27,33 @@ async def test_scheduler_null_name( if not test_complete_future.done() and test_complete_pattern.search(line): test_complete_future.set_result(True) - async with run_compiled(yaml_config, line_callback=check_output): - async with api_client_connected() as client: - # Verify we can connect - device_info = await client.device_info() - assert device_info is not None - assert device_info.name == "scheduler-null-name" + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Verify we can connect + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "scheduler-null-name" - # List services - _, services = await asyncio.wait_for( - client.list_entities_services(), timeout=5.0 + # List services + _, services = await asyncio.wait_for( + client.list_entities_services(), timeout=5.0 + ) + + # Find our test service + test_null_name_service = next( + (s for s in services if s.name == "test_null_name"), None + ) + assert test_null_name_service is not None, "test_null_name service not found" + + # Execute the test + client.execute_service(test_null_name_service, {}) + + # Wait for test completion + try: + await asyncio.wait_for(test_complete_future, timeout=10.0) + except TimeoutError: + pytest.fail( + "Test did not complete within timeout - likely crashed due to NULL name" ) - - # Find our test service - test_null_name_service = next( - (s for s in services if s.name == "test_null_name"), None - ) - assert test_null_name_service is not None, ( - "test_null_name service not found" - ) - - # Execute the test - client.execute_service(test_null_name_service, {}) - - # Wait for test completion - try: - await asyncio.wait_for(test_complete_future, timeout=10.0) - except TimeoutError: - pytest.fail( - "Test did not complete within timeout - likely crashed due to NULL name" - ) diff --git a/tests/integration/test_scheduler_rapid_cancellation.py b/tests/integration/test_scheduler_rapid_cancellation.py index 6b6277c752..1b7da32aaa 100644 --- a/tests/integration/test_scheduler_rapid_cancellation.py +++ b/tests/integration/test_scheduler_rapid_cancellation.py @@ -61,9 +61,10 @@ async def test_scheduler_rapid_cancellation( elif "Total executed:" in line: if match := re.search(r"Total executed: (\d+)", line): test_stats["final_executed"] = int(match.group(1)) - elif "Implicit cancellations (replaced):" in line: - if match := re.search(r"Implicit cancellations \(replaced\): (\d+)", line): - test_stats["final_implicit_cancellations"] = int(match.group(1)) + elif "Implicit cancellations (replaced):" in line and ( + match := re.search(r"Implicit cancellations \(replaced\): (\d+)", line) + ): + test_stats["final_implicit_cancellations"] = int(match.group(1)) # Check for crash indicators if any( diff --git a/tests/script/test_determine_jobs.py b/tests/script/test_determine_jobs.py index 84be7344c3..7200afc2ee 100644 --- a/tests/script/test_determine_jobs.py +++ b/tests/script/test_determine_jobs.py @@ -146,9 +146,11 @@ def test_main_list_components_fails( mock_subprocess_run.side_effect = subprocess.CalledProcessError(1, "cmd") # Run main function with mocked argv - should raise - with patch("sys.argv", ["determine-jobs.py"]): - with pytest.raises(subprocess.CalledProcessError): - determine_jobs.main() + with ( + patch("sys.argv", ["determine-jobs.py"]), + pytest.raises(subprocess.CalledProcessError), + ): + determine_jobs.main() def test_main_with_branch_argument( @@ -243,17 +245,21 @@ def test_should_run_integration_tests_with_branch() -> None: def test_should_run_integration_tests_component_dependency() -> None: """Test that integration tests run when components used in fixtures change.""" - with patch.object( - determine_jobs, "changed_files", return_value=["esphome/components/api/api.cpp"] - ): - with patch.object( + with ( + patch.object( + determine_jobs, + "changed_files", + return_value=["esphome/components/api/api.cpp"], + ), + patch.object( determine_jobs, "get_components_from_integration_fixtures" - ) as mock_fixtures: - mock_fixtures.return_value = {"api", "sensor"} - with patch.object(determine_jobs, "get_all_dependencies") as mock_deps: - mock_deps.return_value = {"api", "sensor", "network"} - result = determine_jobs.should_run_integration_tests() - assert result is True + ) as mock_fixtures, + ): + mock_fixtures.return_value = {"api", "sensor"} + with patch.object(determine_jobs, "get_all_dependencies") as mock_deps: + mock_deps.return_value = {"api", "sensor", "network"} + result = determine_jobs.should_run_integration_tests() + assert result is True @pytest.mark.parametrize( @@ -272,12 +278,14 @@ def test_should_run_clang_tidy( expected_result: bool, ) -> None: """Test should_run_clang_tidy function.""" - with patch.object(determine_jobs, "changed_files", return_value=changed_files): + with ( + patch.object(determine_jobs, "changed_files", return_value=changed_files), + patch("subprocess.run") as mock_run, + ): # Test with hash check returning specific code - with patch("subprocess.run") as mock_run: - mock_run.return_value = Mock(returncode=check_returncode) - result = determine_jobs.should_run_clang_tidy() - assert result == expected_result + mock_run.return_value = Mock(returncode=check_returncode) + result = determine_jobs.should_run_clang_tidy() + assert result == expected_result def test_should_run_clang_tidy_hash_check_exception() -> None: