Skip to content

Commit

Permalink
Revert "Remove deprecated supported features warning in ..." (multipl…
Browse files Browse the repository at this point in the history
…e) (#134933)
  • Loading branch information
frenck committed Jan 7, 2025
1 parent 7a55259 commit 298f059
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 27 deletions.
26 changes: 21 additions & 5 deletions homeassistant/components/camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,19 @@ def supported_features(self) -> CameraEntityFeature:
"""Flag supported features."""
return self._attr_supported_features

@property
def supported_features_compat(self) -> CameraEntityFeature:
"""Return the supported features as CameraEntityFeature.
Remove this compatibility shim in 2025.1 or later.
"""
features = self.supported_features
if type(features) is int: # noqa: E721
new_features = CameraEntityFeature(features)
self._report_deprecated_supported_features_values(new_features)
return new_features
return features

@cached_property
def is_recording(self) -> bool:
"""Return true if the device is recording."""
Expand Down Expand Up @@ -569,7 +582,7 @@ def frontend_stream_type(self) -> StreamType | None:

self._deprecate_attr_frontend_stream_type_logged = True
return self._attr_frontend_stream_type
if CameraEntityFeature.STREAM not in self.supported_features:
if CameraEntityFeature.STREAM not in self.supported_features_compat:
return None
if (
self._webrtc_provider
Expand Down Expand Up @@ -798,7 +811,9 @@ def async_update_token(self) -> None:
async def async_internal_added_to_hass(self) -> None:
"""Run when entity about to be added to hass."""
await super().async_internal_added_to_hass()
self.__supports_stream = self.supported_features & CameraEntityFeature.STREAM
self.__supports_stream = (
self.supported_features_compat & CameraEntityFeature.STREAM
)
await self.async_refresh_providers(write_state=False)

async def async_refresh_providers(self, *, write_state: bool = True) -> None:
Expand Down Expand Up @@ -838,7 +853,7 @@ async def _async_get_supported_webrtc_provider[_T](
self, fn: Callable[[HomeAssistant, Camera], Coroutine[None, None, _T | None]]
) -> _T | None:
"""Get first provider that supports this camera."""
if CameraEntityFeature.STREAM not in self.supported_features:
if CameraEntityFeature.STREAM not in self.supported_features_compat:
return None

return await fn(self.hass, self)
Expand Down Expand Up @@ -896,7 +911,7 @@ def _invalidate_camera_capabilities_cache(self) -> None:
def camera_capabilities(self) -> CameraCapabilities:
"""Return the camera capabilities."""
frontend_stream_types = set()
if CameraEntityFeature.STREAM in self.supported_features:
if CameraEntityFeature.STREAM in self.supported_features_compat:
if self._supports_native_sync_webrtc or self._supports_native_async_webrtc:
# The camera has a native WebRTC implementation
frontend_stream_types.add(StreamType.WEB_RTC)
Expand All @@ -916,7 +931,8 @@ def async_write_ha_state(self) -> None:
"""
super().async_write_ha_state()
if self.__supports_stream != (
supports_stream := self.supported_features & CameraEntityFeature.STREAM
supports_stream := self.supported_features_compat
& CameraEntityFeature.STREAM
):
self.__supports_stream = supports_stream
self._invalidate_camera_capabilities_cache()
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/cover/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,10 @@ def state_attributes(self) -> dict[str, Any]:
def supported_features(self) -> CoverEntityFeature:
"""Flag supported features."""
if (features := self._attr_supported_features) is not None:
if type(features) is int: # noqa: E721
new_features = CoverEntityFeature(features)
self._report_deprecated_supported_features_values(new_features)
return new_features
return features

supported_features = (
Expand Down
51 changes: 33 additions & 18 deletions homeassistant/components/media_player/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,19 @@ def supported_features(self) -> MediaPlayerEntityFeature:
"""Flag media player features that are supported."""
return self._attr_supported_features

@property
def supported_features_compat(self) -> MediaPlayerEntityFeature:
"""Return the supported features as MediaPlayerEntityFeature.
Remove this compatibility shim in 2025.1 or later.
"""
features = self.supported_features
if type(features) is int: # noqa: E721
new_features = MediaPlayerEntityFeature(features)
self._report_deprecated_supported_features_values(new_features)
return new_features
return features

def turn_on(self) -> None:
"""Turn the media player on."""
raise NotImplementedError
Expand Down Expand Up @@ -912,85 +925,87 @@ async def async_set_repeat(self, repeat: RepeatMode) -> None:
@property
def support_play(self) -> bool:
"""Boolean if play is supported."""
return MediaPlayerEntityFeature.PLAY in self.supported_features
return MediaPlayerEntityFeature.PLAY in self.supported_features_compat

@final
@property
def support_pause(self) -> bool:
"""Boolean if pause is supported."""
return MediaPlayerEntityFeature.PAUSE in self.supported_features
return MediaPlayerEntityFeature.PAUSE in self.supported_features_compat

@final
@property
def support_stop(self) -> bool:
"""Boolean if stop is supported."""
return MediaPlayerEntityFeature.STOP in self.supported_features
return MediaPlayerEntityFeature.STOP in self.supported_features_compat

@final
@property
def support_seek(self) -> bool:
"""Boolean if seek is supported."""
return MediaPlayerEntityFeature.SEEK in self.supported_features
return MediaPlayerEntityFeature.SEEK in self.supported_features_compat

@final
@property
def support_volume_set(self) -> bool:
"""Boolean if setting volume is supported."""
return MediaPlayerEntityFeature.VOLUME_SET in self.supported_features
return MediaPlayerEntityFeature.VOLUME_SET in self.supported_features_compat

@final
@property
def support_volume_mute(self) -> bool:
"""Boolean if muting volume is supported."""
return MediaPlayerEntityFeature.VOLUME_MUTE in self.supported_features
return MediaPlayerEntityFeature.VOLUME_MUTE in self.supported_features_compat

@final
@property
def support_previous_track(self) -> bool:
"""Boolean if previous track command supported."""
return MediaPlayerEntityFeature.PREVIOUS_TRACK in self.supported_features
return MediaPlayerEntityFeature.PREVIOUS_TRACK in self.supported_features_compat

@final
@property
def support_next_track(self) -> bool:
"""Boolean if next track command supported."""
return MediaPlayerEntityFeature.NEXT_TRACK in self.supported_features
return MediaPlayerEntityFeature.NEXT_TRACK in self.supported_features_compat

@final
@property
def support_play_media(self) -> bool:
"""Boolean if play media command supported."""
return MediaPlayerEntityFeature.PLAY_MEDIA in self.supported_features
return MediaPlayerEntityFeature.PLAY_MEDIA in self.supported_features_compat

@final
@property
def support_select_source(self) -> bool:
"""Boolean if select source command supported."""
return MediaPlayerEntityFeature.SELECT_SOURCE in self.supported_features
return MediaPlayerEntityFeature.SELECT_SOURCE in self.supported_features_compat

@final
@property
def support_select_sound_mode(self) -> bool:
"""Boolean if select sound mode command supported."""
return MediaPlayerEntityFeature.SELECT_SOUND_MODE in self.supported_features
return (
MediaPlayerEntityFeature.SELECT_SOUND_MODE in self.supported_features_compat
)

@final
@property
def support_clear_playlist(self) -> bool:
"""Boolean if clear playlist command supported."""
return MediaPlayerEntityFeature.CLEAR_PLAYLIST in self.supported_features
return MediaPlayerEntityFeature.CLEAR_PLAYLIST in self.supported_features_compat

@final
@property
def support_shuffle_set(self) -> bool:
"""Boolean if shuffle is supported."""
return MediaPlayerEntityFeature.SHUFFLE_SET in self.supported_features
return MediaPlayerEntityFeature.SHUFFLE_SET in self.supported_features_compat

@final
@property
def support_grouping(self) -> bool:
"""Boolean if player grouping is supported."""
return MediaPlayerEntityFeature.GROUPING in self.supported_features
return MediaPlayerEntityFeature.GROUPING in self.supported_features_compat

async def async_toggle(self) -> None:
"""Toggle the power on the media player."""
Expand Down Expand Up @@ -1019,7 +1034,7 @@ async def async_volume_up(self) -> None:
if (
self.volume_level is not None
and self.volume_level < 1
and MediaPlayerEntityFeature.VOLUME_SET in self.supported_features
and MediaPlayerEntityFeature.VOLUME_SET in self.supported_features_compat
):
await self.async_set_volume_level(
min(1, self.volume_level + self.volume_step)
Expand All @@ -1037,7 +1052,7 @@ async def async_volume_down(self) -> None:
if (
self.volume_level is not None
and self.volume_level > 0
and MediaPlayerEntityFeature.VOLUME_SET in self.supported_features
and MediaPlayerEntityFeature.VOLUME_SET in self.supported_features_compat
):
await self.async_set_volume_level(
max(0, self.volume_level - self.volume_step)
Expand Down Expand Up @@ -1080,7 +1095,7 @@ def media_image_local(self) -> str | None:
def capability_attributes(self) -> dict[str, Any]:
"""Return capability attributes."""
data: dict[str, Any] = {}
supported_features = self.supported_features
supported_features = self.supported_features_compat

if (
source_list := self.source_list
Expand Down Expand Up @@ -1286,7 +1301,7 @@ async def websocket_browse_media(
connection.send_error(msg["id"], "entity_not_found", "Entity not found")
return

if MediaPlayerEntityFeature.BROWSE_MEDIA not in player.supported_features:
if MediaPlayerEntityFeature.BROWSE_MEDIA not in player.supported_features_compat:
connection.send_message(
websocket_api.error_message(
msg["id"], ERR_NOT_SUPPORTED, "Player does not support browsing media"
Expand Down
17 changes: 15 additions & 2 deletions homeassistant/components/vacuum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ def battery_icon(self) -> str:
@property
def capability_attributes(self) -> dict[str, Any] | None:
"""Return capability attributes."""
if VacuumEntityFeature.FAN_SPEED in self.supported_features:
if VacuumEntityFeature.FAN_SPEED in self.supported_features_compat:
return {ATTR_FAN_SPEED_LIST: self.fan_speed_list}
return None

Expand All @@ -330,7 +330,7 @@ def fan_speed_list(self) -> list[str]:
def state_attributes(self) -> dict[str, Any]:
"""Return the state attributes of the vacuum cleaner."""
data: dict[str, Any] = {}
supported_features = self.supported_features
supported_features = self.supported_features_compat

if VacuumEntityFeature.BATTERY in supported_features:
data[ATTR_BATTERY_LEVEL] = self.battery_level
Expand Down Expand Up @@ -369,6 +369,19 @@ def supported_features(self) -> VacuumEntityFeature:
"""Flag vacuum cleaner features that are supported."""
return self._attr_supported_features

@property
def supported_features_compat(self) -> VacuumEntityFeature:
"""Return the supported features as VacuumEntityFeature.
Remove this compatibility shim in 2025.1 or later.
"""
features = self.supported_features
if type(features) is int: # noqa: E721
new_features = VacuumEntityFeature(features)
self._report_deprecated_supported_features_values(new_features)
return new_features
return features

def stop(self, **kwargs: Any) -> None:
"""Stop the vacuum cleaner."""
raise NotImplementedError
Expand Down
27 changes: 26 additions & 1 deletion homeassistant/helpers/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from collections import deque
from collections.abc import Callable, Coroutine, Iterable, Mapping
import dataclasses
from enum import Enum, auto
from enum import Enum, IntFlag, auto
import functools as ft
import logging
import math
Expand Down Expand Up @@ -1639,6 +1639,31 @@ def _suggest_report_issue(self) -> str:
self.hass, integration_domain=platform_name, module=type(self).__module__
)

@callback
def _report_deprecated_supported_features_values(
self, replacement: IntFlag
) -> None:
"""Report deprecated supported features values."""
if self._deprecated_supported_features_reported is True:
return
self._deprecated_supported_features_reported = True
report_issue = self._suggest_report_issue()
report_issue += (
" and reference "
"https://developers.home-assistant.io/blog/2023/12/28/support-feature-magic-numbers-deprecation"
)
_LOGGER.warning(
(
"Entity %s (%s) is using deprecated supported features"
" values which will be removed in HA Core 2025.1. Instead it should use"
" %s, please %s"
),
self.entity_id,
type(self),
repr(replacement),
report_issue,
)


class ToggleEntityDescription(EntityDescription, frozen_or_thawed=True):
"""A class that describes toggle entities."""
Expand Down
20 changes: 20 additions & 0 deletions tests/components/camera/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,26 @@ def test_deprecated_state_constants(
import_and_test_deprecated_constant_enum(caplog, module, enum, "STATE_", "2025.10")


def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) -> None:
"""Test deprecated supported features ints."""

class MockCamera(camera.Camera):
@property
def supported_features(self) -> int:
"""Return supported features."""
return 1

entity = MockCamera()
assert entity.supported_features_compat is camera.CameraEntityFeature(1)
assert "MockCamera" in caplog.text
assert "is using deprecated supported features values" in caplog.text
assert "Instead it should use" in caplog.text
assert "CameraEntityFeature.ON_OFF" in caplog.text
caplog.clear()
assert entity.supported_features_compat is camera.CameraEntityFeature(1)
assert "is using deprecated supported features values" not in caplog.text


@pytest.mark.usefixtures("mock_camera")
async def test_entity_picture_url_changes_on_token_update(hass: HomeAssistant) -> None:
"""Test the token is rotated and entity entity picture cache is cleared."""
Expand Down
19 changes: 19 additions & 0 deletions tests/components/cover/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from enum import Enum

import pytest

from homeassistant.components import cover
from homeassistant.components.cover import CoverState
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM, SERVICE_TOGGLE
Expand Down Expand Up @@ -153,3 +155,20 @@ def _create_tuples(enum: type[Enum], constant_prefix: str) -> list[tuple[Enum, s
def test_all() -> None:
"""Test module.__all__ is correctly set."""
help_test_all(cover)


def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) -> None:
"""Test deprecated supported features ints."""

class MockCoverEntity(cover.CoverEntity):
_attr_supported_features = 1

entity = MockCoverEntity()
assert entity.supported_features is cover.CoverEntityFeature(1)
assert "MockCoverEntity" in caplog.text
assert "is using deprecated supported features values" in caplog.text
assert "Instead it should use" in caplog.text
assert "CoverEntityFeature.OPEN" in caplog.text
caplog.clear()
assert entity.supported_features is cover.CoverEntityFeature(1)
assert "is using deprecated supported features values" not in caplog.text
Loading

0 comments on commit 298f059

Please sign in to comment.