From 17b2678aee8c575acc5f301bea2ae8b8d9513caa Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 28 May 2021 12:32:31 +0200 Subject: [PATCH] Define media_player entity attributes as class variables (#51192) --- .../components/dunehd/media_player.py | 4 +- homeassistant/components/heos/media_player.py | 3 +- .../components/media_player/__init__.py | 160 +++++++++++------- .../components/spotify/media_player.py | 54 ++---- 4 files changed, 115 insertions(+), 106 deletions(-) diff --git a/homeassistant/components/dunehd/media_player.py b/homeassistant/components/dunehd/media_player.py index 17e9b6d9a37..482b92f768e 100644 --- a/homeassistant/components/dunehd/media_player.py +++ b/homeassistant/components/dunehd/media_player.py @@ -31,7 +31,7 @@ from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import ATTR_MANUFACTURER, DEFAULT_NAME, DOMAIN @@ -100,7 +100,7 @@ class DuneHDPlayerEntity(MediaPlayerEntity): return True @property - def state(self) -> StateType: + def state(self) -> str | None: """Return player state.""" state = STATE_OFF if "playback_position" in self._state: diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py index 63b592f1359..46a751983e9 100644 --- a/homeassistant/components/heos/media_player.py +++ b/homeassistant/components/heos/media_player.py @@ -1,7 +1,6 @@ """Denon HEOS Media Player.""" from __future__ import annotations -from collections.abc import Sequence from functools import reduce, wraps import logging from operator import ior @@ -362,7 +361,7 @@ class HeosMediaPlayer(MediaPlayerEntity): return self._source_manager.get_current_source(self._player.now_playing_media) @property - def source_list(self) -> Sequence[str]: + def source_list(self) -> list[str]: """List of available input sources.""" return self._source_manager.source_list diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 6fca2a4c3d5..0b4a5157c72 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -5,7 +5,7 @@ import asyncio import base64 import collections from contextlib import suppress -from datetime import timedelta +import datetime as dt import functools as ft import hashlib import logging @@ -57,6 +57,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, + datetime, ) from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent @@ -137,7 +138,7 @@ CACHE_URL = "url" CACHE_CONTENT = "content" ENTITY_IMAGE_CACHE = {CACHE_IMAGES: collections.OrderedDict(), CACHE_MAXSIZE: 16} -SCAN_INTERVAL = timedelta(seconds=10) +SCAN_INTERVAL = dt.timedelta(seconds=10) DEVICE_CLASS_TV = "tv" DEVICE_CLASS_SPEAKER = "speaker" @@ -371,11 +372,43 @@ class MediaPlayerEntity(Entity): _access_token: str | None = None + _attr_app_id: str | None = None + _attr_app_name: str | None = None + _attr_group_members: list[str] | None = None + _attr_is_volume_muted: bool | None = None + _attr_media_album_artist: str | None = None + _attr_media_album_name: str | None = None + _attr_media_artist: str | None = None + _attr_media_channel: str | None = None + _attr_media_content_id: str | None = None + _attr_media_content_type: str | None = None + _attr_media_duration: int | None = None + _attr_media_episode: str | None = None + _attr_media_image_hash: str | None + _attr_media_image_remotely_accessible: bool = False + _attr_media_image_url: str | None = None + _attr_media_playlist: str | None = None + _attr_media_position_updated_at: dt.datetime | None = None + _attr_media_position: int | None = None + _attr_media_season: str | None = None + _attr_media_series_title: str | None = None + _attr_media_title: str | None = None + _attr_media_track: int | None = None + _attr_repeat: str | None = None + _attr_shuffle: bool | None = None + _attr_sound_mode_list: list[str] | None = None + _attr_sound_mode: str | None = None + _attr_source_list: list[str] | None = None + _attr_source: str | None = None + _attr_state: str | None = None + _attr_supported_features: int = 0 + _attr_volume_level: float | None = None + # Implement these for your media player @property - def state(self): + def state(self) -> str | None: """State of the player.""" - return None + return self._attr_state @property def access_token(self) -> str: @@ -385,56 +418,59 @@ class MediaPlayerEntity(Entity): return self._access_token @property - def volume_level(self): + def volume_level(self) -> float | None: """Volume level of the media player (0..1).""" - return None + return self._attr_volume_level @property - def is_volume_muted(self): + def is_volume_muted(self) -> bool | None: """Boolean if volume is currently muted.""" - return None + return self._attr_is_volume_muted @property - def media_content_id(self): + def media_content_id(self) -> str | None: """Content ID of current playing media.""" - return None + return self._attr_media_content_id @property - def media_content_type(self): + def media_content_type(self) -> str | None: """Content type of current playing media.""" - return None + return self._attr_media_content_type @property - def media_duration(self): + def media_duration(self) -> int | None: """Duration of current playing media in seconds.""" - return None + return self._attr_media_duration @property - def media_position(self): + def media_position(self) -> int | None: """Position of current playing media in seconds.""" - return None + return self._attr_media_position @property - def media_position_updated_at(self): + def media_position_updated_at(self) -> dt.datetime | None: """When was the position of the current playing media valid. Returns value from homeassistant.util.dt.utcnow(). """ - return None + return self._attr_media_position_updated_at @property - def media_image_url(self): + def media_image_url(self) -> str | None: """Image url of current playing media.""" - return None + return self._attr_media_image_url @property def media_image_remotely_accessible(self) -> bool: """If the image url is remotely accessible.""" - return False + return self._attr_media_image_remotely_accessible @property - def media_image_hash(self): + def media_image_hash(self) -> str | None: """Hash value for media image.""" + if hasattr(self, "_attr_media_image_hash"): + return self._attr_media_image_hash + url = self.media_image_url if url is not None: return hashlib.sha256(url.encode("utf-8")).hexdigest()[:16] @@ -463,104 +499,104 @@ class MediaPlayerEntity(Entity): return None, None @property - def media_title(self): + def media_title(self) -> str | None: """Title of current playing media.""" - return None + return self._attr_media_title @property - def media_artist(self): + def media_artist(self) -> str | None: """Artist of current playing media, music track only.""" - return None + return self._attr_media_artist @property - def media_album_name(self): + def media_album_name(self) -> str | None: """Album name of current playing media, music track only.""" - return None + return self._attr_media_album_name @property - def media_album_artist(self): + def media_album_artist(self) -> str | None: """Album artist of current playing media, music track only.""" - return None + return self._attr_media_album_artist @property - def media_track(self): + def media_track(self) -> int | None: """Track number of current playing media, music track only.""" - return None + return self._attr_media_track @property - def media_series_title(self): + def media_series_title(self) -> str | None: """Title of series of current playing media, TV show only.""" - return None + return self._attr_media_series_title @property - def media_season(self): + def media_season(self) -> str | None: """Season of current playing media, TV show only.""" - return None + return self._attr_media_season @property - def media_episode(self): + def media_episode(self) -> str | None: """Episode of current playing media, TV show only.""" - return None + return self._attr_media_episode @property - def media_channel(self): + def media_channel(self) -> str | None: """Channel currently playing.""" - return None + return self._attr_media_channel @property - def media_playlist(self): + def media_playlist(self) -> str | None: """Title of Playlist currently playing.""" - return None + return self._attr_media_playlist @property - def app_id(self): + def app_id(self) -> str | None: """ID of the current running app.""" - return None + return self._attr_app_id @property - def app_name(self): + def app_name(self) -> str | None: """Name of the current running app.""" - return None + return self._attr_app_name @property - def source(self): + def source(self) -> str | None: """Name of the current input source.""" - return None + return self._attr_source @property - def source_list(self): + def source_list(self) -> list[str] | None: """List of available input sources.""" - return None + return self._attr_source_list @property - def sound_mode(self): + def sound_mode(self) -> str | None: """Name of the current sound mode.""" - return None + return self._attr_sound_mode @property - def sound_mode_list(self): + def sound_mode_list(self) -> list[str] | None: """List of available sound modes.""" - return None + return self._attr_sound_mode_list @property - def shuffle(self): + def shuffle(self) -> bool | None: """Boolean if shuffle is enabled.""" - return None + return self._attr_shuffle @property - def repeat(self): + def repeat(self) -> str | None: """Return current repeat mode.""" - return None + return self._attr_repeat @property - def group_members(self): + def group_members(self) -> list[str] | None: """List of members which are currently grouped together.""" - return None + return self._attr_group_members @property - def supported_features(self): + def supported_features(self) -> int: """Flag media player features that are supported.""" - return 0 + return self._attr_supported_features def turn_on(self): """Turn the media player on.""" diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index e9ae2367273..1c92e2ce51a 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -67,8 +67,6 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -ICON = "mdi:spotify" - SCAN_INTERVAL = timedelta(seconds=30) SUPPORT_SPOTIFY = ( @@ -211,12 +209,12 @@ def spotify_exception_handler(func): def wrapper(self, *args, **kwargs): try: result = func(self, *args, **kwargs) - self.player_available = True + self._attr_available = True return result except requests.RequestException: - self.player_available = False + self._attr_available = False except SpotifyException as exc: - self.player_available = False + self._attr_available = False if exc.reason == "NO_ACTIVE_DEVICE": raise HomeAssistantError("No active playback device found") from None @@ -226,6 +224,10 @@ def spotify_exception_handler(func): class SpotifyMediaPlayer(MediaPlayerEntity): """Representation of a Spotify controller.""" + _attr_icon = "mdi:spotify" + _attr_media_content_type = MEDIA_TYPE_MUSIC + _attr_media_image_remotely_accessible = False + def __init__( self, session: OAuth2Session, @@ -247,40 +249,22 @@ class SpotifyMediaPlayer(MediaPlayerEntity): self._currently_playing: dict | None = {} self._devices: list[dict] | None = [] self._playlist: dict | None = None - self._spotify: Spotify = None - self.player_available = False - - @property - def name(self) -> str: - """Return the name.""" - return self._name - - @property - def icon(self) -> str: - """Return the icon.""" - return ICON - - @property - def available(self) -> bool: - """Return True if entity is available.""" - return self.player_available - - @property - def unique_id(self) -> str: - """Return the unique ID.""" - return self._id + self._attr_name = self._name + self._attr_unique_id = user_id @property def device_info(self) -> DeviceInfo: """Return device information about this entity.""" + model = "Spotify Free" if self._me is not None: - model = self._me["product"] + product = self._me["product"] + model = f"Spotify {product}" return { "identifiers": {(DOMAIN, self._id)}, "manufacturer": "Spotify AB", - "model": f"Spotify {model}".rstrip(), + "model": model, "name": self._name, } @@ -304,11 +288,6 @@ class SpotifyMediaPlayer(MediaPlayerEntity): item = self._currently_playing.get("item") or {} return item.get("uri") - @property - def media_content_type(self) -> str | None: - """Return the media type.""" - return MEDIA_TYPE_MUSIC - @property def media_duration(self) -> int | None: """Duration of current playing media in seconds.""" @@ -340,11 +319,6 @@ class SpotifyMediaPlayer(MediaPlayerEntity): return None return fetch_image_url(self._currently_playing["item"]["album"]) - @property - def media_image_remotely_accessible(self) -> bool: - """If the image url is remotely accessible.""" - return False - @property def media_title(self) -> str | None: """Return the media title.""" @@ -357,7 +331,7 @@ class SpotifyMediaPlayer(MediaPlayerEntity): if self._currently_playing.get("item") is None: return None return ", ".join( - [artist["name"] for artist in self._currently_playing["item"]["artists"]] + artist["name"] for artist in self._currently_playing["item"]["artists"] ) @property