Clean up all remaining ruff errors; lint is now fully green

Config:
- .ruff.toml target-version py310 -> py312, matching pyproject's
  requires-python (>=3.12) and the 3.12 'type' aliases switch.py
  already ships. Unblocks two false invalid-syntax errors.
- Ignore D102 in tests (test-class methods), consistent with the
  existing D100/D103 test ignores.

Code (no behavior changes):
- COM812 trailing commas + UP017 dt.UTC via ruff --fix, black re-wrap
- RUF002: unicode minus/en-dash -> ASCII hyphen in docstrings
- SIM105: contextlib.suppress for the lux-reading parse in config_flow
- TRY300: move return out of try in _read_lux_sensor
- PLC0415: hoist inline asyncio/logging imports to module top
- E741: rename ambiguous 'l' comprehension variable to light_id
- PT006/PT018: parametrize tuple + split compound assertion
- D102: docstring for OptionsFlow.async_step_init
- ARG001/ARG002: noqa with justification on HA-required signatures
- PLR0912/PLR0915: noqa on prepare_adaptation_data and
  _update_attrs_and_maybe_adapt_lights (upstream-inherited complexity;
  splitting would hurt readability more than it helps)

./scripts/lint passes clean; 148 tests pass.
This commit is contained in:
Casey 2026-06-05 12:16:40 +02:00
commit 1c56674379
11 changed files with 68 additions and 37 deletions

View file

@ -42,12 +42,12 @@ class TestBrightnessCurve:
"""Spec R4: piecewise tanh ramp around the two sun events."""
def test_min_brightness_more_than_half_width_before_sunrise(self, settings):
"""T = sunrise − 2h → still deep night, brightness at minimum."""
"""T = sunrise - 2h → still deep night, brightness at minimum."""
t = T_SUNRISE - timedelta(hours=2)
assert settings.brightness_pct(t, T_SUNRISE, T_SUNSET) == 5
def test_min_brightness_exactly_at_clamp_boundary(self, settings):
"""T = sunrise − half_width → exactly at the boundary, still min."""
"""T = sunrise - half_width → exactly at the boundary, still min."""
t = T_SUNRISE - timedelta(seconds=HALF_WIDTH)
assert settings.brightness_pct(t, T_SUNRISE, T_SUNSET) == 5

View file

@ -101,7 +101,9 @@ def _section_inner_keys(schema_section) -> set[str]:
def test_options_schema_has_all_seven_sections_in_order() -> None:
"""R1: the options form returns the seven named sections in order."""
schema = _build_options_schema(
{}, show_send_split_delay=False, show_target_lux=False
{},
show_send_split_delay=False,
show_target_lux=False,
)
keys = [
k.schema if hasattr(k, "schema") else k
@ -115,7 +117,9 @@ def test_each_section_contains_only_its_specified_fields() -> None:
the layout table.
"""
schema = _build_options_schema(
{}, show_send_split_delay=True, show_target_lux=False
{},
show_send_split_delay=True,
show_target_lux=False,
)
for marker in schema.schema: # type: ignore[attr-defined]
section_id = marker.schema if hasattr(marker, "schema") else marker
@ -137,7 +141,9 @@ def test_each_section_contains_only_its_specified_fields() -> None:
def test_send_split_delay_hidden_when_driver_false() -> None:
"""R2: send_split_delay is absent when separate_turn_on_commands=False."""
schema = _build_options_schema(
{}, show_send_split_delay=False, show_target_lux=False
{},
show_send_split_delay=False,
show_target_lux=False,
)
advanced_marker = next(
m
@ -151,7 +157,9 @@ def test_send_split_delay_hidden_when_driver_false() -> None:
def test_send_split_delay_visible_when_driver_true() -> None:
"""R2: send_split_delay appears when separate_turn_on_commands=True."""
schema = _build_options_schema(
{}, show_send_split_delay=True, show_target_lux=False
{},
show_send_split_delay=True,
show_target_lux=False,
)
advanced_marker = next(
m
@ -170,7 +178,9 @@ def test_send_split_delay_visible_when_driver_true() -> None:
def test_default_sunrise_and_sunset_entities() -> None:
"""R3: default entities point at the built-in sun.sun sensors."""
schema = _build_options_schema(
{}, show_send_split_delay=False, show_target_lux=False
{},
show_send_split_delay=False,
show_target_lux=False,
)
sun_marker = next(
m
@ -200,7 +210,9 @@ def test_sun_entity_selectors_are_strict_timestamp_sensors() -> None:
device_class=timestamp.
"""
schema = _build_options_schema(
{}, show_send_split_delay=False, show_target_lux=False
{},
show_send_split_delay=False,
show_target_lux=False,
)
sun_marker = next(
m
@ -229,9 +241,11 @@ def test_sun_entity_selectors_are_strict_timestamp_sensors() -> None:
def test_brightness_uses_slider_number_selector() -> None:
"""R5: brightness fields are NumberSelectors with slider mode, 1–100 %, step 1."""
"""R5: brightness fields are NumberSelectors with slider mode, 1-100 %, step 1."""
schema = _build_options_schema(
{}, show_send_split_delay=False, show_target_lux=False
{},
show_send_split_delay=False,
show_target_lux=False,
)
daytime_marker = next(
m
@ -252,9 +266,11 @@ def test_brightness_uses_slider_number_selector() -> None:
def test_color_temp_uses_box_number_selector() -> None:
"""R5: color-temp fields are NumberSelectors, 1000–10000 K, step 100."""
"""R5: color-temp fields are NumberSelectors, 1000-10000 K, step 100."""
schema = _build_options_schema(
{}, show_send_split_delay=False, show_target_lux=False
{},
show_send_split_delay=False,
show_target_lux=False,
)
daytime_marker = next(
m
@ -276,7 +292,9 @@ def test_color_temp_uses_box_number_selector() -> None:
def test_booleans_use_boolean_selector() -> None:
"""R5: every boolean field renders as a BooleanSelector."""
schema = _build_options_schema(
{}, show_send_split_delay=True, show_target_lux=False
{},
show_send_split_delay=True,
show_target_lux=False,
)
boolean_fields = {
CONF_PREFER_RGB_COLOR,
@ -368,7 +386,9 @@ async def test_options_flow_renders_sectioned_schema(hass) -> None:
def test_target_lux_hidden_when_no_sensor() -> None:
"""target_lux should not appear when lux_sensor is empty."""
schema = _build_options_schema(
{}, show_send_split_delay=False, show_target_lux=False
{},
show_send_split_delay=False,
show_target_lux=False,
)
lux_marker = next(
m
@ -400,7 +420,9 @@ def test_target_lux_visible_when_sensor_set() -> None:
def test_lux_sensor_selector_filters_to_illuminance() -> None:
"""lux_sensor entity selector should filter to device_class=illuminance."""
schema = _build_options_schema(
{}, show_send_split_delay=False, show_target_lux=False
{},
show_send_split_delay=False,
show_target_lux=False,
)
lux_marker = next(
m

View file

@ -56,7 +56,8 @@ async def test_stale_version_raises_config_entry_error(hass, caplog) -> None:
# The full captured log (which includes the exception traceback) should
# carry our friendly "delete and recreate" message so the user sees it.
text = caplog.text.lower()
assert "delete" in text and "recreate" in text
assert "delete" in text
assert "recreate" in text
async def test_unload_entry(hass) -> None:

View file

@ -7,6 +7,7 @@ no-reload-on-slider write, and the curve-math read path.
from __future__ import annotations
import datetime
import logging
from unittest.mock import patch
import pytest
@ -138,7 +139,7 @@ async def test_number_entities_share_switch_device(hass) -> None:
@pytest.mark.parametrize(
"field_key,expected",
("field_key", "expected"),
[
("min_brightness", {"min": 1.0, "max": 100.0, "step": 1, "unit": "%"}),
("max_brightness", {"min": 1.0, "max": 100.0, "step": 1, "unit": "%"}),
@ -259,7 +260,7 @@ async def test_restore_state_survives_restart(hass) -> None:
object.__setattr__(
entry,
"modified_at",
datetime.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc),
datetime.datetime(2020, 1, 1, tzinfo=datetime.UTC),
)
# Prime the restore cache with a saved native_value of 30.
@ -412,8 +413,6 @@ async def test_curve_math_falls_back_on_unavailable(hass, caplog) -> None:
al_data = hass.data[DOMAIN][entry.entry_id]
al_switch = al_data["switch"]
import logging
caplog.set_level(logging.DEBUG)
settings = al_switch.sun_light_settings
assert settings.max_brightness == 88 # fell back to options