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
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ jobs:
uses: actions/checkout@v5.0.0

- name: Initialize CodeQL
uses: github/codeql-action/init@v3.30.0
uses: github/codeql-action/init@v3.30.1
with:
languages: python

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3.30.0
uses: github/codeql-action/analyze@v3.30.1
with:
category: "/language:python"
15 changes: 15 additions & 0 deletions homeassistant/components/kitchen_sink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
)
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import DeviceEntry
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util
Expand Down Expand Up @@ -117,6 +118,20 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)


async def async_remove_config_entry_device(
hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry
) -> bool:
"""Remove a config entry from a device."""

# Allow deleting any device except statistics_issues, just to give
# something to test the negative case.
for identifier in device_entry.identifiers:
if identifier[0] == DOMAIN and identifier[1] == "statistics_issues":
return False

return True


async def _notify_backup_listeners(hass: HomeAssistant) -> None:
for listener in hass.data.get(DATA_BACKUP_AGENT_LISTENERS, []):
listener()
Expand Down
22 changes: 0 additions & 22 deletions homeassistant/components/kodi/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,28 +233,6 @@ async def async_step_ws_port(

return self._show_ws_port_form(errors)

async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
"""Handle import from YAML."""
reason = None
try:
await validate_http(self.hass, import_data)
await validate_ws(self.hass, import_data)
except InvalidAuth:
_LOGGER.exception("Invalid Kodi credentials")
reason = "invalid_auth"
except CannotConnect:
_LOGGER.exception("Cannot connect to Kodi")
reason = "cannot_connect"
except Exception:
_LOGGER.exception("Unexpected exception")
reason = "unknown"
else:
return self.async_create_entry(
title=import_data[CONF_NAME], data=import_data
)

return self.async_abort(reason=reason)

@callback
def _show_credentials_form(
self, errors: dict[str, str] | None = None
Expand Down
104 changes: 3 additions & 101 deletions homeassistant/components/kodi/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

from homeassistant.components import media_source
from homeassistant.components.media_player import (
PLATFORM_SCHEMA as MEDIA_PLAYER_PLATFORM_SCHEMA,
BrowseError,
BrowseMedia,
MediaPlayerEntity,
Expand All @@ -24,19 +23,11 @@
MediaType,
async_process_play_media_url,
)
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_DEVICE_ID,
CONF_HOST,
CONF_NAME,
CONF_PASSWORD,
CONF_PORT,
CONF_PROXY_SSL,
CONF_SSL,
CONF_TIMEOUT,
CONF_TYPE,
CONF_USERNAME,
EVENT_HOMEASSISTANT_STARTED,
)
from homeassistant.core import CoreState, HomeAssistant, callback
Expand All @@ -46,13 +37,10 @@
entity_platform,
)
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import (
AddConfigEntryEntitiesCallback,
AddEntitiesCallback,
)
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.network import is_internal_request
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, VolDictType
from homeassistant.helpers.typing import VolDictType
from homeassistant.util import dt as dt_util

from . import KodiConfigEntry
Expand All @@ -62,35 +50,12 @@
library_payload,
media_source_content_filter,
)
from .const import (
CONF_WS_PORT,
DEFAULT_PORT,
DEFAULT_SSL,
DEFAULT_TIMEOUT,
DEFAULT_WS_PORT,
DOMAIN,
EVENT_TURN_OFF,
EVENT_TURN_ON,
)
from .const import DOMAIN, EVENT_TURN_OFF, EVENT_TURN_ON

_LOGGER = logging.getLogger(__name__)

EVENT_KODI_CALL_METHOD_RESULT = "kodi_call_method_result"

CONF_TCP_PORT = "tcp_port"
CONF_TURN_ON_ACTION = "turn_on_action"
CONF_TURN_OFF_ACTION = "turn_off_action"
CONF_ENABLE_WEBSOCKET = "enable_websocket"

DEPRECATED_TURN_OFF_ACTIONS = {
None: None,
"quit": "Application.Quit",
"hibernate": "System.Hibernate",
"suspend": "System.Suspend",
"reboot": "System.Reboot",
"shutdown": "System.Shutdown",
}

WEBSOCKET_WATCHDOG_INTERVAL = timedelta(seconds=10)

# https://github.com/xbmc/xbmc/blob/master/xbmc/media/MediaType.h
Expand Down Expand Up @@ -120,25 +85,6 @@
}


PLATFORM_SCHEMA = MEDIA_PLAYER_PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_TCP_PORT, default=DEFAULT_WS_PORT): cv.port,
vol.Optional(CONF_PROXY_SSL, default=DEFAULT_SSL): cv.boolean,
vol.Optional(CONF_TURN_ON_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_TURN_OFF_ACTION): vol.Any(
cv.SCRIPT_SCHEMA, vol.In(DEPRECATED_TURN_OFF_ACTIONS)
),
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Inclusive(CONF_USERNAME, "auth"): cv.string,
vol.Inclusive(CONF_PASSWORD, "auth"): cv.string,
vol.Optional(CONF_ENABLE_WEBSOCKET, default=True): cv.boolean,
}
)


SERVICE_ADD_MEDIA = "add_to_playlist"
SERVICE_CALL_METHOD = "call_method"

Expand All @@ -161,50 +107,6 @@
)


def find_matching_config_entries_for_host(hass, host):
"""Search existing config entries for one matching the host."""
for entry in hass.config_entries.async_entries(DOMAIN):
if entry.data[CONF_HOST] == host:
return entry
return None


async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Kodi platform."""
if discovery_info:
# Now handled by zeroconf in the config flow
return

host = config[CONF_HOST]
if find_matching_config_entries_for_host(hass, host):
return

websocket = config.get(CONF_ENABLE_WEBSOCKET)
ws_port = config.get(CONF_TCP_PORT) if websocket else None

entry_data = {
CONF_NAME: config.get(CONF_NAME, host),
CONF_HOST: host,
CONF_PORT: config.get(CONF_PORT),
CONF_WS_PORT: ws_port,
CONF_USERNAME: config.get(CONF_USERNAME),
CONF_PASSWORD: config.get(CONF_PASSWORD),
CONF_SSL: config.get(CONF_PROXY_SSL),
CONF_TIMEOUT: config.get(CONF_TIMEOUT),
}

hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=entry_data
)
)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: KodiConfigEntry,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/lifx/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@
"requirements": [
"aiolifx==1.2.1",
"aiolifx-effects==0.3.2",
"aiolifx-themes==0.6.4"
"aiolifx-themes==1.0.2"
]
}
2 changes: 1 addition & 1 deletion homeassistant/components/sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ def _is_valid_suggested_unit(self, suggested_unit_of_measurement: str) -> bool:
unit converter supports both the native and the suggested units of measurement.
"""
# Make sure we can convert the units
if (
if self.native_unit_of_measurement != suggested_unit_of_measurement and (
(unit_converter := UNIT_CONVERTERS.get(self.device_class)) is None
or self.__native_unit_of_measurement_compat
not in unit_converter.VALID_UNITS
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/vodafone_station/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"iot_class": "local_polling",
"loggers": ["aiovodafone"],
"quality_scale": "platinum",
"requirements": ["aiovodafone==0.10.0"]
"requirements": ["aiovodafone==1.2.1"]
}
9 changes: 7 additions & 2 deletions homeassistant/helpers/condition.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
CONF_FOR,
CONF_ID,
CONF_MATCH,
CONF_SELECTOR,
CONF_STATE,
CONF_VALUE_TEMPLATE,
CONF_WEEKDAY,
Expand Down Expand Up @@ -59,9 +60,10 @@
from homeassistant.util.hass_dict import HassKey
from homeassistant.util.yaml import load_yaml_dict

from . import config_validation as cv, entity_registry as er
from . import config_validation as cv, entity_registry as er, selector
from .automation import get_absolute_description_key, get_relative_description_key
from .integration_platform import async_process_integration_platforms
from .selector import TargetSelector
from .template import Template, render_complex
from .trace import (
TraceElement,
Expand Down Expand Up @@ -110,12 +112,15 @@
# Basic schemas to sanity check the condition descriptions,
# full validation is done by hassfest.conditions
_FIELD_SCHEMA = vol.Schema(
{},
{
vol.Optional(CONF_SELECTOR): selector.validate_selector,
},
extra=vol.ALLOW_EXTRA,
)

_CONDITION_SCHEMA = vol.Schema(
{
vol.Optional("target"): TargetSelector.CONFIG_SCHEMA,
vol.Optional("fields"): vol.Schema({str: _FIELD_SCHEMA}),
},
extra=vol.ALLOW_EXTRA,
Expand Down
16 changes: 15 additions & 1 deletion homeassistant/helpers/entity_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -1899,11 +1899,25 @@ def _async_setup_entity_restore(hass: HomeAssistant, registry: EntityRegistry) -
@callback
def cleanup_restored_states_filter(event_data: Mapping[str, Any]) -> bool:
"""Clean up restored states filter."""
return bool(event_data["action"] == "remove")
return (event_data["action"] == "remove") or (
event_data["action"] == "update"
and "old_entity_id" in event_data
and event_data["entity_id"] != event_data["old_entity_id"]
)

@callback
def cleanup_restored_states(event: Event[EventEntityRegistryUpdatedData]) -> None:
"""Clean up restored states."""
if event.data["action"] == "update":
old_entity_id = event.data["old_entity_id"]
old_state = hass.states.get(old_entity_id)
if old_state is None or not old_state.attributes.get(ATTR_RESTORED):
return
hass.states.async_remove(old_entity_id, context=event.context)
if entry := registry.async_get(event.data["entity_id"]):
entry.write_unavailable_state(hass)
return

state = hass.states.get(event.data["entity_id"])

if state is None or not state.attributes.get(ATTR_RESTORED):
Expand Down
4 changes: 2 additions & 2 deletions requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions requirements_test_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading