From af5a7151aaa6bd0ce5116e97e1f66865ca1ef4e1 Mon Sep 17 00:00:00 2001 From: Tom Matheussen <13683094+Tommatheussen@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:05:30 +0100 Subject: [PATCH 01/12] Fix HA 2025.12 breaking (#1291) --- .../adaptive_lighting/hass_utils.py | 20 +++++++++++++++++++ custom_components/adaptive_lighting/switch.py | 3 +-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/custom_components/adaptive_lighting/hass_utils.py b/custom_components/adaptive_lighting/hass_utils.py index c87d481f..21ea67b3 100644 --- a/custom_components/adaptive_lighting/hass_utils.py +++ b/custom_components/adaptive_lighting/hass_utils.py @@ -4,6 +4,7 @@ import logging from collections.abc import Awaitable, Callable from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.helpers import device_registry, entity_registry from homeassistant.util.read_only_dict import ReadOnlyDict from .adaptation_utils import ServiceData @@ -11,6 +12,25 @@ from .adaptation_utils import ServiceData _LOGGER = logging.getLogger(__name__) +def area_entities(hass: HomeAssistant, area_id: str): + """Get all entities linked to an area.""" + ent_reg = entity_registry.async_get(hass) + entity_ids = [ + entry.entity_id + for entry in entity_registry.async_entries_for_area(ent_reg, area_id) + ] + dev_reg = device_registry.async_get(hass) + entity_ids.extend( + [ + entity.entity_id + for device in device_registry.async_entries_for_area(dev_reg, area_id) + for entity in entity_registry.async_entries_for_device(ent_reg, device.id) + if entity.area_id is None + ], + ) + return entity_ids + + def setup_service_call_interceptor( hass: HomeAssistant, domain: str, diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index f00227dc..fdeac7dd 100644 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -75,7 +75,6 @@ from homeassistant.helpers.event import ( ) from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.sun import get_astral_location -from homeassistant.helpers.template import area_entities from homeassistant.util import slugify from homeassistant.util.color import ( color_temperature_to_rgb, @@ -153,7 +152,7 @@ from .const import ( apply_service_schema, replace_none_str, ) -from .hass_utils import setup_service_call_interceptor +from .hass_utils import area_entities, setup_service_call_interceptor from .helpers import ( clamp, color_difference_redmean, From e8af7a485e8bbf1958e343b2ceae93657f8d9c11 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 27 Nov 2025 09:06:41 -0800 Subject: [PATCH 02/12] Bump to 1.27.0 for fixes in 2025.12 (#1293) --- .github/workflows/pytest.yaml | 12 ++++++++- Dockerfile | 6 +++++ .../adaptive_lighting/manifest.json | 2 +- scripts/setup-dependencies | 16 ++++++++++++ tests/conftest.py | 26 +++++++++++++++++++ tests/test_switch.py | 11 +++++++- 6 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 tests/conftest.py diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index fb25eff0..21d76466 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -26,7 +26,17 @@ jobs: python-version: "3.13" - core-version: "2025.5.3" python-version: "3.13" - - core-version: "2025.6.1" + - core-version: "2025.6.3" + python-version: "3.13" + - core-version: "2025.7.4" + python-version: "3.13" + - core-version: "2025.8.3" + python-version: "3.13" + - core-version: "2025.9.4" + python-version: "3.13" + - core-version: "2025.10.4" + python-version: "3.13" + - core-version: "2025.11.3" python-version: "3.13" - core-version: "dev" python-version: "3.13" diff --git a/Dockerfile b/Dockerfile index 3bd36ddd..c4d9c695 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,12 @@ FROM ghcr.io/astral-sh/uv:debian +# Install build dependencies for Python extensions +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3-dev \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + # Clone home-assistant/core RUN git clone --depth 1 --branch dev https://github.com/home-assistant/core.git /core diff --git a/custom_components/adaptive_lighting/manifest.json b/custom_components/adaptive_lighting/manifest.json index 92100afa..856abfe8 100644 --- a/custom_components/adaptive_lighting/manifest.json +++ b/custom_components/adaptive_lighting/manifest.json @@ -8,5 +8,5 @@ "iot_class": "calculated", "issue_tracker": "https://github.com/basnijholt/adaptive-lighting/issues", "requirements": ["ulid-transform"], - "version": "1.26.0" + "version": "1.27.0" } diff --git a/scripts/setup-dependencies b/scripts/setup-dependencies index b9e2de1f..92a41411 100755 --- a/scripts/setup-dependencies +++ b/scripts/setup-dependencies @@ -6,6 +6,22 @@ if grep -q 'mypy-dev==1.14.0a3' core/requirements_test.txt; then # mypy-dev==1.14.0a3 seems to not be available anymore, HA 2024.12 is affected sed -i 's/mypy-dev==1.14.0a3/mypy-dev==1.14.0a7/' core/requirements_test.txt fi +if grep -q 'mypy-dev==1.16.0a1' core/requirements_test.txt; then + # mypy-dev==1.16.0a1 seems to not be available anymore, HA 2025.2 is affected + sed -i 's/mypy-dev==1.16.0a1/mypy-dev==1.16.0a9/' core/requirements_test.txt +fi +if grep -q 'mypy-dev==1.16.0a3' core/requirements_test.txt; then + # mypy-dev==1.16.0a3 seems to not be available anymore, HA 2025.3 is affected + sed -i 's/mypy-dev==1.16.0a3/mypy-dev==1.16.0a9/' core/requirements_test.txt +fi +if grep -q 'mypy-dev==1.16.0a7' core/requirements_test.txt; then + # mypy-dev==1.16.0a7 seems to not be available anymore, HA 2025.4 is affected + sed -i 's/mypy-dev==1.16.0a7/mypy-dev==1.16.0a9/' core/requirements_test.txt +fi +if grep -q 'mypy-dev==1.16.0a8' core/requirements_test.txt; then + # mypy-dev==1.16.0a8 seems to not be available anymore, HA 2025.5 and 2025.6 is affected + sed -i 's/mypy-dev==1.16.0a8/mypy-dev==1.16.0a9/' core/requirements_test.txt +fi uv pip install -r core/requirements.txt uv pip install -r core/requirements_test.txt diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..cce77d96 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,26 @@ +"""Pytest configuration for adaptive-lighting tests.""" + +from unittest.mock import patch + +import pytest + + +@pytest.fixture(autouse=True) +def mock_template_deprecation_issue(): + """Mock the template deprecation issue creation. + + The template component's legacy platform syntax creates deprecation + issues that require translations. Since adaptive-lighting tests use + template lights as test fixtures (not testing the template integration + itself), we mock the issue creation to avoid translation validation errors. + """ + # Patch the create_legacy_template_issue function in the template helpers + # to be a no-op when called for the deprecated_legacy_templates issue + try: + with patch( + "homeassistant.components.template.helpers.create_legacy_template_issue", + ): + yield + except (ImportError, ModuleNotFoundError, AttributeError): + # Older HA versions don't have this function + yield diff --git a/tests/test_switch.py b/tests/test_switch.py index cc03fa24..fc5f67cc 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -82,7 +82,16 @@ from homeassistant.components.light import ( ) from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN -from homeassistant.components.template.light import LightTemplate + +try: + # HA >= 2025.8 + from homeassistant.components.template.light import ( + StateLightEntity as LightTemplate, + ) +except ImportError: + # HA < 2025.8 + from homeassistant.components.template.light import LightTemplate + from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ( ATTR_AREA_ID, From 95a8f34000ab4848bdd724265afe64a2f6fa3aae Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:17:12 -0800 Subject: [PATCH 03/12] docs: add Tommatheussen as a contributor for code (#1294) --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 76a8cf13..6f34f6c3 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1062,6 +1062,15 @@ "contributions": [ "code" ] + }, + { + "login": "Tommatheussen", + "name": "Tom Matheussen", + "avatar_url": "https://avatars.githubusercontent.com/u/13683094?v=4", + "profile": "https://github.com/Tommatheussen", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index 7af1feb8..0b2d11cc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg?style=for-the-badge)](https://github.com/hacs/integration) ![Version](https://img.shields.io/github/v/release/basnijholt/adaptive-lighting?style=for-the-badge) -[![All Contributors](https://img.shields.io/badge/all_contributors-116-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-117-orange.svg?style=flat-square)](#contributors-) # 🌞 Adaptive Lighting: Enhance Your Home's Atmosphere with Smart, Sun-Synchronized Lighting 🌙 @@ -616,6 +616,7 @@ Notice the values of `brightness_mode_time_light` and `brightness_mode_time_dark Luna Jernberg
Luna Jernberg

🌍 Jeff Wilson
Jeff Wilson

💻 Rasmus Lundsgaard
Rasmus Lundsgaard

💻 + Tom Matheussen
Tom Matheussen

💻 From 6f846c26cab2666e296e2e5f617b9805ce8a251d Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 27 Nov 2025 09:27:13 -0800 Subject: [PATCH 04/12] Fix race condition where timer.start_time is None while is_running() is True (#1295) When start() creates a task with asyncio.create_task(), the task is scheduled but not immediately executed. This means is_running() returns True (task exists and not done), but start_time is still None because _run() hasn't executed yet. This causes a TypeError when comparing event.time_fired > timer.start_time. Fix by setting start_time in start() before creating the task. Fixes #1272 --- custom_components/adaptive_lighting/switch.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index fdeac7dd..6a22a41f 100644 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -2680,7 +2680,6 @@ class _AsyncSingleShotTimer: async def _run(self): """Run the timer. Don't call this directly, use start() instead.""" - self.start_time = dt_util.utcnow() await asyncio.sleep(self.delay) if self.callback: if asyncio.iscoroutinefunction(self.callback): @@ -2696,6 +2695,10 @@ class _AsyncSingleShotTimer: """Start the timer.""" if self.task is not None and not self.task.done(): self.task.cancel() + # Set start_time before creating task to avoid race condition + # where is_running() returns True but start_time is still None + # See: https://github.com/basnijholt/adaptive-lighting/issues/1272 + self.start_time = dt_util.utcnow() self.task = asyncio.create_task(self._run()) def cancel(self): From b34cc1ffaa40c802ce07d256b8ef7c9bd8f2858e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:27:27 -0800 Subject: [PATCH 05/12] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Update=20actions/che?= =?UTF-8?q?ckout=20action=20to=20v6=20(#1288)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/deploy-webapp.yml | 2 +- .github/workflows/hassfest.yaml | 2 +- .github/workflows/install_dependencies/action.yml | 4 ++-- .github/workflows/main-to-master-sync.yml | 2 +- .github/workflows/pre-commit.yaml | 2 +- .github/workflows/pytest.yaml | 2 +- .github/workflows/update-readme.yml | 2 +- .github/workflows/validate.yml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy-webapp.yml b/.github/workflows/deploy-webapp.yml index e52963b0..29b8a7b4 100644 --- a/.github/workflows/deploy-webapp.yml +++ b/.github/workflows/deploy-webapp.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set Up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/hassfest.yaml b/.github/workflows/hassfest.yaml index 4d141e56..69f93e15 100644 --- a/.github/workflows/hassfest.yaml +++ b/.github/workflows/hassfest.yaml @@ -11,5 +11,5 @@ jobs: validate_hassfest: runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v4.2.2" + - uses: "actions/checkout@v6.0.0" - uses: home-assistant/actions/hassfest@master diff --git a/.github/workflows/install_dependencies/action.yml b/.github/workflows/install_dependencies/action.yml index 8c4483f7..8d561b02 100644 --- a/.github/workflows/install_dependencies/action.yml +++ b/.github/workflows/install_dependencies/action.yml @@ -14,14 +14,14 @@ runs: using: "composite" steps: - name: Check out code from GitHub - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{ github.repository }} ref: ${{ github.ref }} persist-credentials: false fetch-depth: 0 - name: Check out code from GitHub - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: home-assistant/core path: core diff --git a/.github/workflows/main-to-master-sync.yml b/.github/workflows/main-to-master-sync.yml index d5c915c8..424f50b3 100644 --- a/.github/workflows/main-to-master-sync.yml +++ b/.github/workflows/main-to-master-sync.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: main fetch-depth: 0 diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 7f579afa..c6f2cfb3 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -9,6 +9,6 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-python@v5 - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 21d76466..b8a59b40 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -42,7 +42,7 @@ jobs: python-version: "3.13" steps: - name: Check out code from GitHub - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Home Assistant uses: ./.github/workflows/install_dependencies diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml index a4422130..7cdb8e24 100644 --- a/.github/workflows/update-readme.yml +++ b/.github/workflows/update-readme.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code from GitHub - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Home Assistant uses: ./.github/workflows/install_dependencies diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 79c7fe00..3fa46b4e 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -11,7 +11,7 @@ jobs: validate_hacs: runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v4" + - uses: "actions/checkout@v6" - name: HACS validation uses: "hacs/action@main" with: From 1556de8c4f0962ddc90611ca64e37d114fd04e7c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:27:38 -0800 Subject: [PATCH 06/12] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Update=20mcr.microso?= =?UTF-8?q?ft.com/devcontainers/python=20Docker=20tag=20to=20v3=20(#1292)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer.json b/.devcontainer.json index 0a09ed54..8d5e2bd9 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -1,6 +1,6 @@ { "name": "basnijholt/adaptive_lighting", - "image": "mcr.microsoft.com/devcontainers/python:1-3.13", + "image": "mcr.microsoft.com/devcontainers/python:3-3.13", "postCreateCommand": "./scripts/setup-devcontainer && . .venv/bin/activate", "forwardPorts": [ 8123 From 68e243c87e69c97030d222846c0c0771bccc94ae Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 27 Nov 2025 09:30:01 -0800 Subject: [PATCH 07/12] Fix infinite loop when disabling SimpleSwitch entities (#1296) * Add regression tests for SimpleSwitch initial state bug Adds tests that verify SimpleSwitch._state is set immediately in __init__ rather than waiting for async_added_to_hass(). These tests currently FAIL because _state is None after __init__, which causes an infinite loop in _setup_listeners when the entity is disabled (since async_added_to_hass is never called for disabled entities). Regression tests for: https://github.com/basnijholt/adaptive-lighting/issues/1264 * Fix infinite loop when disabling SimpleSwitch entities The issue was that SimpleSwitch._state was initialized to None in __init__, but only set to a boolean value in async_added_to_hass(). When an entity is disabled, async_added_to_hass() is never called, so _state stayed None. The _setup_listeners() method has a while loop that waits for _state is not None for all SimpleSwitch children (sleep_mode_switch, adapt_brightness_switch, adapt_color_switch). With _state stuck at None, this created an infinite loop. The fix sets _state to initial_state directly in __init__ instead of waiting for async_added_to_hass() to set it. The async_added_to_hass() will still properly restore state from the last session or set based on initial_state as before. Fixes: https://github.com/basnijholt/adaptive-lighting/issues/1264 --- custom_components/adaptive_lighting/switch.py | 2 +- tests/test_switch.py | 74 +++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index 6a22a41f..19e99d78 100644 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -1563,7 +1563,7 @@ class SimpleSwitch(SwitchEntity, RestoreEntity): self.hass = hass data = validate(config_entry) self._icon = icon - self._state: bool | None = None + self._state: bool = initial_state self._which = which self._config_name = data[CONF_NAME] self._unique_id = f"{self._config_name}_{slugify(self._which)}" diff --git a/tests/test_switch.py b/tests/test_switch.py index fc5f67cc..1016b591 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -65,6 +65,7 @@ from homeassistant.components.adaptive_lighting.switch import ( CONF_INTERCEPT, AdaptiveLightingManager, AdaptiveSwitch, + SimpleSwitch, _attributes_have_changed, color_difference_redmean, create_context, @@ -2275,3 +2276,76 @@ async def test_brightness_mode(hass, brightness_mode, dark, light): # After sunrise the brightness should be light_brightness await patch_time_and_update(after_sunrise) assert is_approx_equal(switch._settings[ATTR_BRIGHTNESS_PCT], light_brightness) + + +async def test_simple_switch_initial_state_not_none(hass): + """Test that SimpleSwitch._state is not None after __init__. + + Regression test for https://github.com/basnijholt/adaptive-lighting/issues/1264 + + When an entity is disabled in Home Assistant, async_added_to_hass() is never + called. Previously, SimpleSwitch._state was initialized to None and only set + to True/False in async_added_to_hass(). This caused an infinite loop in + AdaptiveSwitch._setup_listeners() which waits for all SimpleSwitch._state + to be not None. + + The fix is to initialize _state to the initial_state value in __init__. + """ + entry = MockConfigEntry(domain=DOMAIN, data={CONF_NAME: DEFAULT_NAME}) + entry.add_to_hass(hass) + + # Create a SimpleSwitch without calling async_added_to_hass + # (simulating a disabled entity) + switch = SimpleSwitch( + which="Test", + initial_state=True, + hass=hass, + config_entry=entry, + icon="mdi:test", + ) + + # Before the fix: _state would be None, causing infinite loop + # After the fix: _state should be the initial_state value + assert switch._state is not None, ( + "SimpleSwitch._state should not be None after __init__. " + "This would cause an infinite loop in _setup_listeners when the entity is disabled." + ) + assert switch._state is True # Should be the initial_state value + + +async def test_simple_switch_state_after_async_added_to_hass(hass): + """Test that SimpleSwitch._state is properly set after async_added_to_hass. + + This ensures the fix for #1264 doesn't break normal entity initialization. + """ + entry = MockConfigEntry(domain=DOMAIN, data={CONF_NAME: DEFAULT_NAME}) + entry.add_to_hass(hass) + + # Create switches with different initial states + switch_true = SimpleSwitch( + which="Test True", + initial_state=True, + hass=hass, + config_entry=entry, + icon="mdi:test", + ) + switch_false = SimpleSwitch( + which="Test False", + initial_state=False, + hass=hass, + config_entry=entry, + icon="mdi:test", + ) + + # Verify initial state is set correctly + assert switch_true._state is True + assert switch_false._state is False + + # Call async_added_to_hass (simulating normal entity setup) + # Since there's no last state, it should use the initial_state + await switch_true.async_added_to_hass() + await switch_false.async_added_to_hass() + + # State should still be correct after async_added_to_hass + assert switch_true._state is True + assert switch_false._state is False From 9244ff39fe5d2cc0e5ee1e696f2d8f8bdac9dba7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:30:12 -0800 Subject: [PATCH 08/12] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Update=20astral-sh/s?= =?UTF-8?q?etup-uv=20action=20to=20v7=20(#1271)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/install_dependencies/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/install_dependencies/action.yml b/.github/workflows/install_dependencies/action.yml index 8d561b02..9a9903f9 100644 --- a/.github/workflows/install_dependencies/action.yml +++ b/.github/workflows/install_dependencies/action.yml @@ -32,7 +32,7 @@ runs: with: python-version: ${{ inputs.python-version }} - name: Set up UV - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 - name: Install dependencies shell: bash run: | From f6321afeae237e0514b249e2adbcc3609143761b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:30:26 -0800 Subject: [PATCH 09/12] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Update=20actions/upl?= =?UTF-8?q?oad-pages-artifact=20action=20to=20v4=20(#1259)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/deploy-webapp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-webapp.yml b/.github/workflows/deploy-webapp.yml index 29b8a7b4..b6614d12 100644 --- a/.github/workflows/deploy-webapp.yml +++ b/.github/workflows/deploy-webapp.yml @@ -53,7 +53,7 @@ jobs: uses: actions/configure-pages@v5 - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v4 with: # Upload the 'site' directory, where your app has been built path: "site" From 561749ac060e4ccfe706b401094663ec4588904d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:34:46 -0800 Subject: [PATCH 10/12] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Update=20actions/set?= =?UTF-8?q?up-python=20action=20to=20v6=20(#1261)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/deploy-webapp.yml | 2 +- .github/workflows/install_dependencies/action.yml | 2 +- .github/workflows/pre-commit.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-webapp.yml b/.github/workflows/deploy-webapp.yml index b6614d12..698d8fec 100644 --- a/.github/workflows/deploy-webapp.yml +++ b/.github/workflows/deploy-webapp.yml @@ -33,7 +33,7 @@ jobs: uses: actions/checkout@v6 - name: Set Up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: 3.13.5 diff --git a/.github/workflows/install_dependencies/action.yml b/.github/workflows/install_dependencies/action.yml index 9a9903f9..4ab58169 100644 --- a/.github/workflows/install_dependencies/action.yml +++ b/.github/workflows/install_dependencies/action.yml @@ -28,7 +28,7 @@ runs: ref: ${{ inputs.core-version }} - name: Set up Python ${{ inputs.python-version }} id: python - uses: actions/setup-python@v5.6.0 + uses: actions/setup-python@v6.1.0 with: python-version: ${{ inputs.python-version }} - name: Set up UV diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index c6f2cfb3..57263d00 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -10,5 +10,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 - uses: pre-commit/action@v3.0.1 From 32719eabaea9eaa58fd7d52d9202101369c80aa7 Mon Sep 17 00:00:00 2001 From: ams2990 Date: Thu, 27 Nov 2025 07:35:09 -1000 Subject: [PATCH 11/12] Fix some type hint issues (#1280) --- custom_components/adaptive_lighting/__init__.py | 4 ++-- .../adaptive_lighting/_docs_helpers.py | 7 ++----- .../adaptive_lighting/adaptation_utils.py | 6 +++--- .../adaptive_lighting/color_and_brightness.py | 16 ++++++++-------- .../adaptive_lighting/config_flow.py | 3 ++- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/custom_components/adaptive_lighting/__init__.py b/custom_components/adaptive_lighting/__init__.py index 13c2d7d1..0c8bad80 100644 --- a/custom_components/adaptive_lighting/__init__.py +++ b/custom_components/adaptive_lighting/__init__.py @@ -70,12 +70,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): return True -async def async_update_options(hass, config_entry: ConfigEntry): +async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry): """Update options.""" await hass.config_entries.async_reload(config_entry.entry_id) -async def async_unload_entry(hass, config_entry: ConfigEntry) -> bool: +async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = await hass.config_entries.async_forward_entry_unload( config_entry, diff --git a/custom_components/adaptive_lighting/_docs_helpers.py b/custom_components/adaptive_lighting/_docs_helpers.py index 31225a6c..49899395 100644 --- a/custom_components/adaptive_lighting/_docs_helpers.py +++ b/custom_components/adaptive_lighting/_docs_helpers.py @@ -57,8 +57,6 @@ def _type_to_str(type_: Any) -> str: # noqa: PLR0911 def generate_config_markdown_table(): - import pandas as pd - rows = [] for k, default, type_ in VALIDATION_TUPLES: description = DOCS[k] @@ -84,12 +82,11 @@ def _schema_to_dict(schema: vol.Schema) -> dict[str, tuple[Any, Any]]: def _generate_service_markdown_table( - schema: dict[str, tuple[Any, Any]], + schema: vol.Schema, alternative_docs: dict[str, str] | None = None, ): - schema = _schema_to_dict(schema) rows = [] - for k, (default, type_) in schema.items(): + for k, (default, type_) in _schema_to_dict(schema).items(): if alternative_docs is not None and k in alternative_docs: description = alternative_docs[k] else: diff --git a/custom_components/adaptive_lighting/adaptation_utils.py b/custom_components/adaptive_lighting/adaptation_utils.py index aea061d1..2c339223 100644 --- a/custom_components/adaptive_lighting/adaptation_utils.py +++ b/custom_components/adaptive_lighting/adaptation_utils.py @@ -54,7 +54,7 @@ def _split_service_call_data(service_data: ServiceData) -> list[ServiceData]: common_data = {k: service_data[k] for k in common_attrs if k in service_data} attributes_split_sequence = [BRIGHTNESS_ATTRS, COLOR_ATTRS] - service_datas = [] + service_datas: list[dict[str, Any]] = [] for attributes in attributes_split_sequence: split_data = { @@ -106,7 +106,7 @@ async def _create_service_call_data_iterator( hass: HomeAssistant, service_datas: list[ServiceData], filter_by_state: bool, -) -> AsyncGenerator[ServiceData, None]: +) -> AsyncGenerator[ServiceData]: """Enumerates and filters a list of service datas on the fly. If filtering is enabled, every service data is filtered by the current state of @@ -141,7 +141,7 @@ class AdaptationData: entity_id: str context: Context sleep_time: float - service_call_datas: AsyncGenerator[ServiceData, None] + service_call_datas: AsyncGenerator[ServiceData] force: bool max_length: int which: Literal["brightness", "color", "both"] diff --git a/custom_components/adaptive_lighting/color_and_brightness.py b/custom_components/adaptive_lighting/color_and_brightness.py index 6fdb7083..2fd93e67 100644 --- a/custom_components/adaptive_lighting/color_and_brightness.py +++ b/custom_components/adaptive_lighting/color_and_brightness.py @@ -8,8 +8,8 @@ import datetime import logging import math from dataclasses import dataclass -from datetime import timedelta -from functools import cached_property, partial +from datetime import UTC, timedelta +from functools import partial from typing import TYPE_CHECKING, Any, Literal, cast from homeassistant.util.color import ( @@ -17,9 +17,10 @@ from homeassistant.util.color import ( color_temperature_to_rgb, color_xy_to_hs, ) +from propcache.api import cached_property if TYPE_CHECKING: - import astral + import astral.location # Same as homeassistant.const.SUN_EVENT_SUNRISE and homeassistant.const.SUN_EVENT_SUNSET # We re-define them here to not depend on homeassistant in this file. @@ -32,7 +33,6 @@ SUN_EVENT_MIDNIGHT = "solar_midnight" _ORDER = (SUN_EVENT_SUNRISE, SUN_EVENT_NOON, SUN_EVENT_SUNSET, SUN_EVENT_MIDNIGHT) _ALLOWED_ORDERS = {_ORDER[i:] + _ORDER[:i] for i in range(len(_ORDER))} -UTC = datetime.timezone.utc utcnow: partial[datetime.datetime] = partial(datetime.datetime.now, UTC) utcnow.__doc__ = "Get now in UTC time." @@ -44,7 +44,7 @@ class SunEvents: """Track the state of the sun and associated light settings.""" name: str - astral_location: astral.Location + astral_location: astral.location.Location sunrise_time: datetime.time | None min_sunrise_time: datetime.time | None max_sunrise_time: datetime.time | None @@ -198,7 +198,7 @@ class SunLightSettings: """Track the state of the sun and associated light settings.""" name: str - astral_location: astral.Location + astral_location: astral.location.Location adapt_until_sleep: bool max_brightness: int max_color_temp: int @@ -296,7 +296,7 @@ class SunLightSettings: ) return clamp(brightness, self.min_brightness, self.max_brightness) - def brightness_pct(self, dt: datetime.datetime, is_sleep: bool) -> float: + def brightness_pct(self, dt: datetime.datetime, is_sleep: bool) -> float | None: """Calculate the brightness in %.""" if is_sleep: return self.sleep_brightness @@ -331,7 +331,7 @@ class SunLightSettings: ) -> dict[str, Any]: """Calculate the brightness and color.""" sun_position = self.sun.sun_position(dt) - rgb_color: tuple[float, float, float] + rgb_color: tuple[int, int, int] # Variable `force_rgb_color` is needed for RGB color after sunset (if enabled) force_rgb_color = False brightness_pct = self.brightness_pct(dt, is_sleep) diff --git a/custom_components/adaptive_lighting/config_flow.py b/custom_components/adaptive_lighting/config_flow.py index 3922d000..00214fdb 100644 --- a/custom_components/adaptive_lighting/config_flow.py +++ b/custom_components/adaptive_lighting/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Adaptive Lighting integration.""" import logging +from typing import Any import homeassistant.helpers.config_validation as cv import voluptuous as vol @@ -40,7 +41,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_import(self, user_input=None): + async def async_step_import(self, user_input: dict[str, Any]): """Handle configuration by YAML file.""" await self.async_set_unique_id(user_input[CONF_NAME]) # Keep a list of switches that are configured via YAML From 3d3d24691881ed386384a64f2295492dac5745e2 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:36:04 -0800 Subject: [PATCH 12/12] docs: add ams2990 as a contributor for code (#1297) * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 6f34f6c3..004381bb 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1071,6 +1071,15 @@ "contributions": [ "code" ] + }, + { + "login": "ams2990", + "name": "ams2990", + "avatar_url": "https://avatars.githubusercontent.com/u/488907?v=4", + "profile": "https://github.com/ams2990", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index 0b2d11cc..9f4388c3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg?style=for-the-badge)](https://github.com/hacs/integration) ![Version](https://img.shields.io/github/v/release/basnijholt/adaptive-lighting?style=for-the-badge) -[![All Contributors](https://img.shields.io/badge/all_contributors-117-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-118-orange.svg?style=flat-square)](#contributors-) # 🌞 Adaptive Lighting: Enhance Your Home's Atmosphere with Smart, Sun-Synchronized Lighting 🌙 @@ -617,6 +617,7 @@ Notice the values of `brightness_mode_time_light` and `brightness_mode_time_dark Jeff Wilson
Jeff Wilson

💻 Rasmus Lundsgaard
Rasmus Lundsgaard

💻 Tom Matheussen
Tom Matheussen

💻 + ams2990
ams2990

💻