Compare commits

..

2 Commits

Author SHA1 Message Date
epenet f0da2e8038 Use a dict to distinguish property/attribute in media_player (#175193) 2026-07-01 10:58:34 +02:00
Robert Resch d4c626c0e0 Bump deebot-client to 18.4.0 (#175237) 2026-07-01 10:54:04 +02:00
8 changed files with 42 additions and 80 deletions
@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
"requirements": ["py-sucks==0.9.11", "deebot-client==18.3.0"]
"requirements": ["py-sucks==0.9.11", "deebot-client==18.4.0"]
}
@@ -202,31 +202,31 @@ MEDIA_PLAYER_BROWSE_MEDIA_SCHEMA = {
}
ATTR_TO_PROPERTY = [
MediaPlayerEntityStateAttribute.MEDIA_VOLUME_LEVEL,
MediaPlayerEntityStateAttribute.MEDIA_VOLUME_MUTED,
MediaPlayerEntityStateAttribute.MEDIA_CONTENT_ID,
MediaPlayerEntityStateAttribute.MEDIA_CONTENT_TYPE,
MediaPlayerEntityStateAttribute.MEDIA_DURATION,
MediaPlayerEntityStateAttribute.MEDIA_POSITION,
MediaPlayerEntityStateAttribute.MEDIA_POSITION_UPDATED_AT,
MediaPlayerEntityStateAttribute.MEDIA_TITLE,
MediaPlayerEntityStateAttribute.MEDIA_ARTIST,
MediaPlayerEntityStateAttribute.MEDIA_ALBUM_NAME,
MediaPlayerEntityStateAttribute.MEDIA_ALBUM_ARTIST,
MediaPlayerEntityStateAttribute.MEDIA_TRACK,
MediaPlayerEntityStateAttribute.MEDIA_SERIES_TITLE,
MediaPlayerEntityStateAttribute.MEDIA_SEASON,
MediaPlayerEntityStateAttribute.MEDIA_EPISODE,
MediaPlayerEntityStateAttribute.MEDIA_CHANNEL,
MediaPlayerEntityStateAttribute.MEDIA_PLAYLIST,
MediaPlayerEntityStateAttribute.APP_ID,
MediaPlayerEntityStateAttribute.APP_NAME,
MediaPlayerEntityStateAttribute.INPUT_SOURCE,
MediaPlayerEntityStateAttribute.SOUND_MODE,
MediaPlayerEntityStateAttribute.MEDIA_SHUFFLE,
MediaPlayerEntityStateAttribute.MEDIA_REPEAT,
]
PROP_TO_ATTR = {
"volume_level": MediaPlayerEntityStateAttribute.MEDIA_VOLUME_LEVEL,
"is_volume_muted": MediaPlayerEntityStateAttribute.MEDIA_VOLUME_MUTED,
"media_content_id": MediaPlayerEntityStateAttribute.MEDIA_CONTENT_ID,
"media_content_type": MediaPlayerEntityStateAttribute.MEDIA_CONTENT_TYPE,
"media_duration": MediaPlayerEntityStateAttribute.MEDIA_DURATION,
"media_position": MediaPlayerEntityStateAttribute.MEDIA_POSITION,
"media_position_updated_at": MediaPlayerEntityStateAttribute.MEDIA_POSITION_UPDATED_AT,
"media_title": MediaPlayerEntityStateAttribute.MEDIA_TITLE,
"media_artist": MediaPlayerEntityStateAttribute.MEDIA_ARTIST,
"media_album_name": MediaPlayerEntityStateAttribute.MEDIA_ALBUM_NAME,
"media_album_artist": MediaPlayerEntityStateAttribute.MEDIA_ALBUM_ARTIST,
"media_track": MediaPlayerEntityStateAttribute.MEDIA_TRACK,
"media_series_title": MediaPlayerEntityStateAttribute.MEDIA_SERIES_TITLE,
"media_season": MediaPlayerEntityStateAttribute.MEDIA_SEASON,
"media_episode": MediaPlayerEntityStateAttribute.MEDIA_EPISODE,
"media_channel": MediaPlayerEntityStateAttribute.MEDIA_CHANNEL,
"media_playlist": MediaPlayerEntityStateAttribute.MEDIA_PLAYLIST,
"app_id": MediaPlayerEntityStateAttribute.APP_ID,
"app_name": MediaPlayerEntityStateAttribute.APP_NAME,
"source": MediaPlayerEntityStateAttribute.INPUT_SOURCE,
"sound_mode": MediaPlayerEntityStateAttribute.SOUND_MODE,
"shuffle": MediaPlayerEntityStateAttribute.MEDIA_SHUFFLE,
"repeat": MediaPlayerEntityStateAttribute.MEDIA_REPEAT,
}
# mypy: disallow-any-generics
@@ -1141,8 +1141,8 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
if self.state == MediaPlayerState.OFF:
return state_attr
for attr in ATTR_TO_PROPERTY:
if (value := getattr(self, attr)) is not None:
for prop, attr in PROP_TO_ATTR.items():
if (value := getattr(self, prop)) is not None:
state_attr[attr] = value
if self.media_image_remotely_accessible:
@@ -13,7 +13,7 @@ from . import (
ATTR_MEDIA_POSITION,
ATTR_MEDIA_POSITION_UPDATED_AT,
ATTR_MEDIA_VOLUME_LEVEL,
ATTR_TO_PROPERTY,
PROP_TO_ATTR,
)
INSIGNIFICANT_ATTRIBUTES: set[str] = {
@@ -23,7 +23,7 @@ INSIGNIFICANT_ATTRIBUTES: set[str] = {
SIGNIFICANT_ATTRIBUTES: set[str] = {
ATTR_ENTITY_PICTURE_LOCAL,
*ATTR_TO_PROPERTY,
*PROP_TO_ATTR.values(),
} - INSIGNIFICANT_ATTRIBUTES
+5 -3
View File
@@ -36,7 +36,7 @@ from homeassistant.helpers.selector import (
NumericThresholdSelector,
NumericThresholdSelectorConfig,
)
from homeassistant.helpers.sun import get_astral_event_date, get_astral_observer, is_up
from homeassistant.helpers.sun import get_astral_event_date, get_astral_observer
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util
@@ -226,7 +226,8 @@ class _UpCondition(_SunStateCondition):
@override
def _async_check(self, **kwargs: Unpack[ConditionCheckParams]) -> bool:
"""Check the condition."""
return is_up(self._hass)
elevation, _ = _solar_position(self._hass)
return elevation >= ELEVATION_HORIZON
class _SetCondition(_SunStateCondition):
@@ -235,7 +236,8 @@ class _SetCondition(_SunStateCondition):
@override
def _async_check(self, **kwargs: Unpack[ConditionCheckParams]) -> bool:
"""Check the condition."""
return not is_up(self._hass)
elevation, _ = _solar_position(self._hass)
return elevation < ELEVATION_HORIZON
class _AscendingCondition(_SunStateCondition):
+2 -2
View File
@@ -20,13 +20,13 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.sun import (
get_astral_observer,
get_observer_astral_event_next,
is_up,
)
from homeassistant.util import dt as dt_util
from .const import (
ELEVATION_ASTRONOMICAL,
ELEVATION_CIVIL,
ELEVATION_HORIZON,
ELEVATION_NAUTICAL,
SIGNAL_EVENTS_CHANGED,
SIGNAL_POSITION_CHANGED,
@@ -162,7 +162,7 @@ class Sun(Entity):
@override
def state(self) -> str:
"""Return the state of the sun."""
if is_up(self.hass):
if self.solar_elevation > ELEVATION_HORIZON:
return STATE_ABOVE_HORIZON
return STATE_BELOW_HORIZON
+1 -1
View File
@@ -809,7 +809,7 @@ debugpy==1.8.21
decora-wifi==1.4
# homeassistant.components.ecovacs
deebot-client==18.3.0
deebot-client==18.4.0
# homeassistant.components.ihc
# homeassistant.components.ohmconnect
@@ -1318,7 +1318,7 @@ async def test_unavailable_device(
# Check attributes are unavailable
attrs = mock_state.attributes
for attr in mp.ATTR_TO_PROPERTY:
for attr in mp.PROP_TO_ATTR.values():
assert attr not in attrs
assert attrs[ha_const.ATTR_FRIENDLY_NAME] == MOCK_DEVICE_NAME
@@ -2081,8 +2081,8 @@ async def test_disappearing_device(
entity: DlnaDmrEntity = hass.data[mp.DOMAIN].get_entity(mock_disconnected_entity_id)
# Test attribute access
for attr in mp.ATTR_TO_PROPERTY:
value = getattr(entity, attr)
for prop in mp.PROP_TO_ATTR:
value = getattr(entity, prop)
assert value is None
# media_image_url is normally hidden by entity_picture, but we want a direct check
+1 -41
View File
@@ -1,6 +1,6 @@
"""The tests for sun conditions."""
from datetime import datetime, timedelta
from datetime import datetime
from freezegun import freeze_time
import pytest
@@ -11,7 +11,6 @@ from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import trace
from homeassistant.helpers.condition import async_validate_condition_config
from homeassistant.helpers.sun import get_astral_event_next
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
@@ -1460,45 +1459,6 @@ async def test_sun_state_condition_takes_no_options(
)
async def test_is_set_agrees_with_sunset_trigger_time(
hass: HomeAssistant,
service_calls: list[ServiceCall],
) -> None:
"""Test is_set flips at the exact calculated sunset time."""
latitude, longitude, time_zone = _SAN_DIEGO
await hass.config.async_set_time_zone(time_zone)
hass.config.latitude = latitude
hass.config.longitude = longitude
ref = datetime(2015, 9, 15, 12, tzinfo=dt_util.UTC)
with freeze_time(ref):
sunset = get_astral_event_next(hass, SUN_EVENT_SUNSET, ref)
await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"trigger": {"platform": "event", "event_type": "test_event"},
"condition": {"condition": "sun.is_set"},
"action": {"service": "test.automation"},
}
},
)
# One second before sunset: is_set should be false
with freeze_time(sunset - timedelta(seconds=1)):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(service_calls) == 0
# One second after sunset: is_set should be true
with freeze_time(sunset + timedelta(seconds=1)):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(service_calls) == 1
@pytest.mark.parametrize(
("threshold", "elevation", "expected"),
[