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
9 changes: 8 additions & 1 deletion homeassistant/components/anthropic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@
)
from homeassistant.helpers.typing import ConfigType

from .const import CONF_CHAT_MODEL, DOMAIN, LOGGER, RECOMMENDED_CHAT_MODEL
from .const import (
CONF_CHAT_MODEL,
DEFAULT_CONVERSATION_NAME,
DOMAIN,
LOGGER,
RECOMMENDED_CHAT_MODEL,
)

PLATFORMS = (Platform.CONVERSATION,)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
Expand Down Expand Up @@ -123,6 +129,7 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
else:
hass.config_entries.async_update_entry(
entry,
title=DEFAULT_CONVERSATION_NAME,
options={},
version=2,
)
1 change: 0 additions & 1 deletion homeassistant/components/buienradar/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@
key="windazimuth",
translation_key="windazimuth",
native_unit_of_measurement=DEGREE,
icon="mdi:compass-outline",
device_class=SensorDeviceClass.WIND_DIRECTION,
state_class=SensorStateClass.MEASUREMENT_ANGLE,
),
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/dlink/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["pyW215"],
"requirements": ["pyW215==0.7.0"]
"requirements": ["pyW215==0.8.0"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/http/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ async def auth_middleware(
# We first start with a string check to avoid parsing query params
# for every request.
elif (
request.method == "GET"
request.method in ["GET", "HEAD"]
and SIGN_QUERY_PARAM in request.query_string
and async_validate_signed_request(request)
):
Expand Down
37 changes: 34 additions & 3 deletions homeassistant/components/image/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,10 @@ def __init__(self, component: EntityComponent[ImageEntity]) -> None:
"""Initialize an image view."""
self.component = component

async def get(self, request: web.Request, entity_id: str) -> web.StreamResponse:
"""Start a GET request."""
async def _authenticate_request(
self, request: web.Request, entity_id: str
) -> ImageEntity:
"""Authenticate request and return image entity."""
if (image_entity := self.component.get_entity(entity_id)) is None:
raise web.HTTPNotFound

Expand All @@ -306,6 +308,31 @@ async def get(self, request: web.Request, entity_id: str) -> web.StreamResponse:
# Invalid sigAuth or image entity access token
raise web.HTTPForbidden

return image_entity

async def head(self, request: web.Request, entity_id: str) -> web.Response:
"""Start a HEAD request.

This is sent by some DLNA renderers, like Samsung ones, prior to sending
the GET request.
"""
image_entity = await self._authenticate_request(request, entity_id)

# Don't use `handle` as we don't care about the stream case, we only want
# to verify that the image exists.
try:
image = await _async_get_image(image_entity, IMAGE_TIMEOUT)
except (HomeAssistantError, ValueError) as ex:
raise web.HTTPInternalServerError from ex

return web.Response(
content_type=image.content_type,
headers={"Content-Length": str(len(image.content))},
)

async def get(self, request: web.Request, entity_id: str) -> web.StreamResponse:
"""Start a GET request."""
image_entity = await self._authenticate_request(request, entity_id)
return await self.handle(request, image_entity)

async def handle(
Expand All @@ -317,7 +344,11 @@ async def handle(
except (HomeAssistantError, ValueError) as ex:
raise web.HTTPInternalServerError from ex

return web.Response(body=image.content, content_type=image.content_type)
return web.Response(
body=image.content,
content_type=image.content_type,
headers={"Content-Length": str(len(image.content))},
)


async def async_get_still_stream(
Expand Down
47 changes: 30 additions & 17 deletions homeassistant/components/matter/vacuum.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .entity import MatterEntity
Expand Down Expand Up @@ -67,20 +68,31 @@ class MatterVacuum(MatterEntity, StateVacuumEntity):
entity_description: StateVacuumEntityDescription
_platform_translation_key = "vacuum"

def _get_run_mode_by_tag(
self, tag: ModeTag
) -> clusters.RvcRunMode.Structs.ModeOptionStruct | None:
"""Get the run mode by tag."""
supported_run_modes = self._supported_run_modes or {}
for mode in supported_run_modes.values():
for t in mode.modeTags:
if t.value == tag.value:
return mode
return None

async def async_stop(self, **kwargs: Any) -> None:
"""Stop the vacuum cleaner."""
# We simply set the RvcRunMode to the first runmode
# that has the idle tag to stop the vacuum cleaner.
# this is compatible with both Matter 1.2 and 1.3+ devices.
supported_run_modes = self._supported_run_modes or {}
for mode in supported_run_modes.values():
for tag in mode.modeTags:
if tag.value == ModeTag.IDLE:
# stop the vacuum by changing the run mode to idle
await self.send_device_command(
clusters.RvcRunMode.Commands.ChangeToMode(newMode=mode.mode)
)
return
mode = self._get_run_mode_by_tag(ModeTag.IDLE)
if mode is None:
raise HomeAssistantError(
"No supported run mode found to stop the vacuum cleaner."
)

await self.send_device_command(
clusters.RvcRunMode.Commands.ChangeToMode(newMode=mode.mode)
)

async def async_return_to_base(self, **kwargs: Any) -> None:
"""Set the vacuum cleaner to return to the dock."""
Expand Down Expand Up @@ -110,14 +122,15 @@ async def async_start(self) -> None:
# We simply set the RvcRunMode to the first runmode
# that has the cleaning tag to start the vacuum cleaner.
# this is compatible with both Matter 1.2 and 1.3+ devices.
supported_run_modes = self._supported_run_modes or {}
for mode in supported_run_modes.values():
for tag in mode.modeTags:
if tag.value == ModeTag.CLEANING:
await self.send_device_command(
clusters.RvcRunMode.Commands.ChangeToMode(newMode=mode.mode)
)
return
mode = self._get_run_mode_by_tag(ModeTag.CLEANING)
if mode is None:
raise HomeAssistantError(
"No supported run mode found to start the vacuum cleaner."
)

await self.send_device_command(
clusters.RvcRunMode.Commands.ChangeToMode(newMode=mode.mode)
)

async def async_pause(self) -> None:
"""Pause the cleaning task."""
Expand Down
25 changes: 21 additions & 4 deletions homeassistant/components/media_source/local_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,8 @@ def __init__(self, hass: HomeAssistant, source: LocalSource) -> None:
self.hass = hass
self.source = source

async def get(
self, request: web.Request, source_dir_id: str, location: str
) -> web.FileResponse:
"""Start a GET request."""
async def _validate_media_path(self, source_dir_id: str, location: str) -> Path:
"""Validate media path and return it if valid."""
try:
raise_if_invalid_path(location)
except ValueError as err:
Expand All @@ -233,6 +231,25 @@ async def get(
if not mime_type or mime_type.split("/")[0] not in MEDIA_MIME_TYPES:
raise web.HTTPNotFound

return media_path

async def head(
self, request: web.Request, source_dir_id: str, location: str
) -> None:
"""Handle a HEAD request.

This is sent by some DLNA renderers, like Samsung ones, prior to sending
the GET request.

Check whether the location exists or not.
"""
await self._validate_media_path(source_dir_id, location)

async def get(
self, request: web.Request, source_dir_id: str, location: str
) -> web.FileResponse:
"""Handle a GET request."""
media_path = await self._validate_media_path(source_dir_id, location)
return web.FileResponse(media_path)


Expand Down
6 changes: 0 additions & 6 deletions homeassistant/components/music_assistant/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,6 @@ class MusicAssistantFavoriteButton(MusicAssistantEntity, ButtonEntity):
translation_key="favorite_now_playing",
)

@property
def available(self) -> bool:
"""Return availability of entity."""
# mark the button as unavailable if the player has no current media item
return super().available and self.player.current_media is not None

@catch_musicassistant_error
async def async_press(self) -> None:
"""Handle the button press command."""
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/ollama/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
CONF_NUM_CTX,
CONF_PROMPT,
CONF_THINK,
DEFAULT_NAME,
DEFAULT_TIMEOUT,
DOMAIN,
)
Expand Down Expand Up @@ -138,6 +139,7 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
else:
hass.config_entries.async_update_entry(
entry,
title=DEFAULT_NAME,
options={},
version=2,
)
2 changes: 2 additions & 0 deletions homeassistant/components/ollama/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

DOMAIN = "ollama"

DEFAULT_NAME = "Ollama"

CONF_MODEL = "model"
CONF_PROMPT = "prompt"
CONF_THINK = "think"
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/openai_conversation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
CONF_REASONING_EFFORT,
CONF_TEMPERATURE,
CONF_TOP_P,
DEFAULT_NAME,
DOMAIN,
LOGGER,
RECOMMENDED_CHAT_MODEL,
Expand Down Expand Up @@ -351,6 +352,7 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
else:
hass.config_entries.async_update_entry(
entry,
title=DEFAULT_NAME,
options={},
version=2,
)
2 changes: 1 addition & 1 deletion homeassistant/components/openai_conversation/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
LOGGER: logging.Logger = logging.getLogger(__package__)

DEFAULT_CONVERSATION_NAME = "OpenAI Conversation"
DEFAULT_NAME = "OpenAI Conversation"

CONF_CHAT_MODEL = "chat_model"
CONF_FILENAMES = "filenames"
CONF_MAX_TOKENS = "max_tokens"
CONF_PROMPT = "prompt"
CONF_PROMPT = "prompt"
CONF_REASONING_EFFORT = "reasoning_effort"
CONF_RECOMMENDED = "recommended"
CONF_TEMPERATURE = "temperature"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def __init__(self, entry: OpenAIConfigEntry, subentry: ConfigSubentry) -> None:
identifiers={(DOMAIN, subentry.subentry_id)},
name=subentry.title,
manufacturer="OpenAI",
model=entry.data.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL),
model=subentry.data.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL),
entry_type=dr.DeviceEntryType.SERVICE,
)
if self.subentry.data.get(CONF_LLM_HASS_API):
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/smarla/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.exceptions import ConfigEntryError

from .const import HOST, PLATFORMS

Expand All @@ -18,7 +18,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: FederwiegeConfigEntry) -

# Check if token still has access
if not await connection.refresh_token():
raise ConfigEntryAuthFailed("Invalid authentication")
raise ConfigEntryError("Invalid authentication")

federwiege = Federwiege(hass.loop, connection)
federwiege.register()
Expand Down
24 changes: 20 additions & 4 deletions homeassistant/components/telegram_bot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
HomeAssistantError,
ServiceValidationError,
)
from homeassistant.helpers import config_validation as cv
Expand Down Expand Up @@ -390,22 +391,37 @@ async def async_send_telegram_message(service: ServiceCall) -> ServiceResponse:
elif msgtype == SERVICE_DELETE_MESSAGE:
await notify_service.delete_message(context=service.context, **kwargs)
elif msgtype == SERVICE_LEAVE_CHAT:
messages = await notify_service.leave_chat(
context=service.context, **kwargs
)
await notify_service.leave_chat(context=service.context, **kwargs)
elif msgtype == SERVICE_SET_MESSAGE_REACTION:
await notify_service.set_message_reaction(context=service.context, **kwargs)
else:
await notify_service.edit_message(
msgtype, context=service.context, **kwargs
)

if service.return_response and messages:
if service.return_response and messages is not None:
target: list[int] | None = service.data.get(ATTR_TARGET)
if not target:
target = notify_service.get_target_chat_ids(None)

failed_chat_ids = [chat_id for chat_id in target if chat_id not in messages]
if failed_chat_ids:
raise HomeAssistantError(
f"Failed targets: {failed_chat_ids}",
translation_domain=DOMAIN,
translation_key="failed_chat_ids",
translation_placeholders={
"chat_ids": ", ".join([str(i) for i in failed_chat_ids]),
"bot_name": config_entry.title,
},
)

return {
"chats": [
{"chat_id": cid, "message_id": mid} for cid, mid in messages.items()
]
}

return None

# Register notification services
Expand Down
Loading
Loading