Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """Coordinator for Spotify."""
2 
3 from dataclasses import dataclass
4 from datetime import datetime, timedelta
5 import logging
6 from typing import TYPE_CHECKING
7 
8 from spotifyaio import (
9  ContextType,
10  PlaybackState,
11  Playlist,
12  SpotifyClient,
13  SpotifyConnectionError,
14  SpotifyNotFoundError,
15  UserProfile,
16 )
17 
18 from homeassistant.config_entries import ConfigEntry
19 from homeassistant.core import HomeAssistant
20 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
21 import homeassistant.util.dt as dt_util
22 
23 from .const import DOMAIN
24 
25 if TYPE_CHECKING:
26  from .models import SpotifyData
27 
28 _LOGGER = logging.getLogger(__name__)
29 
30 
31 type SpotifyConfigEntry = ConfigEntry[SpotifyData]
32 
33 
34 @dataclass
36  """Class to hold Spotify data."""
37 
38  current_playback: PlaybackState | None
39  position_updated_at: datetime | None
40  playlist: Playlist | None
41  dj_playlist: bool = False
42 
43 
44 # This is a minimal representation of the DJ playlist that Spotify now offers
45 # The DJ is not fully integrated with the playlist API, so we need to guard
46 # against trying to fetch it as a regular playlist
47 SPOTIFY_DJ_PLAYLIST_URI = "spotify:playlist:37i9dQZF1EYkqdzj48dyYq"
48 
49 
50 class SpotifyCoordinator(DataUpdateCoordinator[SpotifyCoordinatorData]):
51  """Class to manage fetching Spotify data."""
52 
53  current_user: UserProfile
54  config_entry: SpotifyConfigEntry
55 
56  def __init__(self, hass: HomeAssistant, client: SpotifyClient) -> None:
57  """Initialize."""
58  super().__init__(
59  hass,
60  _LOGGER,
61  name=DOMAIN,
62  update_interval=timedelta(seconds=30),
63  )
64  self.clientclient = client
65  self._playlist_playlist: Playlist | None = None
66  self._checked_playlist_id_checked_playlist_id: str | None = None
67 
68  async def _async_setup(self) -> None:
69  """Set up the coordinator."""
70  try:
71  self.current_usercurrent_user = await self.clientclient.get_current_user()
72  except SpotifyConnectionError as err:
73  raise UpdateFailed("Error communicating with Spotify API") from err
74 
75  async def _async_update_data(self) -> SpotifyCoordinatorData:
76  try:
77  current = await self.clientclient.get_playback()
78  except SpotifyConnectionError as err:
79  raise UpdateFailed("Error communicating with Spotify API") from err
80  if not current:
82  current_playback=None,
83  position_updated_at=None,
84  playlist=None,
85  )
86  # Record the last updated time, because Spotify's timestamp property is unreliable
87  # and doesn't actually return the fetch time as is mentioned in the API description
88  position_updated_at = dt_util.utcnow()
89 
90  dj_playlist = False
91  if (context := current.context) is not None:
92  dj_playlist = context.uri == SPOTIFY_DJ_PLAYLIST_URI
93  if not (
94  context.uri
95  in (
96  self._checked_playlist_id_checked_playlist_id,
97  SPOTIFY_DJ_PLAYLIST_URI,
98  )
99  or (self._playlist_playlist is None and context.uri == self._checked_playlist_id_checked_playlist_id)
100  ):
101  self._checked_playlist_id_checked_playlist_id = context.uri
102  self._playlist_playlist = None
103  if context.context_type == ContextType.PLAYLIST:
104  # Make sure any playlist lookups don't break the current
105  # playback state update
106  try:
107  self._playlist_playlist = await self.clientclient.get_playlist(context.uri)
108  except SpotifyNotFoundError:
109  _LOGGER.debug(
110  "Spotify playlist '%s' not found. "
111  "Most likely a Spotify-created playlist",
112  context.uri,
113  )
114  self._playlist_playlist = None
115  except SpotifyConnectionError:
116  _LOGGER.debug(
117  "Unable to load spotify playlist '%s'. "
118  "Continuing without playlist data",
119  context.uri,
120  )
121  self._playlist_playlist = None
122  self._checked_playlist_id_checked_playlist_id = None
123  return SpotifyCoordinatorData(
124  current_playback=current,
125  position_updated_at=position_updated_at,
126  playlist=self._playlist_playlist,
127  dj_playlist=dj_playlist,
128  )
None __init__(self, HomeAssistant hass, SpotifyClient client)
Definition: coordinator.py:56