From 47e01cf6245d457b98cfde426f9ef134bc993433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 22 Apr 2025 13:47:51 +0200 Subject: [PATCH] Set minimum Home Assistant version to 2025.3.0 (#4552) * Set minimum Home Assistant version to 2025.3.0 * update more * Retrigger CI? --- .devcontainer.json | 2 +- .github/workflows/generate-hacs-data.yml | 4 +- .github/workflows/lint.yaml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/pytest.yml | 6 +-- .github/workflows/validate.yml | 2 +- action/Dockerfile | 2 +- hacs.json | 2 +- requirements_action.txt | 2 +- requirements_core_min.txt | 6 +-- requirements_generate_data.txt | 2 +- tests/common.py | 6 +-- tests/conftest.py | 2 +- tests/homeassistantfixtures/dev.py | 12 +++-- tests/homeassistantfixtures/min.py | 57 ++++++++++++++---------- 15 files changed, 62 insertions(+), 47 deletions(-) diff --git a/.devcontainer.json b/.devcontainer.json index cd1cd6bc9..d5760da60 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -1,6 +1,6 @@ { "name": "hacs/integration", - "image": "mcr.microsoft.com/devcontainers/python:1-3.12", + "image": "mcr.microsoft.com/devcontainers/python:1-3.13", "postCreateCommand": "scripts/setup", "forwardPorts": [ 8123 diff --git a/.github/workflows/generate-hacs-data.yml b/.github/workflows/generate-hacs-data.yml index dc5086ce8..c4a77d814 100644 --- a/.github/workflows/generate-hacs-data.yml +++ b/.github/workflows/generate-hacs-data.yml @@ -64,7 +64,7 @@ jobs: uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 id: python with: - python-version: "3.12" + python-version: "3.13" cache: 'pip' cache-dependency-path: | requirements_base.txt @@ -230,7 +230,7 @@ jobs: uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 id: python with: - python-version: "3.12" + python-version: "3.13" cache: 'pip' cache-dependency-path: | requirements_base.txt diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index daaf0b239..bfa1b5de2 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -39,7 +39,7 @@ jobs: uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 id: python with: - python-version: "3.12" + python-version: "3.13" cache: 'pip' cache-dependency-path: | requirements_base.txt diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d7aeab0e4..0f89a4691 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -27,7 +27,7 @@ jobs: - name: 🛠️ Set up Python uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: - python-version: "3.12" + python-version: "3.13" - name: 🔢 Get version if: ${{ github.event_name == 'release' }} diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 4fdc020fd..858642117 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -22,10 +22,10 @@ jobs: - name: 📥 Checkout the repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: 🛠️ Set up Python 3.12 + - name: 🛠️ Set up Python 3.13 uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: - python-version: "3.12" + python-version: "3.13" cache: 'pip' cache-dependency-path: | requirements_core_min.txt @@ -89,7 +89,7 @@ jobs: run: scripts/test - name: 📤 Upload coverage to Codecov - if: ${{ matrix.python-version == '3.12' }} + if: ${{ matrix.python-version == '3.13' }} run: | scripts/coverage curl -sfSL https://codecov.io/bash | bash - \ No newline at end of file diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index afec4ec20..4f0b67409 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -113,7 +113,7 @@ jobs: - name: Set up Python uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: - python-version: "3.12" + python-version: "3.13" cache: 'pip' cache-dependency-path: | requirements_base.txt diff --git a/action/Dockerfile b/action/Dockerfile index 4b038fcef..466ec8fd4 100644 --- a/action/Dockerfile +++ b/action/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12-alpine +FROM python:3.13-alpine WORKDIR /hacs COPY . /hacs diff --git a/hacs.json b/hacs.json index dbe96572b..521a24460 100644 --- a/hacs.json +++ b/hacs.json @@ -2,7 +2,7 @@ "name": "HACS", "zip_release": true, "hide_default_branch": true, - "homeassistant": "2024.4.1", + "homeassistant": "2025.3.0", "hacs": "0.19.0", "filename": "hacs.zip" } \ No newline at end of file diff --git a/requirements_action.txt b/requirements_action.txt index 3f1f8bf41..723c9055b 100644 --- a/requirements_action.txt +++ b/requirements_action.txt @@ -1,2 +1,2 @@ aiogithubapi>=24.6.0 -homeassistant==2024.8.3 +homeassistant==2025.3.0 diff --git a/requirements_core_min.txt b/requirements_core_min.txt index 7eb0d9bca..63d1644c6 100644 --- a/requirements_core_min.txt +++ b/requirements_core_min.txt @@ -1,3 +1,3 @@ -# https://github.com/home-assistant/core/blob/2024.4.1/homeassistant/components/frontend/manifest.json -home-assistant-frontend==20240404.1 -homeassistant==2024.4.1 +# https://github.com/home-assistant/core/blob/2025.3.0/homeassistant/components/frontend/manifest.json +home-assistant-frontend==20250305.0 +homeassistant==2025.3.0 diff --git a/requirements_generate_data.txt b/requirements_generate_data.txt index d5a0be489..cff941aa1 100644 --- a/requirements_generate_data.txt +++ b/requirements_generate_data.txt @@ -1,3 +1,3 @@ --requirement requirements_base.txt awscli==1.36.39 -homeassistant==2024.3.3 +homeassistant==2025.3.0 diff --git a/tests/common.py b/tests/common.py index 17a416faf..f7f193374 100644 --- a/tests/common.py +++ b/tests/common.py @@ -558,16 +558,14 @@ def create_config_entry( "version": 1, "minor_version": 0, "domain": DOMAIN, + "discovery_keys": {}, "title": "", "data": {"token": TOKEN, **(data or {})}, "source": "user", + "subentries_data": None, "options": {**(options or {})}, "unique_id": "12345", } - # legacy workaround for tests - if AwesomeVersion(HA_VERSION).dev: - config_entry_data["discovery_keys"] = {} - config_entry_data["subentries_data"] = None return MockConfigEntry(**config_entry_data) diff --git a/tests/conftest.py b/tests/conftest.py index a2587ce32..595956a8e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -144,7 +144,7 @@ async def hass(time_freezer, event_loop, tmpdir, check_report_issue: None): orig_exception_handler(loop, context) exceptions: list[Exception] = [] - if AwesomeVersion(HA_VERSION) > "2024.4.1": + if AwesomeVersion(HA_VERSION) > "2025.3.0": context_manager = async_test_home_assistant_dev( event_loop, config_dir=tmpdir.strpath) else: diff --git a/tests/homeassistantfixtures/dev.py b/tests/homeassistantfixtures/dev.py index 4bd00aa1a..03ba98c54 100644 --- a/tests/homeassistantfixtures/dev.py +++ b/tests/homeassistantfixtures/dev.py @@ -1,7 +1,7 @@ """Return a Home Assistant object pointing at test config dir. This should be copied from latest Home Assistant version, -currently Home Assistant Core 2024.10.0dev0 (2024-09-26). +currently Home Assistant Core 2025.5.0dev0 (2025-04-22). https://github.com/home-assistant/core/blob/dev/tests/common.py """ @@ -113,7 +113,12 @@ async def async_test_home_assistant( hass.config_entries = config_entries.ConfigEntries( hass, - {"_": ("Not empty or else some bad checks for hass config in discovery.py" " breaks")}, + { + "_": ( + "Not empty or else some bad checks for hass config in discovery.py" + " breaks" + ) + }, ) hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STOP, @@ -126,7 +131,8 @@ async def async_test_home_assistant( # setup translation cache instead of calling translation.async_setup(hass) hass.data[translation.TRANSLATION_FLATTEN_CACHE] = translation._TranslationCache( - hass) + hass + ) if load_registries: with ( patch.object(StoreWithoutWriteLoad, diff --git a/tests/homeassistantfixtures/min.py b/tests/homeassistantfixtures/min.py index a393d3237..b724b1aeb 100644 --- a/tests/homeassistantfixtures/min.py +++ b/tests/homeassistantfixtures/min.py @@ -1,14 +1,15 @@ """Return a Home Assistant object pointing at test config dir. This should be copied from the minimum supported version, -currently Home Assistant Core 2024.4.1. +currently Home Assistant Core 2025.3.0. +https://github.com/home-assistant/core/blob/2025.3.0/tests/common.py """ from __future__ import annotations import asyncio from collections.abc import AsyncGenerator -from contextlib import asynccontextmanager +from contextlib import asynccontextmanager, suppress import functools as ft from unittest.mock import AsyncMock, Mock, patch @@ -28,6 +29,7 @@ from homeassistant.helpers import ( restore_state as rs, translation, ) +from homeassistant.util.async_ import _SHUTDOWN_RUN_CALLBACK_THREADSAFE import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import METRIC_SYSTEM @@ -45,12 +47,9 @@ async def async_test_home_assistant( event_loop: asyncio.AbstractEventLoop | None = None, load_registries: bool = True, config_dir: str | None = None, + initial_state: CoreState = CoreState.running, ) -> AsyncGenerator[HomeAssistant]: - """Return a Home Assistant object pointing at test config dir. - - This should be copied from the minimum supported version, - currently Home Assistant Core 2024.4.1. - """ + """Return a Home Assistant object pointing at test config dir.""" hass = HomeAssistant(config_dir or get_test_config_dir()) store = auth_store.AuthStore(hass) hass.auth = auth.AuthManager(hass, store, {}, {}) @@ -59,8 +58,8 @@ async def async_test_home_assistant( orig_async_add_job = hass.async_add_job orig_async_add_executor_job = hass.async_add_executor_job - orig_async_create_task = hass.async_create_task - orig_tz = dt_util.DEFAULT_TIME_ZONE + orig_async_create_task_internal = hass.async_create_task_internal + orig_tz = dt_util.get_default_time_zone() def async_add_job(target, *args, eager_start: bool = False): """Add job.""" @@ -88,18 +87,18 @@ async def async_test_home_assistant( return orig_async_add_executor_job(target, *args) - def async_create_task(coroutine, name=None, eager_start=False): + def async_create_task_internal(coroutine, name=None, eager_start=True): """Create task.""" if isinstance(coroutine, Mock) and not isinstance(coroutine, AsyncMock): fut = asyncio.Future() fut.set_result(None) return fut - return orig_async_create_task(coroutine, name, eager_start) + return orig_async_create_task_internal(coroutine, name, eager_start) hass.async_add_job = async_add_job hass.async_add_executor_job = async_add_executor_job - hass.async_create_task = async_create_task + hass.async_create_task_internal = async_create_task_internal hass.data[loader.DATA_CUSTOM_COMPONENTS] = {} @@ -107,7 +106,7 @@ async def async_test_home_assistant( hass.config.latitude = 32.87336 hass.config.longitude = -117.22743 hass.config.elevation = 0 - hass.config.set_time_zone("US/Pacific") + await hass.config.async_set_time_zone("US/Pacific") hass.config.units = METRIC_SYSTEM hass.config.media_dirs = {"local": get_test_config_dir("media")} hass.config.skip_pip = True @@ -115,12 +114,16 @@ async def async_test_home_assistant( hass.config_entries = config_entries.ConfigEntries( hass, - {"_": ("Not empty or else some bad checks for hass config in discovery.py breaks")}, + { + "_": ( + "Not empty or else some bad checks for hass config in discovery.py" + " breaks" + ) + }, ) hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STOP, hass.config_entries._async_shutdown, - run_immediately=True, ) # Load the registries @@ -128,10 +131,13 @@ async def async_test_home_assistant( loader.async_setup(hass) # setup translation cache instead of calling translation.async_setup(hass) - hass.data[translation.TRANSLATION_FLATTEN_CACHE] = translation._TranslationCache(hass) + hass.data[translation.TRANSLATION_FLATTEN_CACHE] = translation._TranslationCache( + hass + ) if load_registries: with ( - patch.object(StoreWithoutWriteLoad, "async_load", return_value=None), + patch.object(StoreWithoutWriteLoad, + "async_load", return_value=None), patch( "homeassistant.helpers.area_registry.AreaRegistryStore", StoreWithoutWriteLoad, @@ -170,16 +176,21 @@ async def async_test_home_assistant( await rs.async_load(hass) hass.data[bootstrap.DATA_REGISTRIES_LOADED] = None - hass.set_state(CoreState.running) + hass.set_state(initial_state) @callback def clear_instance(event): """Clear global instance.""" - INSTANCES.remove(hass) + # Give aiohttp one loop iteration to close + hass.loop.call_soon(INSTANCES.remove, hass) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, clear_instance) - yield hass - - # Restore timezone, it is set when creating the hass object - dt_util.DEFAULT_TIME_ZONE = orig_tz + try: + yield hass + finally: + # Restore timezone, it is set when creating the hass object + dt_util.set_default_time_zone(orig_tz) + # Remove loop shutdown indicator to not interfere with additional hass objects + with suppress(AttributeError): + delattr(hass.loop, _SHUTDOWN_RUN_CALLBACK_THREADSAFE)