Type Spotify hass data (#66285)

pull/66299/head
Franck Nijhof 2022-02-10 21:38:33 +01:00 committed by GitHub
parent c91a20537a
commit c6f3c5da79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 64 additions and 112 deletions

View File

@ -1,5 +1,8 @@
"""The spotify integration."""
from dataclasses import dataclass
from typing import Any
import aiohttp
from spotipy import Spotify, SpotifyException
import voluptuous as vol
@ -22,13 +25,7 @@ from homeassistant.helpers.typing import ConfigType
from . import config_flow
from .browse_media import async_browse_media
from .const import (
DATA_SPOTIFY_CLIENT,
DATA_SPOTIFY_ME,
DATA_SPOTIFY_SESSION,
DOMAIN,
SPOTIFY_SCOPES,
)
from .const import DOMAIN, SPOTIFY_SCOPES
from .util import is_spotify_media_type, resolve_spotify_media_type
CONFIG_SCHEMA = vol.Schema(
@ -54,6 +51,15 @@ __all__ = [
]
@dataclass
class HomeAssistantSpotifyData:
"""Spotify data stored in the Home Assistant data object."""
client: Spotify
current_user: dict[str, Any]
session: OAuth2Session
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Spotify integration."""
if DOMAIN not in config:
@ -92,12 +98,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except SpotifyException as err:
raise ConfigEntryNotReady from err
if not current_user:
raise ConfigEntryNotReady
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
DATA_SPOTIFY_CLIENT: spotify,
DATA_SPOTIFY_ME: current_user,
DATA_SPOTIFY_SESSION: session,
}
hass.data[DOMAIN][entry.entry_id] = HomeAssistantSpotifyData(
client=spotify,
current_user=current_user,
session=session,
)
if not set(session.token["scope"].split(" ")).issuperset(SPOTIFY_SCOPES):
raise ConfigEntryAuthFailed

View File

@ -27,15 +27,7 @@ from homeassistant.components.media_player.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
from .const import (
DATA_SPOTIFY_CLIENT,
DATA_SPOTIFY_ME,
DATA_SPOTIFY_SESSION,
DOMAIN,
MEDIA_PLAYER_PREFIX,
MEDIA_TYPE_SHOW,
PLAYABLE_MEDIA_TYPES,
)
from .const import DOMAIN, MEDIA_PLAYER_PREFIX, MEDIA_TYPE_SHOW, PLAYABLE_MEDIA_TYPES
from .util import fetch_image_url
BROWSE_LIMIT = 48
@ -155,9 +147,9 @@ async def async_browse_media(
raise BrowseError("No Spotify accounts available")
return await async_browse_media_internal(
hass,
info[DATA_SPOTIFY_CLIENT],
info[DATA_SPOTIFY_SESSION],
info[DATA_SPOTIFY_ME],
info.client,
info.session,
info.current_user,
media_content_type,
media_content_id,
can_play_artist=can_play_artist,

View File

@ -9,10 +9,6 @@ from homeassistant.components.media_player.const import (
DOMAIN = "spotify"
DATA_SPOTIFY_CLIENT = "spotify_client"
DATA_SPOTIFY_ME = "spotify_me"
DATA_SPOTIFY_SESSION = "spotify_session"
SPOTIFY_SCOPES = [
# Needed to be able to control playback
"user-modify-playback-state",

View File

@ -5,7 +5,6 @@ from asyncio import run_coroutine_threadsafe
import datetime as dt
from datetime import timedelta
import logging
from typing import Any
import requests
from spotipy import Spotify, SpotifyException
@ -33,31 +32,17 @@ from homeassistant.components.media_player.const import (
SUPPORT_VOLUME_SET,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_ID,
CONF_NAME,
STATE_IDLE,
STATE_PAUSED,
STATE_PLAYING,
)
from homeassistant.const import CONF_ID, STATE_IDLE, STATE_PAUSED, STATE_PLAYING
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.dt import utc_from_timestamp
from . import HomeAssistantSpotifyData
from .browse_media import async_browse_media_internal
from .const import (
DATA_SPOTIFY_CLIENT,
DATA_SPOTIFY_ME,
DATA_SPOTIFY_SESSION,
DOMAIN,
MEDIA_PLAYER_PREFIX,
PLAYABLE_MEDIA_TYPES,
SPOTIFY_SCOPES,
)
from .const import DOMAIN, MEDIA_PLAYER_PREFIX, PLAYABLE_MEDIA_TYPES, SPOTIFY_SCOPES
from .util import fetch_image_url
_LOGGER = logging.getLogger(__name__)
@ -98,7 +83,7 @@ async def async_setup_entry(
spotify = SpotifyMediaPlayer(
hass.data[DOMAIN][entry.entry_id],
entry.data[CONF_ID],
entry.data[CONF_NAME],
entry.title,
)
async_add_entities([spotify], True)
@ -135,57 +120,36 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
def __init__(
self,
spotify_data,
data: HomeAssistantSpotifyData,
user_id: str,
name: str,
) -> None:
"""Initialize."""
self._id = user_id
self._spotify_data = spotify_data
self._name = f"Spotify {name}"
self._scope_ok = set(self._session.token["scope"].split(" ")).issuperset(
SPOTIFY_SCOPES
)
self.data = data
self._currently_playing: dict | None = {}
self._devices: list[dict] | None = []
self._playlist: dict | None = None
self._attr_name = self._name
self._attr_name = f"Spotify {name}"
self._attr_unique_id = user_id
@property
def _me(self) -> dict[str, Any]:
"""Return spotify user info."""
return self._spotify_data[DATA_SPOTIFY_ME]
if self.data.current_user["product"] == "premium":
self._attr_supported_features = SUPPORT_SPOTIFY
@property
def _session(self) -> OAuth2Session:
"""Return spotify session."""
return self._spotify_data[DATA_SPOTIFY_SESSION]
@property
def _spotify(self) -> Spotify:
"""Return spotify API."""
return self._spotify_data[DATA_SPOTIFY_CLIENT]
@property
def device_info(self) -> DeviceInfo:
"""Return device information about this entity."""
model = "Spotify Free"
if self._me is not None:
product = self._me["product"]
model = f"Spotify {product}"
return DeviceInfo(
identifiers={(DOMAIN, self._id)},
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, user_id)},
manufacturer="Spotify AB",
model=model,
name=self._name,
model=f"Spotify {data.current_user['product']}",
name=f"Spotify {name}",
entry_type=DeviceEntryType.SERVICE,
configuration_url="https://open.spotify.com",
)
self._scope_ok = set(data.session.token["scope"].split(" ")).issuperset(
SPOTIFY_SCOPES
)
self._currently_playing: dict | None = {}
self._devices: list[dict] | None = []
self._playlist: dict | None = None
@property
def state(self) -> str | None:
"""Return the playback state."""
@ -315,42 +279,35 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
return None
return REPEAT_MODE_MAPPING_TO_HA.get(repeat_state)
@property
def supported_features(self) -> int:
"""Return the media player features that are supported."""
if self._me["product"] != "premium":
return 0
return SUPPORT_SPOTIFY
@spotify_exception_handler
def set_volume_level(self, volume: int) -> None:
"""Set the volume level."""
self._spotify.volume(int(volume * 100))
self.data.client.volume(int(volume * 100))
@spotify_exception_handler
def media_play(self) -> None:
"""Start or resume playback."""
self._spotify.start_playback()
self.data.client.start_playback()
@spotify_exception_handler
def media_pause(self) -> None:
"""Pause playback."""
self._spotify.pause_playback()
self.data.client.pause_playback()
@spotify_exception_handler
def media_previous_track(self) -> None:
"""Skip to previous track."""
self._spotify.previous_track()
self.data.client.previous_track()
@spotify_exception_handler
def media_next_track(self) -> None:
"""Skip to next track."""
self._spotify.next_track()
self.data.client.next_track()
@spotify_exception_handler
def media_seek(self, position):
"""Send seek command."""
self._spotify.seek_track(int(position * 1000))
self.data.client.seek_track(int(position * 1000))
@spotify_exception_handler
def play_media(self, media_type: str, media_id: str, **kwargs) -> None:
@ -379,7 +336,7 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
):
kwargs["device_id"] = self._devices[0].get("id")
self._spotify.start_playback(**kwargs)
self.data.client.start_playback(**kwargs)
@spotify_exception_handler
def select_source(self, source: str) -> None:
@ -389,7 +346,7 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
for device in self._devices:
if device["name"] == source:
self._spotify.transfer_playback(
self.data.client.transfer_playback(
device["id"], self.state == STATE_PLAYING
)
return
@ -397,14 +354,14 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
@spotify_exception_handler
def set_shuffle(self, shuffle: bool) -> None:
"""Enable/Disable shuffle mode."""
self._spotify.shuffle(shuffle)
self.data.client.shuffle(shuffle)
@spotify_exception_handler
def set_repeat(self, repeat: str) -> None:
"""Set repeat mode."""
if repeat not in REPEAT_MODE_MAPPING_TO_SPOTIFY:
raise ValueError(f"Unsupported repeat mode: {repeat}")
self._spotify.repeat(REPEAT_MODE_MAPPING_TO_SPOTIFY[repeat])
self.data.client.repeat(REPEAT_MODE_MAPPING_TO_SPOTIFY[repeat])
@spotify_exception_handler
def update(self) -> None:
@ -412,23 +369,21 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
if not self.enabled:
return
if not self._session.valid_token or self._spotify is None:
if not self.data.session.valid_token or self.data.client is None:
run_coroutine_threadsafe(
self._session.async_ensure_token_valid(), self.hass.loop
self.data.session.async_ensure_token_valid(), self.hass.loop
).result()
self._spotify_data[DATA_SPOTIFY_CLIENT] = Spotify(
auth=self._session.token["access_token"]
)
self.data.client = Spotify(auth=self.data.session.token["access_token"])
current = self._spotify.current_playback()
current = self.data.client.current_playback()
self._currently_playing = current or {}
self._playlist = None
context = self._currently_playing.get("context")
if context is not None and context["type"] == MEDIA_TYPE_PLAYLIST:
self._playlist = self._spotify.playlist(current["context"]["uri"])
self._playlist = self.data.client.playlist(current["context"]["uri"])
devices = self._spotify.devices() or {}
devices = self.data.client.devices() or {}
self._devices = devices.get("devices", [])
async def async_browse_media(
@ -444,9 +399,9 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
return await async_browse_media_internal(
self.hass,
self._spotify,
self._session,
self._me,
self.data.client,
self.data.session,
self.data.current_user,
media_content_type,
media_content_id,
)