Compare commits

..

11 Commits

Author SHA1 Message Date
J. Nick Koston 51cddb88f5 Merge remote-tracking branch 'upstream/dev' into ci-uv-managed-python
# Conflicts:
#	.github/workflows/ci.yaml
2026-05-26 18:24:40 -05:00
J. Nick Koston 748a9842af Merge branch 'ci-cache-postgres-mariadb-deps' into ci-uv-managed-python 2026-05-21 14:33:44 -05:00
J. Nick Koston 55786dbdfc Use dpkg-architecture to derive multiarch lib path
So the ldconfig workaround also works on non-x86_64 runners.
2026-05-21 14:32:58 -05:00
J. Nick Koston e88c03a437 Merge branch 'dev' into ci-cache-postgres-mariadb-deps 2026-05-21 13:37:03 -05:00
J. Nick Koston dbc0dc1ea6 Install Python eagerly via setup-uv-python composite
setup-uv only sets UV_PYTHON, it does not actually fetch the
interpreter; uv installs it lazily on the first 'uv venv' or
'uv pip install'. When the venv cache hits, no uv command runs, so
the cached venv's bin/python3 symlink points at an interpreter that
was never installed in this job and the next step that activates the
venv aborts with 'broken symlink'.

Extract setup-uv plus an explicit 'uv python install' into the
.github/actions/setup-uv-python composite action so every job that
restores the venv ends up with a real Python at the expected path.
Enable cache-python in the wrapper so subsequent jobs reuse astral's
download instead of refetching it.
2026-05-21 13:16:39 -05:00
J. Nick Koston 31271876bf Pin uv version in every setup-uv call to skip manifest fetch
Mirrors esphome/esphome#16534. Without an explicit version, setup-uv
fetches uv.ndjson from raw.githubusercontent.com on every cache miss,
which periodically times out and fails the job. Expose the uv version
from requirements.txt via the info job and pass it to every setup-uv
call. Also set ignore-nothing-to-cache: true so jobs that do not touch
uv (e.g. gen-copilot-instructions) no longer fail on the post-step
cache save.
2026-05-21 13:08:57 -05:00
J. Nick Koston d5c31332b5 Switch CI to astral-managed Python via setup-uv
Replace actions/setup-python with astral-sh/setup-uv so every job uses
the python-build-standalone interpreter astral ships, which bakes in
the PEP 744 tail call interpreter on 3.14. setup-uv handles both
installing uv and provisioning the requested Python version, so the
venv bootstrap uses 'uv venv' instead of 'python -m venv' and there is
no longer a separate uv install step on cache miss.

Bumps CACHE_VERSION so the old setup-python venv caches are invalidated;
the venv symlinks would otherwise point at the absent hostedtoolcache
interpreter.
2026-05-21 13:05:30 -05:00
J. Nick Koston 3f0c93c26c Merge branch 'dev' into ci-cache-postgres-mariadb-deps 2026-05-21 12:48:19 -05:00
J. Nick Koston 07ed913ba2 Extract apt caching into composite action with alternatives workaround
Wrap awalsh128/cache-apt-pkgs-action in .github/actions/cache-apt-packages
so every job uses the same pattern, and route /usr/lib/x86_64-linux-gnu
subdirectories through ldconfig. The upstream action does not run postinst
on cache restore so update-alternatives symlinks (libblas, liblapack via
ffmpeg) never appear; adding the subdirs to ld.so.conf.d lets the linker
find the real libraries without those symlinks.
2026-05-21 10:45:13 -05:00
J. Nick Koston b7905b163f Run ldconfig after cache-apt-pkgs-action restore
The action restores cached .deb files to disk but skips dpkg-trigger so
/etc/ld.so.cache stays stale and ctypes-based loaders (eg opuslib)
cannot find libopus.so.0. Add an explicit ldconfig step after each
action call.
2026-05-21 10:02:39 -05:00
J. Nick Koston c712b07da3 Switch CI apt caching to awalsh128/cache-apt-pkgs-action 2026-05-21 09:42:20 -05:00
28 changed files with 127 additions and 182 deletions
@@ -0,0 +1,42 @@
name: Set up uv and managed Python
description: >-
Pins uv (avoids the raw.githubusercontent.com manifest fetch on cache miss)
and proactively installs the requested Python so cached venvs created with
`uv venv` resolve their interpreter symlinks in jobs that only restore the
venv. setup-uv alone only sets UV_PYTHON, it does not actually fetch the
interpreter until uv first uses it, so jobs that just activate the venv
blow up with broken symlinks on cache hit.
inputs:
python-version:
description: The Python version uv should install and use.
required: true
uv-version:
description: The uv version setup-uv should install.
required: true
outputs:
python-version:
description: The Python version uv reports as installed.
value: ${{ steps.uv.outputs.python-version }}
runs:
using: composite
steps:
- name: Set up uv
id: uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: ${{ inputs.uv-version }}
python-version: ${{ inputs.python-version }}
# Persist astral's managed Python across jobs so 'uv venv' below is
# fast on the second job onwards.
cache-python: true
# Lint-only and codegen jobs touch no Python deps, so the post-step
# cache save would otherwise abort the job.
ignore-nothing-to-cache: true
- name: Install Python interpreter
shell: bash
env:
PYTHON_VERSION: ${{ inputs.python-version }}
run: uv python install "${PYTHON_VERSION}"
+7 -7
View File
@@ -36,7 +36,7 @@
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
# - github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8
# - github/gh-aw-actions/setup@d3abfe96a194bce3a523ed2093ddedd5704cdf62 # v0.74.4
#
# Container images used:
# - ghcr.io/github/gh-aw-firewall/agent:0.25.46
@@ -90,7 +90,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8
uses: github/gh-aw-actions/setup@d3abfe96a194bce3a523ed2093ddedd5704cdf62 # v0.74.4
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -352,7 +352,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8
uses: github/gh-aw-actions/setup@d3abfe96a194bce3a523ed2093ddedd5704cdf62 # v0.74.4
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -961,7 +961,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8
uses: github/gh-aw-actions/setup@d3abfe96a194bce3a523ed2093ddedd5704cdf62 # v0.74.4
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1100,7 +1100,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8
uses: github/gh-aw-actions/setup@d3abfe96a194bce3a523ed2093ddedd5704cdf62 # v0.74.4
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1325,7 +1325,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8
uses: github/gh-aw-actions/setup@d3abfe96a194bce3a523ed2093ddedd5704cdf62 # v0.74.4
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1383,7 +1383,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8
uses: github/gh-aw-actions/setup@d3abfe96a194bce3a523ed2093ddedd5704cdf62 # v0.74.4
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
+45 -47
View File
@@ -37,7 +37,7 @@ on:
type: boolean
env:
CACHE_VERSION: 3
CACHE_VERSION: 4
MYPY_CACHE_VERSION: 1
HA_SHORT_VERSION: "2026.6"
ADDITIONAL_PYTHON_VERSIONS: "[]"
@@ -89,6 +89,8 @@ jobs:
mariadb_groups: ${{ steps.info.outputs.mariadb_groups }}
postgresql_groups: ${{ steps.info.outputs.postgresql_groups }}
python_versions: ${{ steps.info.outputs.python_versions }}
default_python: ${{ steps.info.outputs.default_python }}
uv_version: ${{ steps.info.outputs.uv_version }}
test_full_suite: ${{ steps.info.outputs.test_full_suite }}
test_group_count: ${{ steps.info.outputs.test_group_count }}
test_groups: ${{ steps.info.outputs.test_groups }}
@@ -235,6 +237,11 @@ jobs:
echo "postgresql_groups=${postgresql_groups}" >> $GITHUB_OUTPUT
echo "python_versions: ${all_python_versions}"
echo "python_versions=${all_python_versions}" >> $GITHUB_OUTPUT
echo "default_python: ${default_python}"
echo "default_python=${default_python}" >> $GITHUB_OUTPUT
uv_version=$(grep '^uv==' requirements.txt | cut -d'=' -f3)
echo "uv_version: ${uv_version}"
echo "uv_version=${uv_version}" >> $GITHUB_OUTPUT
echo "test_full_suite: ${test_full_suite}"
echo "test_full_suite=${test_full_suite}" >> $GITHUB_OUTPUT
echo "integrations_glob: ${integrations_glob}"
@@ -344,12 +351,12 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
- name: Set up uv and Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: ./.github/actions/setup-uv-python
with:
uv-version: ${{ needs.info.outputs.uv_version }}
python-version: ${{ matrix.python-version }}
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
@@ -397,21 +404,13 @@ jobs:
libudev-dev
version: ${{ env.APT_CACHE_VERSION }}
execute_install_scripts: true
- name: Read uv version from requirements.txt
if: steps.cache-venv.outputs.cache-hit != 'true'
id: read-uv-version
run: |
echo "version=$(grep '^uv==' requirements.txt | cut -d'=' -f3)" >> "$GITHUB_OUTPUT"
- name: Set up uv
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: ${{ steps.read-uv-version.outputs.version }}
- name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true'
id: create-venv
env:
PYTHON_VERSION: ${{ steps.python.outputs.python-version }}
run: |
python -m venv venv
uv venv venv --python "${PYTHON_VERSION}"
. venv/bin/activate
python --version
uv pip install -r requirements.txt
@@ -419,7 +418,6 @@ jobs:
uv pip install -e . --config-settings editable_mode=compat
- name: Dump pip freeze
run: |
python -m venv venv
. venv/bin/activate
python --version
uv pip freeze >> pip_freeze.txt
@@ -480,10 +478,10 @@ jobs:
version: ${{ env.APT_CACHE_VERSION }}
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: ./.github/actions/setup-uv-python
with:
python-version-file: ".python-version"
check-latest: true
uv-version: ${{ needs.info.outputs.uv_version }}
python-version: ${{ needs.info.outputs.default_python }}
- name: Restore full Python virtual environment
id: cache-venv
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
@@ -517,10 +515,10 @@ jobs:
persist-credentials: false
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: ./.github/actions/setup-uv-python
with:
python-version-file: ".python-version"
check-latest: true
uv-version: ${{ needs.info.outputs.uv_version }}
python-version: ${{ needs.info.outputs.default_python }}
- name: Restore full Python virtual environment
id: cache-venv
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
@@ -553,10 +551,10 @@ jobs:
persist-credentials: false
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: ./.github/actions/setup-uv-python
with:
python-version-file: ".python-version"
check-latest: true
uv-version: ${{ needs.info.outputs.uv_version }}
python-version: ${{ needs.info.outputs.default_python }}
- name: Run gen_copilot_instructions.py
run: |
python -m script.gen_copilot_instructions validate
@@ -608,10 +606,10 @@ jobs:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: ./.github/actions/setup-uv-python
with:
uv-version: ${{ needs.info.outputs.uv_version }}
python-version: ${{ matrix.python-version }}
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
@@ -659,10 +657,10 @@ jobs:
persist-credentials: false
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: ./.github/actions/setup-uv-python
with:
python-version-file: ".python-version"
check-latest: true
uv-version: ${{ needs.info.outputs.uv_version }}
python-version: ${{ needs.info.outputs.default_python }}
- name: Restore full Python virtual environment
id: cache-venv
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
@@ -712,10 +710,10 @@ jobs:
persist-credentials: false
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: ./.github/actions/setup-uv-python
with:
python-version-file: ".python-version"
check-latest: true
uv-version: ${{ needs.info.outputs.uv_version }}
python-version: ${{ needs.info.outputs.default_python }}
- name: Restore full Python virtual environment
id: cache-venv
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
@@ -763,10 +761,10 @@ jobs:
persist-credentials: false
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: ./.github/actions/setup-uv-python
with:
python-version-file: ".python-version"
check-latest: true
uv-version: ${{ needs.info.outputs.uv_version }}
python-version: ${{ needs.info.outputs.default_python }}
- name: Generate partial mypy restore key
id: generate-mypy-key
run: |
@@ -840,10 +838,10 @@ jobs:
execute_install_scripts: true
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: ./.github/actions/setup-uv-python
with:
python-version-file: ".python-version"
check-latest: true
uv-version: ${{ needs.info.outputs.uv_version }}
python-version: ${{ needs.info.outputs.default_python }}
- name: Restore full Python virtual environment
id: cache-venv
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
@@ -905,10 +903,10 @@ jobs:
execute_install_scripts: true
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: ./.github/actions/setup-uv-python
with:
uv-version: ${{ needs.info.outputs.uv_version }}
python-version: ${{ matrix.python-version }}
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
@@ -1047,10 +1045,10 @@ jobs:
execute_install_scripts: true
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: ./.github/actions/setup-uv-python
with:
uv-version: ${{ needs.info.outputs.uv_version }}
python-version: ${{ matrix.python-version }}
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
@@ -1203,10 +1201,10 @@ jobs:
version: ${{ env.APT_CACHE_VERSION }}
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: ./.github/actions/setup-uv-python
with:
uv-version: ${{ needs.info.outputs.uv_version }}
python-version: ${{ matrix.python-version }}
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
@@ -1371,10 +1369,10 @@ jobs:
execute_install_scripts: true
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: ./.github/actions/setup-uv-python
with:
uv-version: ${{ needs.info.outputs.uv_version }}
python-version: ${{ matrix.python-version }}
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
Generated
+2 -2
View File
@@ -2054,8 +2054,8 @@ CLAUDE.md @home-assistant/core
/tests/components/yamaha_musiccast/ @vigonotion @micha91
/homeassistant/components/yandex_transport/ @rishatik92 @devbis
/tests/components/yandex_transport/ @rishatik92 @devbis
/homeassistant/components/yardian/ @aeon-matrix
/tests/components/yardian/ @aeon-matrix
/homeassistant/components/yardian/ @h3l1o5
/tests/components/yardian/ @h3l1o5
/homeassistant/components/yeelight/ @zewelor @shenxn @starkillerOG @alexyao2015
/tests/components/yeelight/ @zewelor @shenxn @starkillerOG @alexyao2015
/homeassistant/components/yeelightsunflower/ @lindsaymarkward
@@ -51,6 +51,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: S3ConfigEntry) -> bool:
translation_key="invalid_bucket_name",
) from err
except ValueError as err:
# pylint: disable-next=home-assistant-exception-translation-key-missing
raise ConfigEntryError(
translation_domain=DOMAIN,
translation_key="invalid_endpoint_url",
+1 -4
View File
@@ -7,7 +7,7 @@
"cannot_connect": "[%key:component::aws_s3::exceptions::cannot_connect::message%]",
"invalid_bucket_name": "[%key:component::aws_s3::exceptions::invalid_bucket_name::message%]",
"invalid_credentials": "[%key:component::aws_s3::exceptions::invalid_credentials::message%]",
"invalid_endpoint_url": "[%key:component::aws_s3::exceptions::invalid_endpoint_url::message%]"
"invalid_endpoint_url": "Invalid endpoint URL. Please make sure it's a valid AWS S3 endpoint URL."
},
"step": {
"user": {
@@ -48,9 +48,6 @@
},
"invalid_credentials": {
"message": "Bucket cannot be accessed using provided combination of access key ID and secret access key."
},
"invalid_endpoint_url": {
"message": "Invalid endpoint URL. Please make sure it's a valid AWS S3 endpoint URL."
}
}
}
@@ -20,7 +20,7 @@
"bluetooth-adapters==2.3.0",
"bluetooth-auto-recovery==1.6.4",
"bluetooth-data-tools==1.29.18",
"dbus-fast==5.0.15",
"habluetooth==6.7.9"
"dbus-fast==5.0.14",
"habluetooth==6.7.4"
]
}
@@ -38,9 +38,6 @@ from homeassistant.const import (
from homeassistant.core import Event, HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.entity_platform import (
async_create_platform_config_not_supported_issue,
)
from homeassistant.helpers.event import (
async_track_time_interval,
async_track_utc_time_change,
@@ -382,8 +379,8 @@ async def async_extract_config(
if platform.type == PLATFORM_TYPE_LEGACY:
legacy.append(platform)
else:
async_create_platform_config_not_supported_issue(
hass, platform.name, DOMAIN
raise ValueError(
f"Unable to determine type for {platform.name}: {platform.type}"
)
return legacy
@@ -19,7 +19,7 @@ DEFAULT_STT_PROMPT = "Transcribe the attached audio"
CONF_RECOMMENDED = "recommended"
CONF_CHAT_MODEL = "chat_model"
RECOMMENDED_CHAT_MODEL = "models/gemini-3.1-flash-lite"
RECOMMENDED_CHAT_MODEL = "models/gemini-2.5-flash"
RECOMMENDED_STT_MODEL = RECOMMENDED_CHAT_MODEL
RECOMMENDED_TTS_MODEL = "models/gemini-2.5-flash-preview-tts"
RECOMMENDED_IMAGE_MODEL = "models/gemini-2.5-flash-image"
@@ -2,7 +2,7 @@
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any, override
from typing import Any
from incomfortclient import Heater as InComfortHeater
@@ -97,13 +97,11 @@ class IncomfortBinarySensor(IncomfortBoilerEntity, BinarySensorEntity):
self._attr_unique_id = f"{heater.serial_no}_{description.key}"
@property
@override
def is_on(self) -> bool:
"""Return the status of the sensor."""
return bool(self._heater.status[self.entity_description.value_key])
@property
@override
def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return the device state attributes."""
if (attributes_fn := self.entity_description.extra_state_attributes_fn) is None:
@@ -1,6 +1,6 @@
"""Support for an Intergas boiler via an InComfort/InTouch Lan2RF gateway."""
from typing import Any, override
from typing import Any
from incomfortclient import Heater as InComfortHeater, Room as InComfortRoom
@@ -76,19 +76,16 @@ class InComfortClimate(IncomfortEntity, ClimateEntity):
)
@property
@override
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the device state attributes."""
return {"status": self._room.status}
@property
@override
def current_temperature(self) -> float | None:
"""Return the current temperature."""
return self._room.room_temp
@property
@override
def hvac_action(self) -> HVACAction | None:
"""Return the actual current HVAC action."""
if self._heater.is_burning and self._heater.is_pumping:
@@ -96,7 +93,6 @@ class InComfortClimate(IncomfortEntity, ClimateEntity):
return HVACAction.IDLE
@property
@override
def target_temperature(self) -> float | None:
"""Return the (override)temperature we try to reach.
@@ -110,13 +106,11 @@ class InComfortClimate(IncomfortEntity, ClimateEntity):
return self._room.setpoint
return self._room.override or self._room.setpoint
@override
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set a new target temperature for this zone."""
temperature: float = kwargs[ATTR_TEMPERATURE]
await self._room.set_override(temperature)
await self.coordinator.async_refresh()
@override
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new target hvac mode."""
@@ -2,7 +2,7 @@
from collections.abc import Mapping
import logging
from typing import Any, override
from typing import Any
from incomfortclient import InvalidGateway, InvalidHeaterList
import voluptuous as vol
@@ -100,7 +100,6 @@ class InComfortConfigFlow(ConfigFlow, domain=DOMAIN):
_discovered_host: str
@override
@staticmethod
@callback
def async_get_options_flow(
@@ -109,7 +108,6 @@ class InComfortConfigFlow(ConfigFlow, domain=DOMAIN):
"""Get the options flow for this handler."""
return InComfortOptionsFlowHandler()
@override
async def async_step_dhcp(
self, discovery_info: DhcpServiceInfo
) -> ConfigFlowResult:
@@ -171,7 +169,6 @@ class InComfortConfigFlow(ConfigFlow, domain=DOMAIN):
description_placeholders={CONF_HOST: self._discovered_host},
)
@override
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@@ -3,7 +3,7 @@
from dataclasses import dataclass, field
from datetime import timedelta
import logging
from typing import Any, override
from typing import Any
from aiohttp import ClientResponseError
from incomfortclient import (
@@ -74,7 +74,6 @@ class InComfortDataCoordinator(DataUpdateCoordinator[InComfortData]):
)
self.incomfort_data = incomfort_data
@override
async def _async_update_data(self) -> InComfortData:
"""Fetch data from API endpoint."""
try:
+1 -3
View File
@@ -1,7 +1,7 @@
"""Support for an Intergas heater via an InComfort/InTouch Lan2RF gateway."""
from dataclasses import dataclass
from typing import Any, override
from typing import Any
from incomfortclient import Heater as InComfortHeater
@@ -104,13 +104,11 @@ class IncomfortSensor(IncomfortBoilerEntity, SensorEntity):
self._attr_unique_id = f"{heater.serial_no}_{description.key}"
@property
@override
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self._heater.status[self.entity_description.value_key] # type: ignore [no-any-return]
@property
@override
def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return the device state attributes."""
if (extra_key := self.entity_description.extra_key) is None:
@@ -1,7 +1,7 @@
"""Support for an Intergas boiler via an InComfort/Intouch Lan2RF gateway."""
import logging
from typing import Any, override
from typing import Any
from incomfortclient import Heater as InComfortHeater
@@ -49,13 +49,11 @@ class IncomfortWaterHeater(IncomfortBoilerEntity, WaterHeaterEntity):
self._attr_unique_id = heater.serial_no
@property
@override
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the device state attributes."""
return {k: v for k, v in self._heater.status.items() if k in HEATER_ATTRS}
@property
@override
def current_temperature(self) -> float | None:
"""Return the current temperature."""
if self._heater.is_tapping:
@@ -69,7 +67,6 @@ class IncomfortWaterHeater(IncomfortBoilerEntity, WaterHeaterEntity):
return max(self._heater.heater_temp, self._heater.tap_temp)
@property
@override
def current_operation(self) -> str | None:
"""Return the current operation mode."""
return self._heater.display_text
@@ -111,11 +111,8 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, OpowerData]]):
raise ConfigEntryAuthFailed from err
except CannotConnect as err:
_LOGGER.error("Error during login: %s", err)
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="login_error",
translation_placeholders={"error": str(err)},
) from err
# pylint: disable-next=home-assistant-exception-not-translated
raise UpdateFailed(f"Error during login: {err}") from err
try:
accounts = await self.api.async_get_accounts()
@@ -124,11 +124,6 @@
}
}
},
"exceptions": {
"login_error": {
"message": "Error during login: {error}"
}
},
"issues": {
"return_to_grid_migration": {
"description": "We found negative values in your existing consumption statistics, likely because you have solar. We split those into separate export statistics for a better experience in the Energy dashboard.\n\nPlease visit the [Energy configuration page]({energy_settings}) to add the following statistics in the **Energy exported to grid** section:\n\n{target_ids}\n\nOnce you have added them, ignore this issue.",
@@ -21,5 +21,5 @@
"documentation": "https://www.home-assistant.io/integrations/qingping",
"integration_type": "device",
"iot_class": "local_push",
"requirements": ["qingping-ble==1.1.5"]
"requirements": ["qingping-ble==1.1.4"]
}
@@ -217,9 +217,9 @@
"home": "[%key:common::entity::button::home::name%]",
"insert": "Insert",
"left": "[%key:common::entity::button::left::name%]",
"lights_kbd_down": "Keyboard backlight brightness down",
"lights_kbd_down": "Keyboasrd backlight brightness down",
"lights_kbd_toggle": "Toggle keyboard backlight",
"lights_kbd_up": "Keyboard backlight brightness up",
"lights_kbd_up": "Keyboard backlight brighness up",
"lights_mon_down": "Display brightness down",
"lights_mon_up": "Display brightness up",
"numpad_0": "NumPad 0",
@@ -1,7 +1,7 @@
{
"domain": "yardian",
"name": "Yardian",
"codeowners": ["@aeon-matrix"],
"codeowners": ["@h3l1o5"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/yardian",
"integration_type": "device",
+2 -2
View File
@@ -30,12 +30,12 @@ certifi>=2021.5.30
ciso8601==2.3.3
cronsim==2.7
cryptography==48.0.0
dbus-fast==5.0.15
dbus-fast==5.0.14
file-read-backwards==2.0.0
fnv-hash-fast==2.0.3
go2rtc-client==0.4.0
ha-ffmpeg==3.2.2
habluetooth==6.7.9
habluetooth==6.7.4
hass-nabucasa==2.2.0
hassil==3.5.0
home-assistant-bluetooth==2.0.0
+3 -3
View File
@@ -797,7 +797,7 @@ datadog==0.52.0
datapoint==0.12.1
# homeassistant.components.bluetooth
dbus-fast==5.0.15
dbus-fast==5.0.14
# homeassistant.components.debugpy
debugpy==1.8.17
@@ -1213,7 +1213,7 @@ ha-xthings-cloud==1.0.5
habiticalib==0.4.7
# homeassistant.components.bluetooth
habluetooth==6.7.9
habluetooth==6.7.4
# homeassistant.components.hanna
hanna-cloud==0.0.7
@@ -2839,7 +2839,7 @@ qbittorrent-api==2026.5.1
qbusmqttapi==1.5.0
# homeassistant.components.qingping
qingping-ble==1.1.5
qingping-ble==1.1.4
# homeassistant.components.qnap
qnapstats==0.4.0
-3
View File
@@ -2,7 +2,6 @@
import asyncio
from datetime import timedelta
import sys
import time
from typing import Any
from unittest.mock import AsyncMock, MagicMock, Mock, patch
@@ -100,7 +99,6 @@ async def test_setup_and_stop(
assert len(mock_bleak_scanner_start.mock_calls) == 1
@pytest.mark.skipif(sys.platform != "linux", reason="Requires Linux BlueZ scanner")
@pytest.mark.parametrize(
"options",
[{CONF_MODE: "passive"}, {CONF_PASSIVE: True}],
@@ -163,7 +161,6 @@ async def test_setup_and_stop_passive(
}
@pytest.mark.skipif(sys.platform != "linux", reason="Requires Linux BlueZ scanner")
async def test_setup_and_stop_old_bluez(
hass: HomeAssistant,
mock_bleak_scanner_start: MagicMock,
+1 -63
View File
@@ -28,7 +28,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, State, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import discovery, issue_registry as ir
from homeassistant.helpers import discovery
from homeassistant.helpers.discovery import DiscoveryInfoType
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.json import JSONEncoder
@@ -821,65 +821,3 @@ async def test_modern_platform_setup(hass: HomeAssistant) -> None:
"in_zones": [],
"source_type": SourceType.ROUTER,
}
async def test_unsupported_legacy_config_creates_issue(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test unsupported legacy config creates issue."""
integration_domain = "test"
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
hass.async_create_task(
discovery.async_load_platform(
hass, "device_tracker", integration_domain, {}, config
)
)
return True
mock_integration(
hass,
MockModule(integration_domain, async_setup=async_setup),
)
mock_platform(
hass,
f"{integration_domain}.device_tracker",
MockPlatform(),
)
await async_setup_component(hass, "homeassistant", {})
await async_setup_component(
hass,
device_tracker.DOMAIN,
{device_tracker.DOMAIN: {"platform": integration_domain, "something": "value"}},
)
await async_setup_component(hass, integration_domain, {})
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
assert len(hass.states.async_all(device_tracker.DOMAIN)) == 0
assert (
f"The {integration_domain} platform for the {device_tracker.DOMAIN} integration does not support platform"
" setup, please remove it from your config" in caplog.text
)
issue = issue_registry.async_get_issue(
"homeassistant",
f"platform_integration_no_support_{device_tracker.DOMAIN}_{integration_domain}",
)
assert issue
assert issue.issue_domain == integration_domain
assert issue.learn_more_url is None
assert issue.translation_key == "platform_setup_not_supported"
assert issue.severity == ir.IssueSeverity.ERROR
assert issue.translation_placeholders == {
"platform_domain": device_tracker.DOMAIN,
"integration_domain": integration_domain,
"platform_key": f"platform: {integration_domain}",
"yaml_example": f"```yaml\n{device_tracker.DOMAIN}:\n - platform: {integration_domain}\n```",
}
@@ -18,7 +18,7 @@
}),
'ulid-conversation': dict({
'data': dict({
'chat_model': 'models/gemini-3.1-flash-lite',
'chat_model': 'models/gemini-2.5-flash',
'dangerous_block_threshold': 'BLOCK_MEDIUM_AND_ABOVE',
'harassment_block_threshold': 'BLOCK_MEDIUM_AND_ABOVE',
'hate_block_threshold': 'BLOCK_MEDIUM_AND_ABOVE',
@@ -21,7 +21,7 @@
'labels': set({
}),
'manufacturer': 'Google',
'model': 'gemini-3.1-flash-lite',
'model': 'gemini-2.5-flash',
'model_id': None,
'name': 'Google AI Conversation',
'name_by_user': None,
@@ -50,7 +50,7 @@
'labels': set({
}),
'manufacturer': 'Google',
'model': 'gemini-3.1-flash-lite',
'model': 'gemini-2.5-flash',
'model_id': None,
'name': 'Google AI STT',
'name_by_user': None,
@@ -108,7 +108,7 @@
'labels': set({
}),
'manufacturer': 'Google',
'model': 'gemini-3.1-flash-lite',
'model': 'gemini-2.5-flash',
'model_id': None,
'name': 'Google AI Task',
'name_by_user': None,
@@ -51,7 +51,7 @@ def get_models_pager():
model_25_flash = Mock(
supported_actions=["generateContent"],
)
model_25_flash.name = "models/gemini-3.1-flash-lite"
model_25_flash.name = "models/gemini-2.5-flash"
model_20_flash = Mock(
supported_actions=["generateContent"],
@@ -21,7 +21,7 @@ from . import API_ERROR_500, CLIENT_ERROR_BAD_REQUEST
from tests.common import MockConfigEntry
TEST_CHAT_MODEL = "models/gemini-3.1-flash-lite"
TEST_CHAT_MODEL = "models/gemini-2.5-flash"
TEST_PROMPT = "Please transcribe the audio."