Compare commits

..

7 Commits

Author SHA1 Message Date
Franck Nijhof ce72faa016 Suppress existing exception translation violations
Add pylint disable-next comments for all 227 existing violations and
set exception-translations to todo for 30 non-Gold/Platinum integrations
that had incorrectly marked the rule as done.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-20 12:17:52 +00:00
Franck Nijhof 7f8b064534 Correct manifests for non-gold/non-platinum for W7417 2026-05-20 10:42:14 +00:00
Franck Nijhof b033d0e1c0 Check domain/key mismatch before hardcoded string check, use orjson
Move the translation_key/translation_domain mismatch check (E7408)
before the hardcoded string check so that cases like
raise HomeAssistantError("msg", translation_domain=DOMAIN) are
correctly flagged as missing translation_key.

Also switch from json to orjson for faster translation file parsing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-20 09:50:32 +00:00
Franck Nijhof 7d0b05315f Use orjson, it is just faster 2026-05-20 09:43:26 +00:00
Franck Nijhof c9611e358c Address review feedback for exception translations checker
- Flag extra placeholders when strings.json expects none
- Validate parsed translation JSON is a dict before caching
- Add test for extra placeholders case

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-20 09:28:30 +00:00
Franck Nijhof 37001ea0c2 Correct module level doc 2026-05-20 09:11:21 +00:00
Franck Nijhof dae25fa3cd Add pylint checker for exception translation validation 2026-05-20 09:08:35 +00:00
208 changed files with 1459 additions and 1631 deletions
@@ -18,13 +18,6 @@ description: Reviews GitHub pull requests and provides feedback comments. This i
4. Ensure any existing review comments have been addressed.
5. Generate constructive review comments in the CONSOLE. DO NOT POST TO GITHUB YOURSELF.
## Verification:
- After the review, run parallel subagents for each finding to double check it.
- Spawn up to a maximum of 10 parallel subagents at a time.
- Gather the results from the subagents and summarize them in the final review comments.
## IMPORTANT:
- Just review. DO NOT make any changes
- Be constructive and specific in your comments
+1 -1
View File
@@ -14,7 +14,7 @@ env:
UV_HTTP_TIMEOUT: 60
UV_SYSTEM_PYTHON: "true"
# Base image version from https://github.com/home-assistant/docker
BASE_IMAGE_VERSION: "2026.05.0"
BASE_IMAGE_VERSION: "2026.04.0"
ARCHITECTURES: '["amd64", "aarch64"]'
permissions: {}
@@ -236,7 +236,7 @@ jobs:
- name: Detect duplicates using AI
id: ai_detection
if: steps.extract.outputs.should_continue == 'true' && steps.fetch_similar.outputs.has_similar == 'true'
uses: actions/ai-inference@17ff458cb182449bbb2e43701fcd98f6af8f6570 # v2.1.0
uses: actions/ai-inference@e09e65981758de8b2fdab13c2bfb7c7d5493b0b6 # v2.0.7
with:
model: openai/gpt-4o
system-prompt: |
@@ -62,7 +62,7 @@ jobs:
- name: Detect language using AI
id: ai_language_detection
if: steps.detect_language.outputs.should_continue == 'true'
uses: actions/ai-inference@17ff458cb182449bbb2e43701fcd98f6af8f6570 # v2.1.0
uses: actions/ai-inference@e09e65981758de8b2fdab13c2bfb7c7d5493b0b6 # v2.0.7
with:
model: openai/gpt-4o-mini
system-prompt: |
+1 -1
View File
@@ -1 +1 @@
3.14.5
3.14.4
@@ -81,8 +81,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> boo
) as err:
raise ConfigEntryAuthFailed from err
except AirOSKeyDataMissingError as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryError("key_data_missing") from err
except Exception as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryError("unknown") from err
airos_class: type[AirOS8 | AirOS6] = (
@@ -91,6 +91,7 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
translation_placeholders={"error": repr(err)},
) from err
except CannotAuthenticate as err:
# pylint: disable-next=home-assistant-exception-translation-key-missing
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="invalid_auth",
@@ -82,7 +82,7 @@ rules:
comment: |
This integration does not have any entities that should disabled by default.
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow: todo
repair-issues:
@@ -67,7 +67,7 @@ rules:
comment: |
Only one entity type (device_tracker) is created, making this not applicable.
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow:
status: todo
+2 -2
View File
@@ -205,9 +205,9 @@ class AveaLight(LightEntity):
def turn_off(self, **kwargs: Any) -> None:
"""Instruct the light to turn off."""
if self._attr_brightness:
self._last_brightness = self._attr_brightness
self._light.set_brightness(0)
self._attr_is_on = False
self._attr_brightness = 0
def update(self) -> None:
"""Fetch new state data for this light."""
+1 -1
View File
@@ -14,5 +14,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["avea"],
"requirements": ["avea==1.8.0"]
"requirements": ["avea==1.6.1"]
}
@@ -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",
@@ -72,7 +72,7 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations:
status: exempt
comment: This integration does not use icons.
@@ -59,18 +59,21 @@ async def async_setup_entry(
if not await container_client.exists():
await container_client.create_container()
except ResourceNotFoundError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ConfigEntryError(
translation_domain=DOMAIN,
translation_key="account_not_found",
translation_placeholders={CONF_ACCOUNT_NAME: entry.data[CONF_ACCOUNT_NAME]},
) from err
except ClientAuthenticationError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="invalid_auth",
translation_placeholders={CONF_ACCOUNT_NAME: entry.data[CONF_ACCOUNT_NAME]},
) from err
except AzureError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="cannot_connect",
@@ -75,11 +75,13 @@ def handle_backup_errors[_R, **P](
err.message,
exc_info=True,
)
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupAgentError(
f"Error during backup operation in {func.__name__}:"
f" Status {err.status_code}, message: {err.message}"
) from err
except ServiceRequestError as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupAgentError(
f"Timeout during backup operation in {func.__name__}"
) from err
@@ -90,6 +92,7 @@ def handle_backup_errors[_R, **P](
err,
exc_info=True,
)
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupAgentError(
f"Error during backup operation in {func.__name__}: {err}"
) from err
@@ -118,6 +121,7 @@ class AzureStorageBackupAgent(BackupAgent):
"""Download a backup file."""
blob = await self._find_blob_by_backup_id(backup_id)
if blob is None:
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupNotFound(f"Backup {backup_id} not found")
download_stream = await self._client.download_blob(blob.name)
return download_stream.chunks()
@@ -155,6 +159,7 @@ class AzureStorageBackupAgent(BackupAgent):
"""Delete a backup file."""
blob = await self._find_blob_by_backup_id(backup_id)
if blob is None:
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupNotFound(f"Backup {backup_id} not found")
await self._client.delete_blob(blob.name)
@@ -181,6 +186,7 @@ class AzureStorageBackupAgent(BackupAgent):
"""Return a backup."""
blob = await self._find_blob_by_backup_id(backup_id)
if blob is None:
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupNotFound(f"Backup {backup_id} not found")
return AgentBackup.from_dict(json.loads(blob.metadata["backup_metadata"]))
@@ -89,6 +89,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: BackblazeConfigEntry) ->
translation_key="cannot_connect",
) from err
except exception.MissingAccountData as err:
# pylint: disable-next=home-assistant-exception-translation-key-missing
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="invalid_auth",
@@ -96,7 +96,7 @@ rules:
entity-translations:
status: exempt
comment: This integration does not have entities.
exception-translations: done
exception-translations: todo
icon-translations:
status: exempt
comment: This integration does not use icons.
+2
View File
@@ -169,6 +169,7 @@ class BlinkCamera(CoordinatorEntity[BlinkUpdateCoordinator], Camera):
try:
await self._camera.save_recent_clips(output_dir=file_path)
except OSError as err:
# pylint: disable-next=home-assistant-exception-message-with-translation
raise ServiceValidationError(
str(err),
translation_domain=DOMAIN,
@@ -190,6 +191,7 @@ class BlinkCamera(CoordinatorEntity[BlinkUpdateCoordinator], Camera):
try:
await self._camera.video_to_file(filename)
except OSError as err:
# pylint: disable-next=home-assistant-exception-message-with-translation
raise ServiceValidationError(
str(err),
translation_domain=DOMAIN,
@@ -97,6 +97,7 @@ class BringDataUpdateCoordinator(BringBaseCoordinator[dict[str, BringData]]):
translation_key="setup_parse_exception",
) from e
except BringAuthException as e:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="setup_authentication_exception",
@@ -153,6 +154,7 @@ class BringDataUpdateCoordinator(BringBaseCoordinator[dict[str, BringData]]):
translation_key="setup_parse_exception",
) from e
except BringAuthException as e:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="setup_authentication_exception",
@@ -215,6 +217,7 @@ class BringActivityCoordinator(BringBaseCoordinator[dict[str, BringActivityData]
activity = await self.coordinator.bring.get_activity(lst.listUuid)
users = await self.coordinator.bring.get_list_users(lst.listUuid)
except BringAuthException as e:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="setup_authentication_exception",
@@ -55,6 +55,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
or not (entity := er.async_get(hass).async_get(call.data[ATTR_ENTITY_ID]))
or not entity.config_entry_id
):
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="entity_not_found",
@@ -183,6 +183,7 @@ class BSBLANClimate(BSBLanCircuitEntity, ClimateEntity):
try:
await self.coordinator.client.thermostat(**data, circuit=self._circuit)
except BSBLANError as err:
# pylint: disable-next=home-assistant-exception-message-with-translation
raise HomeAssistantError(
"An error occurred while updating the BSBLAN device",
translation_domain=DOMAIN,
@@ -8,7 +8,7 @@
"iot_class": "local_polling",
"loggers": ["bsblan"],
"quality_scale": "silver",
"requirements": ["python-bsblan==6.0.1"],
"requirements": ["python-bsblan==5.2.1"],
"zeroconf": [
{
"name": "bsb-lan*",
+1 -3
View File
@@ -17,8 +17,6 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from .const import TIMEOUT
type CalDavConfigEntry = ConfigEntry[caldav.DAVClient]
_LOGGER = logging.getLogger(__name__)
@@ -34,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: CalDavConfigEntry) -> bo
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
ssl_verify_cert=entry.data[CONF_VERIFY_SSL],
timeout=TIMEOUT,
timeout=30,
)
try:
await hass.async_add_executor_job(client.principal)
@@ -13,7 +13,7 @@ from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME, CONF_VERIFY_SSL
from homeassistant.helpers import config_validation as cv
from .const import DOMAIN, TIMEOUT
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -65,7 +65,6 @@ class CalDavConfigFlow(ConfigFlow, domain=DOMAIN):
username=user_input[CONF_USERNAME],
password=user_input[CONF_PASSWORD],
ssl_verify_cert=user_input[CONF_VERIFY_SSL],
timeout=TIMEOUT,
)
try:
await self.hass.async_add_executor_job(client.principal)
@@ -76,9 +75,6 @@ class CalDavConfigFlow(ConfigFlow, domain=DOMAIN):
# AuthorizationError can be raised if the url is incorrect or
# on some other unexpected server response.
return "cannot_connect"
except requests.Timeout as err:
_LOGGER.warning("Timeout connecting to CalDAV server: %s", err)
return "cannot_connect"
except requests.ConnectionError as err:
_LOGGER.warning("Connection Error connecting to CalDAV server: %s", err)
return "cannot_connect"
-1
View File
@@ -3,4 +3,3 @@
from typing import Final
DOMAIN: Final = "caldav"
TIMEOUT: Final = 30
@@ -7,7 +7,6 @@ from homeassistant.const import CONF_ADDRESS, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN
from .coordinator import CasperGlowConfigEntry, CasperGlowCoordinator
PLATFORMS: list[Platform] = [
@@ -24,10 +23,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: CasperGlowConfigEntry) -
address: str = entry.data[CONF_ADDRESS]
ble_device = bluetooth.async_ble_device_from_address(hass, address.upper(), True)
if not ble_device:
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="device_not_found",
translation_placeholders={"address": address},
f"Could not find Casper Glow device with address {address}"
)
glow = CasperGlow(ble_device)
@@ -54,9 +54,6 @@
"exceptions": {
"communication_error": {
"message": "An error occurred while communicating with the Casper Glow: {error}"
},
"device_not_found": {
"message": "Could not find Casper Glow device with address {address}"
}
}
}
+4 -2
View File
@@ -17,8 +17,6 @@ from homeassistant.components.sensor import (
)
from homeassistant.const import (
APPLICATION_NAME,
ATTR_LATITUDE,
ATTR_LONGITUDE,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_NAME,
@@ -47,6 +45,10 @@ HA_USER_AGENT = (
)
ATTR_UID = "uid"
# pylint: disable-next=home-assistant-duplicate-const
ATTR_LATITUDE = "latitude"
# pylint: disable-next=home-assistant-duplicate-const
ATTR_LONGITUDE = "longitude"
ATTR_EMPTY_SLOTS = "empty_slots"
ATTR_FREE_EBIKES = "free_ebikes"
ATTR_TIMESTAMP = "timestamp"
+22 -22
View File
@@ -32,28 +32,28 @@ set_temperature:
max: 250
step: 0.1
mode: box
temperature_range:
fields:
target_temp_high:
filter:
supported_features:
- climate.ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
selector:
number:
min: 0
max: 250
step: 0.1
mode: box
target_temp_low:
filter:
supported_features:
- climate.ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
selector:
number:
min: 0
max: 250
step: 0.1
mode: box
target_temp_high:
filter:
supported_features:
- climate.ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
advanced: true
selector:
number:
min: 0
max: 250
step: 0.1
mode: box
target_temp_low:
filter:
supported_features:
- climate.ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
advanced: true
selector:
number:
min: 0
max: 250
step: 0.1
mode: box
hvac_mode:
selector:
state:
@@ -373,12 +373,7 @@
"name": "Target temperature"
}
},
"name": "Set thermostat target temperature",
"sections": {
"temperature_range": {
"name": "Temperature range"
}
}
"name": "Set thermostat target temperature"
},
"toggle": {
"description": "Toggles a thermostat on/off.",
@@ -94,7 +94,7 @@ rules:
entity-translations:
status: exempt
comment: This integration does not have entities.
exception-translations: done
exception-translations: todo
icon-translations:
status: exempt
comment: This integration does not use icons.
@@ -65,6 +65,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
translation_placeholders={"error": repr(err)},
) from err
except aiocomelit_exceptions.CannotAuthenticate as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise InvalidAuth(
translation_domain=DOMAIN,
translation_key="cannot_authenticate",
@@ -69,6 +69,7 @@ class CookidooDataUpdateCoordinator(DataUpdateCoordinator[CookidooData]):
translation_key="setup_request_exception",
) from e
except CookidooAuthException as e:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="setup_authentication_exception",
@@ -89,6 +90,7 @@ class CookidooDataUpdateCoordinator(DataUpdateCoordinator[CookidooData]):
try:
await self.cookidoo.refresh_token()
except CookidooAuthException as exc:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="setup_authentication_exception",
+1 -1
View File
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/dnsip",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["aiodns==4.0.4"]
"requirements": ["aiodns==4.0.3"]
}
@@ -60,6 +60,7 @@ class DuckDnsUpdateCoordinator(DataUpdateCoordinator[None]):
self.config_entry.data[CONF_ACCESS_TOKEN],
):
self.failed += 1
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="update_failed",
@@ -70,6 +71,7 @@ class DuckDnsUpdateCoordinator(DataUpdateCoordinator[None]):
)
except ClientError as e:
self.failed += 1
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="connection_error",
@@ -66,6 +66,7 @@ async def update_domain_service(call: ServiceCall) -> None:
entry.data[CONF_ACCESS_TOKEN],
txt=call.data.get(ATTR_TXT),
):
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="update_failed",
@@ -74,6 +75,7 @@ async def update_domain_service(call: ServiceCall) -> None:
},
)
except ClientError as e:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="connection_error",
+2 -5
View File
@@ -60,11 +60,8 @@ class DucoCoordinator(DataUpdateCoordinator[DucoData]):
translation_placeholders={"error": repr(err)},
) from err
except DucoError as err:
raise ConfigEntryError(
translation_domain=DOMAIN,
translation_key="api_error",
translation_placeholders={"error": repr(err)},
) from err
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryError(f"Duco API error: {err}") from err
async def _async_update_data(self) -> DucoData:
"""Fetch node data from the Duco box."""
@@ -353,6 +353,7 @@ class EcovacsVacuum(
if self._capability.clean.action.area is None:
info = self._device.device_info
name = info.get("nick", info["name"])
# pylint: disable-next=home-assistant-exception-translation-key-missing
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="vacuum_send_command_area_not_supported",
@@ -88,7 +88,7 @@ rules:
entity-translations:
status: exempt
comment: This integration does not create its own entities.
exception-translations: done
exception-translations: todo
icon-translations:
status: exempt
comment: This integration does not create its own entities.
@@ -356,6 +356,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
return
if entry.source == SOURCE_IGNORE:
# Don't call _fetch_device_info() for ignored entries
# pylint: disable-next=home-assistant-exception-not-translated
raise AbortFlow("already_configured")
configured_host: str | None = entry.data.get(CONF_HOST)
configured_port: int = entry.data.get(CONF_PORT, DEFAULT_PORT)
@@ -363,11 +364,13 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
if configured_host == host and (port is None or configured_port == port):
# Don't probe to verify the mac is correct since
# the host matches (and port matches if provided).
# pylint: disable-next=home-assistant-exception-not-translated
raise AbortFlow("already_configured")
# If the entry is loaded and the device is currently connected,
# don't update the host. This prevents transient mDNS announcements
# (e.g., during WiFi mesh roaming) from overwriting a working connection.
if entry.state is ConfigEntryState.LOADED and entry.runtime_data.available:
# pylint: disable-next=home-assistant-exception-not-translated
raise AbortFlow("already_configured")
configured_psk: str | None = entry.data.get(CONF_NOISE_PSK)
await self._fetch_device_info(host, port or configured_port, configured_psk)
@@ -364,6 +364,7 @@ class ESPHomeManager:
response_dict = {"response": response}
except TemplateError as ex:
# pylint: disable-next=home-assistant-exception-not-translated
raise HomeAssistantError(
f"Error rendering response template: {ex}"
) from ex
@@ -116,6 +116,7 @@ def _resolve_ctl_unique_id(
entry = er.async_get(hass).async_get(entity_id)
if entry is None:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="entity_not_found",
@@ -161,6 +162,7 @@ def _validate_set_system_mode_params(tcs: ControlSystem, data: dict[str, Any]) -
# via this service call
if (mode_info := tcs_modes.get(mode)) is None:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="mode_not_supported",
@@ -171,6 +173,7 @@ def _validate_set_system_mode_params(tcs: ControlSystem, data: dict[str, Any]) -
if not mode_info[SZ_CAN_BE_TEMPORARY]:
if ATTR_DURATION in data or ATTR_PERIOD in data:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="mode_cant_be_temporary",
@@ -181,6 +184,7 @@ def _validate_set_system_mode_params(tcs: ControlSystem, data: dict[str, Any]) -
timing_mode = mode_info.get(SZ_TIMING_MODE) # will not be None, as can_be_temporary
if timing_mode == SZ_DURATION and ATTR_PERIOD in data:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="mode_cant_have_period",
@@ -188,6 +192,7 @@ def _validate_set_system_mode_params(tcs: ControlSystem, data: dict[str, Any]) -
)
if timing_mode == SZ_PERIOD and ATTR_DURATION in data:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="mode_cant_have_duration",
+16 -20
View File
@@ -99,16 +99,14 @@ increase_speed:
supported_features:
- fan.FanEntityFeature.SET_SPEED
fields:
additional_fields:
collapsed: true
fields:
percentage_step:
required: false
selector:
number:
min: 0
max: 100
unit_of_measurement: "%"
percentage_step:
advanced: true
required: false
selector:
number:
min: 0
max: 100
unit_of_measurement: "%"
decrease_speed:
target:
@@ -117,13 +115,11 @@ decrease_speed:
supported_features:
- fan.FanEntityFeature.SET_SPEED
fields:
additional_fields:
collapsed: true
fields:
percentage_step:
required: false
selector:
number:
min: 0
max: 100
unit_of_measurement: "%"
percentage_step:
advanced: true
required: false
selector:
number:
min: 0
max: 100
unit_of_measurement: "%"
+2 -13
View File
@@ -2,7 +2,6 @@
"common": {
"condition_behavior_name": "Condition passes if",
"condition_for_name": "For at least",
"section_additional_fields_name": "Additional options",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
@@ -110,12 +109,7 @@
"name": "Decrement"
}
},
"name": "Decrease fan speed",
"sections": {
"additional_fields": {
"name": "[%key:component::fan::common::section_additional_fields_name%]"
}
}
"name": "Decrease fan speed"
},
"increase_speed": {
"description": "Increases the speed of a fan.",
@@ -125,12 +119,7 @@
"name": "Increment"
}
},
"name": "Increase fan speed",
"sections": {
"additional_fields": {
"name": "[%key:component::fan::common::section_additional_fields_name%]"
}
}
"name": "Increase fan speed"
},
"oscillate": {
"description": "Controls the oscillation of a fan.",
@@ -63,7 +63,7 @@ rules:
comment: |
This integration does not have many entities. All of them are fundamental.
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow: done
repair-issues: todo
@@ -67,6 +67,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> boo
"X_AVM-DE_UPnP1" in avm_wrapper.connection.services
and not (await avm_wrapper.async_get_upnp_configuration())["NewEnable"]
):
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryAuthFailed("Missing UPnP configuration")
await avm_wrapper.async_config_entry_first_refresh()
@@ -5,11 +5,7 @@ from typing import Any
from google_air_quality_api.api import GoogleAirQualityApi
from google_air_quality_api.auth import Auth
from google_air_quality_api.exceptions import (
GoogleAirQualityApiError,
InvalidCustomLAQIConfigurationError,
)
from google_air_quality_api.mapping import AQICategoryMapping
from google_air_quality_api.exceptions import GoogleAirQualityApiError
import voluptuous as vol
from homeassistant.config_entries import (
@@ -22,7 +18,6 @@ from homeassistant.config_entries import (
)
from homeassistant.const import (
CONF_API_KEY,
CONF_COUNTRY,
CONF_LATITUDE,
CONF_LOCATION,
CONF_LONGITUDE,
@@ -31,28 +26,11 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import SectionConfig, section
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.selector import (
CountrySelector,
LocationSelector,
LocationSelectorConfig,
SelectSelector,
SelectSelectorConfig,
SelectSelectorMode,
)
from homeassistant.helpers.selector import LocationSelector, LocationSelectorConfig
from .const import (
CONF_ENABLE_CUSTOM_LAQI,
CONF_REFERRER,
CUSTOM_LAQI,
CUSTOM_LOCAL_AQI_OPTIONS,
DOMAIN,
SECTION_API_KEY_OPTIONS,
)
from .const import CONF_REFERRER, DOMAIN, SECTION_API_KEY_OPTIONS
_LOGGER = logging.getLogger(__name__)
AIR_QUALITY_COVERAGE_URL = (
"https://developers.google.com/maps/documentation/air-quality/coverage"
)
STEP_USER_DATA_SCHEMA = vol.Schema(
{
@@ -72,31 +50,10 @@ async def _validate_input(
description_placeholders: dict[str, str],
) -> bool:
try:
custom_options = user_input.get(CUSTOM_LOCAL_AQI_OPTIONS) or {}
enable_custom_laqi = custom_options.get(CONF_ENABLE_CUSTOM_LAQI)
if enable_custom_laqi:
country = custom_options.get(CONF_COUNTRY)
custom_laqi = custom_options.get(CUSTOM_LAQI)
# When custom LAQI is enabled, both country and custom_laqi must be provided
if not country or not custom_laqi:
errors[CUSTOM_LOCAL_AQI_OPTIONS] = "missing_custom_laqi_options"
return False
await api.async_get_current_conditions(
lat=user_input[CONF_LOCATION][CONF_LATITUDE],
lon=user_input[CONF_LOCATION][CONF_LONGITUDE],
region_code=country,
custom_local_aqi=custom_laqi,
)
else:
await api.async_get_current_conditions(
lat=user_input[CONF_LOCATION][CONF_LATITUDE],
lon=user_input[CONF_LOCATION][CONF_LONGITUDE],
)
except InvalidCustomLAQIConfigurationError:
errors["base"] = "mismatch_country_and_laqi"
await api.async_get_current_conditions(
lat=user_input[CONF_LOCATION][CONF_LATITUDE],
lon=user_input[CONF_LOCATION][CONF_LONGITUDE],
)
except GoogleAirQualityApiError as err:
errors["base"] = "cannot_connect"
description_placeholders["error_message"] = str(err)
@@ -122,25 +79,6 @@ def _get_location_schema(hass: HomeAssistant) -> vol.Schema:
CONF_LONGITUDE: hass.config.longitude,
},
): LocationSelector(LocationSelectorConfig(radius=False)),
vol.Optional(CUSTOM_LOCAL_AQI_OPTIONS): section(
vol.Schema(
{
vol.Required(CONF_ENABLE_CUSTOM_LAQI, default=False): bool,
vol.Optional(
CONF_COUNTRY, default=hass.config.country
): CountrySelector(),
vol.Optional(CUSTOM_LAQI): SelectSelector(
SelectSelectorConfig(
options=sorted(
AQICategoryMapping.get_all_laq_indices()
),
mode=SelectSelectorMode.DROPDOWN,
)
),
}
),
SectionConfig(collapsed=True),
),
}
)
@@ -185,7 +123,6 @@ class GoogleAirQualityConfigFlow(ConfigFlow, domain=DOMAIN):
errors: dict[str, str] = {}
description_placeholders: dict[str, str] = {
"api_key_url": "https://developers.google.com/maps/documentation/air-quality/get-api-key",
"air_quality_coverage_url": AIR_QUALITY_COVERAGE_URL,
"restricting_api_keys_url": "https://developers.google.com/maps/api-security-best-practices#restricting-api-keys",
}
if user_input is not None:
@@ -195,13 +132,10 @@ class GoogleAirQualityConfigFlow(ConfigFlow, domain=DOMAIN):
if _is_location_already_configured(self.hass, user_input[CONF_LOCATION]):
return self.async_abort(reason="already_configured")
session = async_get_clientsession(self.hass)
referrer = user_input.get(SECTION_API_KEY_OPTIONS, {}).get(CONF_REFERRER)
auth = Auth(session, user_input[CONF_API_KEY], referrer=referrer)
api = GoogleAirQualityApi(auth)
if await _validate_input(user_input, api, errors, description_placeholders):
subentry_data = dict(user_input[CONF_LOCATION])
custom_opts = user_input.get(CUSTOM_LOCAL_AQI_OPTIONS)
if custom_opts and custom_opts.get(CONF_ENABLE_CUSTOM_LAQI):
subentry_data[CUSTOM_LOCAL_AQI_OPTIONS] = custom_opts
return self.async_create_entry(
title="Google Air Quality",
data={
@@ -211,7 +145,7 @@ class GoogleAirQualityConfigFlow(ConfigFlow, domain=DOMAIN):
subentries=[
{
"subentry_type": "location",
"data": subentry_data,
"data": user_input[CONF_LOCATION],
"title": user_input[CONF_NAME],
"unique_id": None,
},
@@ -251,9 +185,7 @@ class LocationSubentryFlowHandler(ConfigSubentryFlow):
return self.async_abort(reason="entry_not_loaded")
errors: dict[str, str] = {}
description_placeholders: dict[str, str] = {
"air_quality_coverage_url": AIR_QUALITY_COVERAGE_URL
}
description_placeholders: dict[str, str] = {}
if user_input is not None:
if _is_location_already_configured(self.hass, user_input[CONF_LOCATION]):
errors["base"] = "location_already_configured"
@@ -270,13 +202,9 @@ class LocationSubentryFlowHandler(ConfigSubentryFlow):
description_placeholders=description_placeholders,
)
if await _validate_input(user_input, api, errors, description_placeholders):
data = dict(user_input[CONF_LOCATION])
custom_options = user_input.get(CUSTOM_LOCAL_AQI_OPTIONS)
if custom_options and custom_options.get(CONF_ENABLE_CUSTOM_LAQI):
data[CUSTOM_LOCAL_AQI_OPTIONS] = custom_options
return self.async_create_entry(
title=user_input[CONF_NAME],
data=data,
data=user_input[CONF_LOCATION],
)
else:
user_input = {}
@@ -2,9 +2,6 @@
from typing import Final
CONF_ENABLE_CUSTOM_LAQI: Final = "enable_custom_laqi"
CONF_REFERRER: Final = "referrer"
CUSTOM_LAQI: Final = "custom_laqi"
CUSTOM_LOCAL_AQI_OPTIONS: Final = "custom_local_aqi_options"
DOMAIN: Final = "google_air_quality"
DOMAIN = "google_air_quality"
SECTION_API_KEY_OPTIONS: Final = "api_key_options"
CONF_REFERRER: Final = "referrer"
@@ -10,16 +10,11 @@ from google_air_quality_api.exceptions import GoogleAirQualityApiError
from google_air_quality_api.model import AirQualityCurrentConditionsData
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_COUNTRY, CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import (
CONF_ENABLE_CUSTOM_LAQI,
CUSTOM_LAQI,
CUSTOM_LOCAL_AQI_OPTIONS,
DOMAIN,
)
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -54,27 +49,11 @@ class GoogleAirQualityUpdateCoordinator(
subentry = config_entry.subentries[subentry_id]
self.lat = subentry.data[CONF_LATITUDE]
self.long = subentry.data[CONF_LONGITUDE]
self.custom_local_aqi: str | None = None
self.region_code: str | None = None
options = subentry.data.get(CUSTOM_LOCAL_AQI_OPTIONS)
if isinstance(options, dict) and options.get(CONF_ENABLE_CUSTOM_LAQI):
custom_laqi = options.get(CUSTOM_LAQI)
region_code = options.get(CONF_COUNTRY)
if custom_laqi is not None and region_code is not None:
self.custom_local_aqi = custom_laqi
self.region_code = region_code
async def _async_update_data(self) -> AirQualityCurrentConditionsData:
"""Fetch air quality data for this coordinate."""
try:
return await self.client.async_get_current_conditions(
lat=self.lat,
lon=self.long,
region_code=self.region_code,
custom_local_aqi=self.custom_local_aqi,
)
return await self.client.async_get_current_conditions(self.lat, self.long)
except GoogleAirQualityApiError as ex:
_LOGGER.debug("Cannot fetch air quality data: %s", str(ex))
raise UpdateFailed(
@@ -204,6 +204,7 @@ async def async_setup_entry(
for subentry_id, subentry in entry.subentries.items():
coordinator = coordinators[subentry_id]
_LOGGER.debug("subentry.data: %s", subentry.data)
async_add_entities(
(
AirQualitySensorEntity(coordinator, description, subentry_id, subentry)
@@ -15,8 +15,6 @@
},
"error": {
"cannot_connect": "Unable to connect to the Google Air Quality API:\n\n{error_message}",
"mismatch_country_and_laqi": "This local AQI is not available for the selected country. Please select an available combination.",
"missing_custom_laqi_options": "Please provide both country and custom local AQI when custom local AQI is enabled.",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"step": {
@@ -41,20 +39,6 @@
"referrer": "Specify this only if the API key has a [website application restriction]({restricting_api_keys_url})."
},
"name": "Optional API key options"
},
"custom_local_aqi_options": {
"data": {
"country": "[%key:common::config_flow::data::country%]",
"custom_laqi": "Custom local AQI",
"enable_custom_laqi": "Enable custom local AQI"
},
"data_description": {
"country": "Country of the location",
"custom_laqi": "The target air quality index",
"enable_custom_laqi": "Select to enable a custom local air quality index"
},
"description": "Country and custom local AQI must match. You can find the available combinations here: {air_quality_coverage_url}",
"name": "Custom local AQI options"
}
}
}
@@ -67,11 +51,8 @@
},
"entry_type": "Air quality location",
"error": {
"cannot_connect": "[%key:component::google_air_quality::config::error::cannot_connect%]",
"location_already_configured": "[%key:common::config_flow::abort::already_configured_location%]",
"location_name_already_configured": "Location name already configured.",
"mismatch_country_and_laqi": "[%key:component::google_air_quality::config::error::mismatch_country_and_laqi%]",
"missing_custom_laqi_options": "[%key:component::google_air_quality::config::error::missing_custom_laqi_options%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"initiate_flow": {
@@ -88,22 +69,6 @@
"name": "[%key:component::google_air_quality::config::step::user::data_description::name%]"
},
"description": "Select the coordinates for which you want to create an entry.",
"sections": {
"custom_local_aqi_options": {
"data": {
"country": "[%key:common::config_flow::data::country%]",
"custom_laqi": "[%key:component::google_air_quality::config::step::user::sections::custom_local_aqi_options::data::custom_laqi%]",
"enable_custom_laqi": "[%key:component::google_air_quality::config::step::user::sections::custom_local_aqi_options::data::enable_custom_laqi%]"
},
"data_description": {
"country": "[%key:component::google_air_quality::config::step::user::sections::custom_local_aqi_options::data_description::country%]",
"custom_laqi": "[%key:component::google_air_quality::config::step::user::sections::custom_local_aqi_options::data_description::custom_laqi%]",
"enable_custom_laqi": "[%key:component::google_air_quality::config::step::user::sections::custom_local_aqi_options::data_description::enable_custom_laqi%]"
},
"description": "[%key:component::google_air_quality::config::step::user::sections::custom_local_aqi_options::description%]",
"name": "[%key:component::google_air_quality::config::step::user::sections::custom_local_aqi_options::name%]"
}
},
"title": "Air quality data location"
}
}
@@ -100,6 +100,7 @@ class GoogleDriveBackupAgent(BackupAgent):
try:
await self._client.async_upload_backup(wrapped_open_stream, backup)
except (GoogleDriveApiError, HomeAssistantError, TimeoutError) as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupAgentError(f"Failed to upload backup: {err}") from err
async def async_list_backups(self, **kwargs: Any) -> list[AgentBackup]:
@@ -107,6 +108,7 @@ class GoogleDriveBackupAgent(BackupAgent):
try:
return await self._client.async_list_backups()
except (GoogleDriveApiError, HomeAssistantError, TimeoutError) as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupAgentError(f"Failed to list backups: {err}") from err
async def async_get_backup(
@@ -119,6 +121,7 @@ class GoogleDriveBackupAgent(BackupAgent):
for backup in backups:
if backup.backup_id == backup_id:
return backup
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupNotFound(f"Backup {backup_id} not found")
async def async_download_backup(
@@ -139,7 +142,9 @@ class GoogleDriveBackupAgent(BackupAgent):
stream = await self._client.async_download(file_id)
return ChunkAsyncStreamIterator(stream)
except (GoogleDriveApiError, HomeAssistantError, TimeoutError) as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupAgentError(f"Failed to download backup: {err}") from err
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupNotFound(f"Backup {backup_id} not found")
async def async_delete_backup(
@@ -160,5 +165,7 @@ class GoogleDriveBackupAgent(BackupAgent):
_LOGGER.debug("Deleted backup_id: %s", backup_id)
return
except (GoogleDriveApiError, HomeAssistantError, TimeoutError) as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupAgentError(f"Failed to delete backup: {err}") from err
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupNotFound(f"Backup {backup_id} not found")
@@ -84,6 +84,7 @@ async def _async_handle_upload(call: ServiceCall) -> ServiceResponse:
scopes = config_entry.data["token"]["scope"].split(" ")
if UPLOAD_SCOPE not in scopes:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="missing_upload_permission",
@@ -56,7 +56,7 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow: done
repair-issues:
@@ -109,6 +109,7 @@ class HabiticaBaseNotifyEntity(HabiticaBase, NotifyEntity):
try:
await self._send_message(message)
except NotAuthorizedError as e:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="send_message_forbidden",
@@ -118,6 +119,7 @@ class HabiticaBaseNotifyEntity(HabiticaBase, NotifyEntity):
},
) from e
except NotFoundError as e:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="send_message_not_found",
+2 -1
View File
@@ -14,7 +14,6 @@ from homeassistant.components.sensor import (
SensorEntity,
)
from homeassistant.const import (
ATTR_MODEL,
CONF_DISKS,
CONF_HOST,
CONF_NAME,
@@ -29,6 +28,8 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
_LOGGER = logging.getLogger(__name__)
ATTR_DEVICE = "device"
# pylint: disable-next=home-assistant-duplicate-const
ATTR_MODEL = "model"
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 7634
@@ -105,6 +105,7 @@ class HomeConnectCommandButtonEntity(HomeConnectButtonEntity):
value=True,
)
except HomeConnectError as error:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="execute_command",
@@ -266,6 +266,7 @@ class HomeConnectAirConditioningEntity(HomeConnectEntity, ClimateEntity):
value=BSH_POWER_ON,
)
except HomeConnectError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="power_on",
@@ -285,6 +286,7 @@ class HomeConnectAirConditioningEntity(HomeConnectEntity, ClimateEntity):
value=BSH_POWER_STANDBY,
)
except HomeConnectError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="power_off",
@@ -301,6 +303,7 @@ class HomeConnectAirConditioningEntity(HomeConnectEntity, ClimateEntity):
self.appliance.info.ha_id, program_key=program_key
)
except HomeConnectError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="start_program",
@@ -150,6 +150,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
value=True,
)
except HomeConnectError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_on_light",
@@ -169,6 +170,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
value=self._enable_custom_color_value_key,
)
except HomeConnectError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="select_light_custom_color",
@@ -187,6 +189,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
value=f"#{hex_val}",
)
except HomeConnectError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_light_color",
@@ -219,6 +222,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
value=f"#{hex_val}",
)
except HomeConnectError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_light_color",
@@ -242,6 +246,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
value=brightness,
)
except HomeConnectError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_light_brightness",
@@ -260,6 +265,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
value=False,
)
except HomeConnectError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_off_light",
@@ -180,6 +180,7 @@ class HomeConnectNumberEntity(HomeConnectEntity, NumberEntity):
value=value,
)
except HomeConnectError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_setting_entity",
@@ -503,6 +503,7 @@ class HomeConnectSelectEntity(HomeConnectEntity, SelectEntity):
value=value,
)
except HomeConnectError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_setting_entity",
@@ -207,6 +207,7 @@ async def async_service_setting(call: ServiceCall) -> None:
try:
await client.set_setting(ha_id, setting_key=key, value=value)
except HomeConnectError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_setting",
@@ -319,6 +320,7 @@ async def async_service_start_selected_program(call: ServiceCall) -> None:
options=list(options_dict.values()) if options_dict else None,
)
except HomeConnectError as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="start_program",
@@ -228,6 +228,7 @@ class HomeConnectSwitch(HomeConnectEntity, SwitchEntity):
)
except HomeConnectError as err:
self._attr_available = False
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_on",
@@ -248,6 +249,7 @@ class HomeConnectSwitch(HomeConnectEntity, SwitchEntity):
)
except HomeConnectError as err:
self._attr_available = False
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_off",
@@ -278,6 +280,7 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
)
except HomeConnectError as err:
self._attr_is_on = False
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="power_on",
@@ -314,6 +317,7 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
)
except HomeConnectError as err:
self._attr_is_on = True
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="power_off",
@@ -458,7 +458,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
hass,
DOMAIN,
"unsupported_local_deps",
breaks_in_ha_version="2026.11.0",
learn_more_url=DEPRECATION_URL,
is_fixable=False,
severity=IssueSeverity.WARNING,
@@ -59,7 +59,7 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow: done
repair-issues: todo
@@ -50,12 +50,14 @@ def homevolt_exception_handler[_HomevoltEntityT: HomevoltEntity, **_P](
translation_key="auth_failed",
) from error
except HomevoltConnectionError as error:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="communication_error",
translation_placeholders={"error": str(error)},
) from error
except HomevoltError as error:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="unknown_error",
@@ -331,15 +331,18 @@ async def async_try_connect(ip_address: str, token: str | None = None) -> Device
return await energy_api.device()
except DisabledError as ex:
# pylint: disable-next=home-assistant-exception-not-translated
raise RecoverableError(
"API disabled, API must be enabled in the app", "api_not_enabled"
) from ex
except UnsupportedError as ex:
LOGGER.error("API version unsuppored")
# pylint: disable-next=home-assistant-exception-not-translated
raise AbortFlow("unsupported_api_version") from ex
except RequestError as ex:
# pylint: disable-next=home-assistant-exception-not-translated
raise RecoverableError(
"Device unreachable or unexpected response", "network_error"
) from ex
@@ -349,6 +352,7 @@ async def async_try_connect(ip_address: str, token: str | None = None) -> Device
except Exception as ex:
LOGGER.exception("Unexpected exception")
# pylint: disable-next=home-assistant-exception-not-translated
raise AbortFlow("unknown_error") from ex
finally:
@@ -44,6 +44,7 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry]
data = await self.api.combined()
except RequestError as ex:
# pylint: disable-next=home-assistant-exception-message-with-translation
raise UpdateFailed(
ex, translation_domain=DOMAIN, translation_key="communication_error"
) from ex
@@ -60,6 +61,7 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry]
self.config_entry.entry_id
)
# pylint: disable-next=home-assistant-exception-message-with-translation
raise UpdateFailed(
ex, translation_domain=DOMAIN, translation_key="api_disabled"
) from ex
@@ -133,11 +133,6 @@ class PowerViewShadeBase(ShadeEntity, CoverEntity):
"""
return self._is_hard_wired
@property
def available(self) -> bool:
"""Return True if shade position data is available."""
return super().available and self.positions.primary is not None
@property
def extra_state_attributes(self) -> dict[str, str]:
"""Return the state attributes."""
@@ -62,7 +62,7 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow:
status: exempt
@@ -156,10 +156,6 @@ class HusqvarnaAutomowerBleConfigFlow(ConfigFlow, domain=DOMAIN):
assert self.address
if device is None:
LOGGER.debug("Could not find device with address '%s'", self.address)
return None
try:
(manufacturer, device_type, _model) = await Mower(
channel_id, self.address
@@ -94,7 +94,7 @@ rules:
entity-translations:
status: exempt
comment: This integration does not have entities.
exception-translations: done
exception-translations: todo
icon-translations:
status: exempt
comment: This integration does not use icons.
@@ -62,7 +62,7 @@ rules:
comment: >
The device class is a service. When removed, entities are removed as well.
diagnostics: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow:
status: todo
@@ -73,6 +73,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: InComfortConfigEntry) ->
except InvalidHeaterList as exc:
raise NoHeaters from exc
except InvalidGateway as exc:
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryAuthFailed("Incorrect credentials") from exc
except ClientResponseError as exc:
if exc.status == 404:
@@ -130,6 +130,7 @@ class InComfortConfigFlow(ConfigFlow, domain=DOMAIN):
self.hass.config_entries.async_schedule_reload(
existing_entries_without_unique_id[0].entry_id
)
# pylint: disable-next=home-assistant-exception-not-translated
raise AbortFlow("already_configured")
await self.async_set_unique_id(unique_id)
@@ -78,11 +78,15 @@ class InComfortDataCoordinator(DataUpdateCoordinator[InComfortData]):
for heater in self.incomfort_data.heaters:
await heater.update()
except TimeoutError as exc:
# pylint: disable-next=home-assistant-exception-not-translated
raise UpdateFailed("Timeout error") from exc
except ClientResponseError as exc:
if exc.status == 401:
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryError("Incorrect credentials") from exc
# pylint: disable-next=home-assistant-exception-not-translated
raise UpdateFailed(exc.message) from exc
except InvalidHeaterList as exc:
# pylint: disable-next=home-assistant-exception-not-translated
raise UpdateFailed(exc.message) from exc
return self.incomfort_data
@@ -7,5 +7,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"quality_scale": "silver",
"requirements": ["indevolt-api==1.8.1"]
"requirements": ["indevolt-api==1.8.0"]
}
@@ -60,7 +60,7 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow: done
repair-issues:
@@ -60,7 +60,7 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow: todo
repair-issues: done
@@ -29,6 +29,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: IsraelRailConfigEntry) -
try:
await hass.async_add_executor_job(train_schedule.query, start, destination)
except Exception as e:
# pylint: disable-next=home-assistant-exception-translation-key-missing
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="request_timeout",
@@ -49,6 +49,7 @@ class IstaCoordinator(DataUpdateCoordinator[dict[str, Any]]):
translation_key="connection_exception",
) from e
except (LoginError, KeycloakError) as e:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="authentication_exception",
@@ -68,6 +69,7 @@ class IstaCoordinator(DataUpdateCoordinator[dict[str, Any]]):
translation_key="connection_exception",
) from e
except (LoginError, KeycloakError) as e:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="authentication_exception",
@@ -115,6 +115,7 @@ async def async_attach_trigger(
try:
trigger_config = TRIGGER_TRIGGER_SCHEMA(trigger_config)
except vol.Invalid as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise InvalidDeviceAutomationConfig(f"{err}") from err
return await trigger.async_attach_trigger(
+1
View File
@@ -99,6 +99,7 @@ def get_resource(domain_name: str, domain_data: ConfigType) -> str:
return cast(str, domain_data["setpoint"])
if domain_name == "scene":
return f"{domain_data['register']}{domain_data['scene']}"
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="invalid_domain",
@@ -66,7 +66,7 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow: todo
repair-issues: todo
@@ -105,6 +105,7 @@ class ThinQEntity(CoordinatorEntity[DeviceDataUpdateCoordinator]):
except ThinQAPIException as exc:
if on_fail_method:
on_fail_method()
# pylint: disable-next=home-assistant-exception-message-with-translation
raise ServiceValidationError(
exc.message, translation_domain=DOMAIN, translation_key=exc.code
) from exc
@@ -44,8 +44,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: LiebherrConfigEntry) ->
try:
devices = await client.get_devices()
except LiebherrAuthenticationError as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryAuthFailed("Invalid API key") from err
except LiebherrConnectionError as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryNotReady(f"Failed to connect to Liebherr API: {err}") from err
# Create a coordinator for each device (may be empty if no devices)
@@ -58,8 +58,10 @@ class LiebherrCoordinator(DataUpdateCoordinator[DeviceState]):
try:
await self.client.get_device(self.device_id)
except LiebherrAuthenticationError as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryAuthFailed("Invalid API key") from err
except LiebherrConnectionError as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryNotReady(
f"Failed to connect to device {self.device_id}: {err}"
) from err
@@ -69,12 +71,15 @@ class LiebherrCoordinator(DataUpdateCoordinator[DeviceState]):
try:
return await self.client.get_device_state(self.device_id)
except LiebherrAuthenticationError as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryAuthFailed("API key is no longer valid") from err
except LiebherrTimeoutError as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise UpdateFailed(
f"Timeout communicating with device {self.device_id}"
) from err
except LiebherrConnectionError as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise UpdateFailed(
f"Error communicating with device {self.device_id}"
) from err
+3 -3
View File
@@ -227,7 +227,7 @@ turn_on:
selector:
state:
attribute: effect
additional_fields:
advanced_fields:
collapsed: true
fields:
rgbw_color: &rgbw_color
@@ -298,7 +298,7 @@ turn_off:
domain: light
fields:
transition: *transition
additional_fields:
advanced_fields:
collapsed: true
fields:
flash: *flash
@@ -313,7 +313,7 @@ toggle:
color_temp_kelvin: *color_temp_kelvin
brightness_pct: *brightness_pct
effect: *effect
additional_fields:
advanced_fields:
collapsed: true
fields:
rgbw_color: *rgbw_color
+7 -7
View File
@@ -35,7 +35,7 @@
"field_white_name": "White",
"field_xy_color_description": "Color in XY-format. A list of two decimal numbers between 0 and 1.",
"field_xy_color_name": "XY-color",
"section_additional_fields_name": "Additional options",
"section_advanced_fields_name": "Advanced options",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least",
"trigger_threshold_name": "Threshold type"
@@ -396,8 +396,8 @@
},
"name": "Toggle light",
"sections": {
"additional_fields": {
"name": "[%key:component::light::common::section_additional_fields_name%]"
"advanced_fields": {
"name": "[%key:component::light::common::section_advanced_fields_name%]"
}
}
},
@@ -415,8 +415,8 @@
},
"name": "Turn off light",
"sections": {
"additional_fields": {
"name": "[%key:component::light::common::section_additional_fields_name%]"
"advanced_fields": {
"name": "[%key:component::light::common::section_advanced_fields_name%]"
}
}
},
@@ -490,8 +490,8 @@
},
"name": "Turn on light",
"sections": {
"additional_fields": {
"name": "[%key:component::light::common::section_additional_fields_name%]"
"advanced_fields": {
"name": "[%key:component::light::common::section_advanced_fields_name%]"
}
}
}
@@ -15,6 +15,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Local file from a config entry."""
file_path: str = entry.options[CONF_FILE_PATH]
if not await hass.async_add_executor_job(check_file_path_access, file_path):
# pylint: disable-next=home-assistant-exception-translation-key-missing
raise ConfigEntryError(
translation_domain=DOMAIN,
translation_key="not_readable_path",
@@ -77,6 +77,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LunatoneConfigEntry) ->
await coordinator_info.async_config_entry_first_refresh()
if info_api.data is None or info_api.serial_number is None:
# pylint: disable-next=home-assistant-exception-translation-key-missing
raise ConfigEntryError(
translation_domain=DOMAIN, translation_key="missing_device_info"
)
@@ -422,6 +422,7 @@ class ManualAlarm(AlarmControlPanelEntity, RestoreEntity):
},
)
# pylint: disable-next=home-assistant-exception-message-with-translation
raise ServiceValidationError(
"Invalid alarm code provided",
translation_domain=DOMAIN,
@@ -53,10 +53,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: MastodonConfigEntry) ->
translation_key="auth_failed",
) from error
except MastodonError as ex:
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="failed_to_connect",
) from ex
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryNotReady("Failed to connect") from ex
assert entry.unique_id
@@ -62,9 +62,7 @@ class MastodonCoordinator(DataUpdateCoordinator[Account]):
translation_key="auth_failed",
) from error
except MastodonError as ex:
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="update_failed",
) from ex
# pylint: disable-next=home-assistant-exception-not-translated
raise UpdateFailed(ex) from ex
return account
@@ -101,9 +101,6 @@
"auth_failed": {
"message": "Authentication failed, please reauthenticate with Mastodon."
},
"failed_to_connect": {
"message": "Failed to connect."
},
"idempotency_key_too_short": {
"message": "Idempotency key must be at least 4 characters long."
},
@@ -133,9 +130,6 @@
},
"unable_to_upload_image": {
"message": "Unable to upload image {media_path}."
},
"update_failed": {
"message": "Update failed."
}
},
"selector": {
+2
View File
@@ -165,6 +165,7 @@ class MieleFan(MieleEntity, FanEntity):
try:
await self.api.send_action(self._device_id, {POWER_ON: True})
except ClientResponseError as ex:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_state_error",
@@ -183,6 +184,7 @@ class MieleFan(MieleEntity, FanEntity):
try:
await self.api.send_action(self._device_id, {POWER_OFF: True})
except ClientResponseError as ex:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_state_error",
@@ -60,7 +60,7 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow: todo
repair-issues:
@@ -291,6 +291,7 @@ async def async_check_config_schema(
message = conf_util.format_schema_error(
hass, exc, domain, config, integration.documentation
)
# pylint: disable-next=home-assistant-exception-message-with-translation
raise ServiceValidationError(
message,
translation_domain=DOMAIN,
+3
View File
@@ -156,6 +156,7 @@ async def async_publish(
) -> None:
"""Publish message to a MQTT topic."""
if not mqtt_config_entry_enabled(hass):
# pylint: disable-next=home-assistant-exception-message-with-translation
raise HomeAssistantError(
f"Cannot publish to topic '{topic}', MQTT is not enabled",
translation_key="mqtt_not_setup_cannot_publish",
@@ -281,6 +282,7 @@ def async_subscribe_internal(
try:
mqtt_data = hass.data[DATA_MQTT]
except KeyError as exc:
# pylint: disable-next=home-assistant-exception-message-with-translation
raise HomeAssistantError(
f"Cannot subscribe to topic '{topic}', make sure MQTT is set up correctly",
translation_key="mqtt_not_setup_cannot_subscribe",
@@ -289,6 +291,7 @@ def async_subscribe_internal(
) from exc
client = mqtt_data.client
if not mqtt_config_entry_enabled(hass):
# pylint: disable-next=home-assistant-exception-message-with-translation
raise HomeAssistantError(
f"Cannot subscribe to topic '{topic}', MQTT is not enabled",
translation_key="mqtt_not_setup_cannot_subscribe",
@@ -4135,6 +4135,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
data=config,
)
# pylint: disable-next=home-assistant-exception-not-translated
raise AbortFlow(
"addon_connection_failed",
description_placeholders={"addon": self._addon_manager.addon_name},
@@ -4149,6 +4150,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
try:
addon_info = await addon_manager.async_get_addon_info()
except AddonError as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise AbortFlow(
"addon_info_failed",
description_placeholders={"addon": self._addon_manager.addon_name},

Some files were not shown because too many files have changed in this diff Show More