Update pyheos to 0.9.0 (#134947)

Bump pyheos
This commit is contained in:
Andrew Sayre 2025-01-08 02:36:02 -06:00 committed by GitHub
parent dc1928f3eb
commit 3fea4efb9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 87 additions and 68 deletions

View File

@ -275,11 +275,11 @@ class GroupManager:
player_id_to_entity_id_map = self.entity_id_map player_id_to_entity_id_map = self.entity_id_map
for group in groups.values(): for group in groups.values():
leader_entity_id = player_id_to_entity_id_map.get(group.leader.player_id) leader_entity_id = player_id_to_entity_id_map.get(group.lead_player_id)
member_entity_ids = [ member_entity_ids = [
player_id_to_entity_id_map[member.player_id] player_id_to_entity_id_map[member]
for member in group.members for member in group.member_player_ids
if member.player_id in player_id_to_entity_id_map if member in player_id_to_entity_id_map
] ]
# Make sure the group leader is always the first element # Make sure the group leader is always the first element
group_info = [leader_entity_id, *member_entity_ids] group_info = [leader_entity_id, *member_entity_ids]
@ -422,7 +422,7 @@ class SourceManager:
None, None,
) )
if index is not None: if index is not None:
await player.play_favorite(index) await player.play_preset_station(index)
return return
input_source = next( input_source = next(
@ -434,7 +434,7 @@ class SourceManager:
None, None,
) )
if input_source is not None: if input_source is not None:
await player.play_input_source(input_source) await player.play_input_source(input_source.media_id)
return return
_LOGGER.error("Unknown source: %s", source) _LOGGER.error("Unknown source: %s", source)
@ -447,7 +447,7 @@ class SourceManager:
( (
input_source.name input_source.name
for input_source in self.inputs for input_source in self.inputs
if input_source.input_name == now_playing_media.media_id if input_source.media_id == now_playing_media.media_id
), ),
None, None,
) )

View File

@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/heos", "documentation": "https://www.home-assistant.io/integrations/heos",
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["pyheos"], "loggers": ["pyheos"],
"requirements": ["pyheos==0.8.0"], "requirements": ["pyheos==0.9.0"],
"single_config_entry": true, "single_config_entry": true,
"ssdp": [ "ssdp": [
{ {

View File

@ -47,9 +47,9 @@ BASE_SUPPORTED_FEATURES = (
) )
PLAY_STATE_TO_STATE = { PLAY_STATE_TO_STATE = {
heos_const.PLAY_STATE_PLAY: MediaPlayerState.PLAYING, heos_const.PlayState.PLAY: MediaPlayerState.PLAYING,
heos_const.PLAY_STATE_STOP: MediaPlayerState.IDLE, heos_const.PlayState.STOP: MediaPlayerState.IDLE,
heos_const.PLAY_STATE_PAUSE: MediaPlayerState.PAUSED, heos_const.PlayState.PAUSE: MediaPlayerState.PAUSED,
} }
CONTROL_TO_SUPPORT = { CONTROL_TO_SUPPORT = {
@ -61,11 +61,11 @@ CONTROL_TO_SUPPORT = {
} }
HA_HEOS_ENQUEUE_MAP = { HA_HEOS_ENQUEUE_MAP = {
None: heos_const.ADD_QUEUE_REPLACE_AND_PLAY, None: heos_const.AddCriteriaType.REPLACE_AND_PLAY,
MediaPlayerEnqueue.ADD: heos_const.ADD_QUEUE_ADD_TO_END, MediaPlayerEnqueue.ADD: heos_const.AddCriteriaType.ADD_TO_END,
MediaPlayerEnqueue.REPLACE: heos_const.ADD_QUEUE_REPLACE_AND_PLAY, MediaPlayerEnqueue.REPLACE: heos_const.AddCriteriaType.REPLACE_AND_PLAY,
MediaPlayerEnqueue.NEXT: heos_const.ADD_QUEUE_PLAY_NEXT, MediaPlayerEnqueue.NEXT: heos_const.AddCriteriaType.PLAY_NEXT,
MediaPlayerEnqueue.PLAY: heos_const.ADD_QUEUE_PLAY_NOW, MediaPlayerEnqueue.PLAY: heos_const.AddCriteriaType.PLAY_NOW,
} }
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -268,7 +268,7 @@ class HeosMediaPlayer(MediaPlayerEntity):
) )
if index is None: if index is None:
raise ValueError(f"Invalid favorite '{media_id}'") raise ValueError(f"Invalid favorite '{media_id}'")
await self._player.play_favorite(index) await self._player.play_preset_station(index)
return return
raise ValueError(f"Unsupported media type '{media_type}'") raise ValueError(f"Unsupported media type '{media_type}'")

View File

@ -1977,7 +1977,7 @@ pygti==0.9.4
pyhaversion==22.8.0 pyhaversion==22.8.0
# homeassistant.components.heos # homeassistant.components.heos
pyheos==0.8.0 pyheos==0.9.0
# homeassistant.components.hive # homeassistant.components.hive
pyhiveapi==0.5.16 pyhiveapi==0.5.16

View File

@ -1606,7 +1606,7 @@ pygti==0.9.4
pyhaversion==22.8.0 pyhaversion==22.8.0
# homeassistant.components.heos # homeassistant.components.heos
pyheos==0.8.0 pyheos==0.9.0
# homeassistant.components.hive # homeassistant.components.hive
pyhiveapi==0.5.16 pyhiveapi==0.5.16

View File

@ -5,15 +5,7 @@ from __future__ import annotations
from collections.abc import Sequence from collections.abc import Sequence
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from pyheos import ( from pyheos import Dispatcher, Heos, HeosGroup, HeosPlayer, MediaItem, const
Dispatcher,
Heos,
HeosGroup,
HeosPlayer,
HeosSource,
InputSource,
const,
)
import pytest import pytest
import pytest_asyncio import pytest_asyncio
@ -124,12 +116,13 @@ def player_fixture(quick_selects):
player.version = "1.0.0" player.version = "1.0.0"
player.is_muted = False player.is_muted = False
player.available = True player.available = True
player.state = const.PLAY_STATE_STOP player.state = const.PlayState.STOP
player.ip_address = f"127.0.0.{i}" player.ip_address = f"127.0.0.{i}"
player.network = "wired" player.network = "wired"
player.shuffle = False player.shuffle = False
player.repeat = const.REPEAT_OFF player.repeat = const.RepeatType.OFF
player.volume = 25 player.volume = 25
player.now_playing_media = Mock()
player.now_playing_media.supported_controls = const.CONTROLS_ALL player.now_playing_media.supported_controls = const.CONTROLS_ALL
player.now_playing_media.album_id = 1 player.now_playing_media.album_id = 1
player.now_playing_media.queue_id = 1 player.now_playing_media.queue_id = 1
@ -151,34 +144,52 @@ def player_fixture(quick_selects):
@pytest.fixture(name="group") @pytest.fixture(name="group")
def group_fixture(players): def group_fixture(players):
"""Create a HEOS group consisting of two players.""" """Create a HEOS group consisting of two players."""
group = Mock(HeosGroup) group = HeosGroup(
group.leader = players[1] name="Group", group_id=999, lead_player_id=1, member_player_ids=[2]
group.members = [players[2]] )
group.group_id = 999
return {group.group_id: group} return {group.group_id: group}
@pytest.fixture(name="favorites") @pytest.fixture(name="favorites")
def favorites_fixture() -> dict[int, HeosSource]: def favorites_fixture() -> dict[int, MediaItem]:
"""Create favorites fixture.""" """Create favorites fixture."""
station = Mock(HeosSource) station = MediaItem(
station.type = const.TYPE_STATION source_id=const.MUSIC_SOURCE_PANDORA,
station.name = "Today's Hits Radio" name="Today's Hits Radio",
station.media_id = "123456789" media_id="123456789",
radio = Mock(HeosSource) type=const.MediaType.STATION,
radio.type = const.TYPE_STATION playable=True,
radio.name = "Classical MPR (Classical Music)" browsable=False,
radio.media_id = "s1234" image_url="",
heos=None,
)
radio = MediaItem(
source_id=const.MUSIC_SOURCE_TUNEIN,
name="Classical MPR (Classical Music)",
media_id="s1234",
type=const.MediaType.STATION,
playable=True,
browsable=False,
image_url="",
heos=None,
)
return {1: station, 2: radio} return {1: station, 2: radio}
@pytest.fixture(name="input_sources") @pytest.fixture(name="input_sources")
def input_sources_fixture() -> Sequence[InputSource]: def input_sources_fixture() -> Sequence[MediaItem]:
"""Create a set of input sources for testing.""" """Create a set of input sources for testing."""
source = Mock(InputSource) source = MediaItem(
source.player_id = 1 source_id=1,
source.input_name = const.INPUT_AUX_IN_1 name="HEOS Drive - Line In 1",
source.name = "HEOS Drive - Line In 1" media_id=const.INPUT_AUX_IN_1,
type=const.MediaType.STATION,
playable=True,
browsable=False,
image_url="",
heos=None,
)
return [source] return [source]
@ -240,11 +251,17 @@ def quick_selects_fixture() -> dict[int, str]:
@pytest.fixture(name="playlists") @pytest.fixture(name="playlists")
def playlists_fixture() -> Sequence[HeosSource]: def playlists_fixture() -> Sequence[MediaItem]:
"""Create favorites fixture.""" """Create favorites fixture."""
playlist = Mock(HeosSource) playlist = MediaItem(
playlist.type = const.TYPE_PLAYLIST source_id=const.MUSIC_SOURCE_PLAYLISTS,
playlist.name = "Awesome Music" name="Awesome Music",
type=const.MediaType.PLAYLIST,
playable=True,
browsable=True,
image_url="",
heos=None,
)
return [playlist] return [playlist]

View File

@ -115,7 +115,7 @@ async def test_updates_from_signals(
player = controller.players[1] player = controller.players[1]
# Test player does not update for other players # Test player does not update for other players
player.state = const.PLAY_STATE_PLAY player.state = const.PlayState.PLAY
player.heos.dispatcher.send( player.heos.dispatcher.send(
const.SIGNAL_PLAYER_EVENT, 2, const.EVENT_PLAYER_STATE_CHANGED const.SIGNAL_PLAYER_EVENT, 2, const.EVENT_PLAYER_STATE_CHANGED
) )
@ -124,7 +124,7 @@ async def test_updates_from_signals(
assert state.state == STATE_IDLE assert state.state == STATE_IDLE
# Test player_update standard events # Test player_update standard events
player.state = const.PLAY_STATE_PLAY player.state = const.PlayState.PLAY
player.heos.dispatcher.send( player.heos.dispatcher.send(
const.SIGNAL_PLAYER_EVENT, player.player_id, const.EVENT_PLAYER_STATE_CHANGED const.SIGNAL_PLAYER_EVENT, player.player_id, const.EVENT_PLAYER_STATE_CHANGED
) )
@ -241,7 +241,7 @@ async def test_updates_from_players_changed(
async_dispatcher_connect(hass, SIGNAL_HEOS_UPDATED, set_signal) async_dispatcher_connect(hass, SIGNAL_HEOS_UPDATED, set_signal)
assert hass.states.get("media_player.test_player").state == STATE_IDLE assert hass.states.get("media_player.test_player").state == STATE_IDLE
player.state = const.PLAY_STATE_PLAY player.state = const.PlayState.PLAY
player.heos.dispatcher.send( player.heos.dispatcher.send(
const.SIGNAL_CONTROLLER_EVENT, const.EVENT_PLAYERS_CHANGED, change_data const.SIGNAL_CONTROLLER_EVENT, const.EVENT_PLAYERS_CHANGED, change_data
) )
@ -551,7 +551,7 @@ async def test_select_favorite(
{ATTR_ENTITY_ID: "media_player.test_player", ATTR_INPUT_SOURCE: favorite.name}, {ATTR_ENTITY_ID: "media_player.test_player", ATTR_INPUT_SOURCE: favorite.name},
blocking=True, blocking=True,
) )
player.play_favorite.assert_called_once_with(1) player.play_preset_station.assert_called_once_with(1)
# Test state is matched by station name # Test state is matched by station name
player.now_playing_media.station = favorite.name player.now_playing_media.station = favorite.name
player.heos.dispatcher.send( player.heos.dispatcher.send(
@ -576,7 +576,7 @@ async def test_select_radio_favorite(
{ATTR_ENTITY_ID: "media_player.test_player", ATTR_INPUT_SOURCE: favorite.name}, {ATTR_ENTITY_ID: "media_player.test_player", ATTR_INPUT_SOURCE: favorite.name},
blocking=True, blocking=True,
) )
player.play_favorite.assert_called_once_with(2) player.play_preset_station.assert_called_once_with(2)
# Test state is matched by album id # Test state is matched by album id
player.now_playing_media.station = "Classical" player.now_playing_media.station = "Classical"
player.now_playing_media.album_id = favorite.media_id player.now_playing_media.album_id = favorite.media_id
@ -601,14 +601,14 @@ async def test_select_radio_favorite_command_error(
player = controller.players[1] player = controller.players[1]
# Test set radio preset # Test set radio preset
favorite = favorites[2] favorite = favorites[2]
player.play_favorite.side_effect = CommandFailedError(None, "Failure", 1) player.play_preset_station.side_effect = CommandFailedError(None, "Failure", 1)
await hass.services.async_call( await hass.services.async_call(
MEDIA_PLAYER_DOMAIN, MEDIA_PLAYER_DOMAIN,
SERVICE_SELECT_SOURCE, SERVICE_SELECT_SOURCE,
{ATTR_ENTITY_ID: "media_player.test_player", ATTR_INPUT_SOURCE: favorite.name}, {ATTR_ENTITY_ID: "media_player.test_player", ATTR_INPUT_SOURCE: favorite.name},
blocking=True, blocking=True,
) )
player.play_favorite.assert_called_once_with(2) player.play_preset_station.assert_called_once_with(2)
assert "Unable to select source: Failure (1)" in caplog.text assert "Unable to select source: Failure (1)" in caplog.text
@ -629,7 +629,7 @@ async def test_select_input_source(
}, },
blocking=True, blocking=True,
) )
player.play_input_source.assert_called_once_with(input_source) player.play_input_source.assert_called_once_with(input_source.media_id)
# Test state is matched by media id # Test state is matched by media id
player.now_playing_media.source_id = const.MUSIC_SOURCE_AUX_INPUT player.now_playing_media.source_id = const.MUSIC_SOURCE_AUX_INPUT
player.now_playing_media.media_id = const.INPUT_AUX_IN_1 player.now_playing_media.media_id = const.INPUT_AUX_IN_1
@ -681,7 +681,7 @@ async def test_select_input_command_error(
}, },
blocking=True, blocking=True,
) )
player.play_input_source.assert_called_once_with(input_source) player.play_input_source.assert_called_once_with(input_source.media_id)
assert "Unable to select source: Failure (1)" in caplog.text assert "Unable to select source: Failure (1)" in caplog.text
@ -831,7 +831,7 @@ async def test_play_media_playlist(
blocking=True, blocking=True,
) )
player.add_to_queue.assert_called_once_with( player.add_to_queue.assert_called_once_with(
playlist, const.ADD_QUEUE_REPLACE_AND_PLAY playlist, const.AddCriteriaType.REPLACE_AND_PLAY
) )
# Play with enqueuing # Play with enqueuing
player.add_to_queue.reset_mock() player.add_to_queue.reset_mock()
@ -846,7 +846,9 @@ async def test_play_media_playlist(
}, },
blocking=True, blocking=True,
) )
player.add_to_queue.assert_called_once_with(playlist, const.ADD_QUEUE_ADD_TO_END) player.add_to_queue.assert_called_once_with(
playlist, const.AddCriteriaType.ADD_TO_END
)
# Invalid name # Invalid name
player.add_to_queue.reset_mock() player.add_to_queue.reset_mock()
await hass.services.async_call( await hass.services.async_call(
@ -888,9 +890,9 @@ async def test_play_media_favorite(
}, },
blocking=True, blocking=True,
) )
player.play_favorite.assert_called_once_with(index) player.play_preset_station.assert_called_once_with(index)
# Play by name # Play by name
player.play_favorite.reset_mock() player.play_preset_station.reset_mock()
await hass.services.async_call( await hass.services.async_call(
MEDIA_PLAYER_DOMAIN, MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA, SERVICE_PLAY_MEDIA,
@ -901,9 +903,9 @@ async def test_play_media_favorite(
}, },
blocking=True, blocking=True,
) )
player.play_favorite.assert_called_once_with(index) player.play_preset_station.assert_called_once_with(index)
# Invalid name # Invalid name
player.play_favorite.reset_mock() player.play_preset_station.reset_mock()
await hass.services.async_call( await hass.services.async_call(
MEDIA_PLAYER_DOMAIN, MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA, SERVICE_PLAY_MEDIA,
@ -914,7 +916,7 @@ async def test_play_media_favorite(
}, },
blocking=True, blocking=True,
) )
assert player.play_favorite.call_count == 0 assert player.play_preset_station.call_count == 0
assert "Unable to play media: Invalid favorite 'Invalid'" in caplog.text assert "Unable to play media: Invalid favorite 'Invalid'" in caplog.text