Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ jobs:
- name: Generate app token
id: token
# Pinned to a specific version of the action for security reasons
# v3.2.0
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
permission-issues: write
permission-pull-requests: write
app-id: ${{ secrets.ISSUE_TRIAGE_APP_ID }} # zizmor: ignore[secrets-outside-env]
client-id: ${{ secrets.ISSUE_TRIAGE_APP_ID }} # zizmor: ignore[secrets-outside-env]
private-key: ${{ secrets.ISSUE_TRIAGE_APP_PEM }} # zizmor: ignore[secrets-outside-env]

Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ repos:
exclude_types: [csv, json, html]
exclude: ^tests/fixtures/|homeassistant/generated/|tests/components/.*/snapshots/
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.24.1
rev: v1.25.2
hooks:
- id: zizmor
args:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/aidot/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
"integration_type": "hub",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["python-aidot==0.3.53"]
"requirements": ["python-aidot==0.3.56"]
}
17 changes: 8 additions & 9 deletions homeassistant/components/airnow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirNowConfigEntry) -> bo
latitude = entry.data[CONF_LATITUDE]
longitude = entry.data[CONF_LONGITUDE]

# Station Radius is a user-configurable option
distance = entry.options[CONF_RADIUS]

# Reports are published hourly but update twice per hour
update_interval = datetime.timedelta(minutes=30)

# Setup the Coordinator
session = async_get_clientsession(hass)
coordinator = AirNowDataUpdateCoordinator(
hass, entry, session, api_key, latitude, longitude, distance, update_interval
hass, entry, session, api_key, latitude, longitude, update_interval
)

# Sync with Coordinator
Expand Down Expand Up @@ -68,13 +65,15 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s", entry.version)

if entry.version == 1:
new_options = {CONF_RADIUS: entry.data[CONF_RADIUS]}
new_data = entry.data.copy()
del new_data[CONF_RADIUS]
if entry.version < 3:
# The 2026 AirNow API dropped the distance parameter, so the radius
# option no longer affects lookups. Strip it from both older layouts:
# version 1 kept it in data, version 2 in options.
new_data = {k: v for k, v in entry.data.items() if k != CONF_RADIUS}
new_options = {k: v for k, v in entry.options.items() if k != CONF_RADIUS}

hass.config_entries.async_update_entry(
entry, data=new_data, options=new_options, version=2
entry, data=new_data, options=new_options, version=3
)

_LOGGER.info("Migration to version %s successful", entry.version)
Expand Down
49 changes: 4 additions & 45 deletions homeassistant/components/airnow/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,9 @@
from pyairnow.errors import AirNowError, EmptyResponseError, InvalidKeyError
import voluptuous as vol

from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlowWithReload,
)
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS
from homeassistant.core import HomeAssistant, callback
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
Expand Down Expand Up @@ -60,7 +55,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> bool:
class AirNowConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for AirNow."""

VERSION = 2
VERSION = 3

@override
async def async_step_user(
Expand Down Expand Up @@ -90,14 +85,12 @@ async def async_step_user(
errors["base"] = "unknown"
else:
# Create Entry
radius = user_input.pop(CONF_RADIUS)
return self.async_create_entry(
title=(
f"AirNow Sensor at {user_input[CONF_LATITUDE]},"
f" {user_input[CONF_LONGITUDE]}"
),
data=user_input,
options={CONF_RADIUS: radius},
)

return self.async_show_form(
Expand All @@ -111,46 +104,12 @@ async def async_step_user(
vol.Optional(
CONF_LONGITUDE, default=self.hass.config.longitude
): cv.longitude,
vol.Optional(CONF_RADIUS, default=150): vol.All(
int, vol.Range(min=5)
),
}
),
description_placeholders={"api_key_url": _API_KEY_URL},
errors=errors,
)

@staticmethod
@callback
@override
def async_get_options_flow(
config_entry: ConfigEntry,
) -> AirNowOptionsFlowHandler:
"""Return the options flow."""
return AirNowOptionsFlowHandler()


class AirNowOptionsFlowHandler(OptionsFlowWithReload):
"""Handle an options flow for AirNow."""

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(data=user_input)

options_schema = vol.Schema(
{vol.Optional(CONF_RADIUS): vol.All(int, vol.Range(min=5))}
)

return self.async_show_form(
step_id="init",
data_schema=self.add_suggested_values_to_schema(
options_schema, self.config_entry.options
),
)


class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""
Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/airnow/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,11 @@ def __init__(
api_key: str,
latitude: float,
longitude: float,
distance: int,
update_interval: timedelta,
) -> None:
"""Initialize."""
self.latitude = latitude
self.longitude = longitude
self.distance = distance

self.airnow = WebServiceAPI(api_key, session=session)

Expand Down
20 changes: 3 additions & 17 deletions homeassistant/components/airnow/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,20 @@
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"invalid_location": "No results found for that location, try changing the location or station radius.",
"invalid_location": "No results found for that location, try changing the location.",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"step": {
"user": {
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
"latitude": "[%key:common::config_flow::data::latitude%]",
"longitude": "[%key:common::config_flow::data::longitude%]",
"radius": "Station radius (miles; optional)"
"longitude": "[%key:common::config_flow::data::longitude%]"
},
"data_description": {
"api_key": "To generate an API key, go to {api_key_url}.",
"latitude": "The latitude of your location.",
"longitude": "The longitude of your location.",
"radius": "The radius in miles around your location to search for reporting stations."
"longitude": "The longitude of your location."
},
"description": "To generate an API key, go to {api_key_url}."
}
Expand All @@ -40,17 +38,5 @@
}
}
}
},
"options": {
"step": {
"init": {
"data": {
"radius": "Station radius (miles)"
},
"data_description": {
"radius": "The radius in miles around your location to search for reporting stations."
}
}
}
}
}
2 changes: 1 addition & 1 deletion homeassistant/components/alexa_devices/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aioamazondevices"],
"quality_scale": "platinum",
"requirements": ["aioamazondevices==14.2.0"]
"requirements": ["aioamazondevices==14.2.2"]
}
39 changes: 39 additions & 0 deletions homeassistant/components/config/device_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def async_setup(hass: HomeAssistant) -> bool:

websocket_api.async_register_command(hass, websocket_list_composite_splits)
websocket_api.async_register_command(hass, websocket_list_devices)
websocket_api.async_register_command(hass, websocket_list_linked_devices)
websocket_api.async_register_command(hass, websocket_update_device)
websocket_api.async_register_command(
hass, websocket_remove_config_entry_from_device
Expand Down Expand Up @@ -95,6 +96,44 @@ def websocket_list_devices(
connection.send_message(msg_json)


@callback
@websocket_api.websocket_command(
{
vol.Required("type"): "config/device_registry/list_linked_devices",
vol.Required("device_id"): str,
}
)
def websocket_list_linked_devices(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Handle list linked devices command.

Linked devices share at least one connection or identifier with the given
device. Each such connection or identifier is unique within a config entry, so
the linked devices belong to other config entries.
"""
registry = dr.async_get(hass)
device_id = msg["device_id"]

if (device := registry.async_get(device_id)) is None:
connection.send_error(
msg["id"], websocket_api.ERR_NOT_FOUND, "Device not found"
)
return

linked_devices = [
entry.id
for entry in registry.async_get_devices(
identifiers=device.identifiers, connections=device.connections
)
if entry.id != device_id
]

connection.send_result(msg["id"], {"linked_devices": linked_devices})


@require_admin
@websocket_api.websocket_command(
{
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/hassio/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,10 @@
PLACEHOLDER_KEY_REFERENCE = "reference"
PLACEHOLDER_KEY_COMPONENTS = "components"
PLACEHOLDER_KEY_FREE_SPACE = "free_space"
PLACEHOLDER_KEY_PORT = "port"
PLACEHOLDER_KEY_REASON = "reason"

ISSUE_KEY_ADDON_APP_PORT_CONFLICT = "issue_addon_app_port_conflict"
ISSUE_KEY_ADDON_BOOT_FAIL = "issue_addon_boot_fail"
ISSUE_KEY_SYSTEM_DOCKER_CONFIG = "issue_system_docker_config"
ISSUE_KEY_ADDON_DETACHED_ADDON_MISSING = "issue_addon_detached_addon_missing"
Expand Down
14 changes: 14 additions & 0 deletions homeassistant/components/hassio/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
HASSIO_ISSUES_UPDATE_INTERVAL,
HASSIO_MAIN_UPDATE_INTERVAL,
HASSIO_STATS_UPDATE_INTERVAL,
ISSUE_KEY_ADDON_APP_PORT_CONFLICT,
ISSUE_KEY_ADDON_BOOT_FAIL,
ISSUE_KEY_ADDON_DEPRECATED_ARCH,
ISSUE_KEY_ADDON_DETACHED_ADDON_MISSING,
Expand All @@ -102,6 +103,7 @@
PLACEHOLDER_KEY_ADDON,
PLACEHOLDER_KEY_ADDON_URL,
PLACEHOLDER_KEY_FREE_SPACE,
PLACEHOLDER_KEY_PORT,
PLACEHOLDER_KEY_REASON,
PLACEHOLDER_KEY_REFERENCE,
REQUEST_REFRESH_DELAY,
Expand Down Expand Up @@ -131,6 +133,7 @@

# Keys (type + context) of issues that when found should be made into a repair.
ISSUE_KEYS_FOR_REPAIRS = {
ISSUE_KEY_ADDON_APP_PORT_CONFLICT,
ISSUE_KEY_ADDON_BOOT_FAIL,
ISSUE_MOUNT_MOUNT_FAILED,
"issue_system_multiple_data_disks",
Expand Down Expand Up @@ -346,6 +349,7 @@ def _create_or_update_issue_repair(self, issue: Issue) -> None:
placeholders[PLACEHOLDER_KEY_REFERENCE] = issue.reference

if issue.key in {
ISSUE_KEY_ADDON_APP_PORT_CONFLICT,
ISSUE_KEY_ADDON_DETACHED_ADDON_MISSING,
ISSUE_KEY_ADDON_PWNED,
}:
Expand All @@ -359,6 +363,14 @@ def _create_or_update_issue_repair(self, issue: Issue) -> None:
placeholders[PLACEHOLDER_KEY_ADDON] = addon[ATTR_NAME]
break

if (
issue.key == ISSUE_KEY_ADDON_APP_PORT_CONFLICT
and issue.reference_extra
):
placeholders[PLACEHOLDER_KEY_PORT] = str(
issue.reference_extra["port"]
)

elif issue.key == ISSUE_KEY_SYSTEM_FREE_SPACE:
host_info = get_host_info(self.hass)
if host_info and "disk_free" in host_info:
Expand Down Expand Up @@ -447,12 +459,14 @@ async def _issue_from_data(self, data: SupervisorIssue) -> Issue | None:
type=str(data.type),
context=data.context,
reference=data.reference,
reference_extra=data.reference_extra,
suggestions=[
Suggestion(
uuid=suggestion.uuid,
type=str(suggestion.type),
context=suggestion.context,
reference=suggestion.reference,
reference_extra=suggestion.reference_extra,
)
for suggestion in suggestions
],
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/hassio/issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class SuggestionDataType(TypedDict):
type: str
context: str
reference: str | None
reference_extra: dict | None


@dataclass(slots=True, frozen=True)
Expand All @@ -24,6 +25,7 @@ class Suggestion:
type: str
context: ContextType
reference: str | None = None
reference_extra: dict | None = field(default=None, hash=False)

@property
def key(self) -> str:
Expand All @@ -38,6 +40,7 @@ def from_dict(cls, data: SuggestionDataType) -> Suggestion:
type=data["type"],
context=ContextType(data["context"]),
reference=data["reference"],
reference_extra=data["reference_extra"],
)


Expand All @@ -48,6 +51,7 @@ class IssueDataType(TypedDict):
type: str
context: str
reference: str | None
reference_extra: dict | None
suggestions: NotRequired[list[SuggestionDataType]]


Expand All @@ -59,6 +63,7 @@ class Issue:
type: str
context: ContextType
reference: str | None = None
reference_extra: dict | None = field(default=None, hash=False)
suggestions: list[Suggestion] = field(default_factory=list, compare=False)

@property
Expand All @@ -75,6 +80,7 @@ def from_dict(cls, data: IssueDataType) -> Issue:
type=data["type"],
context=ContextType(data["context"]),
reference=data["reference"],
reference_extra=data["reference_extra"],
suggestions=[
Suggestion.from_dict(suggestion) for suggestion in suggestions
],
Expand Down
Loading
Loading