1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-25 05:26:47 +00:00

Migrate library xbox-webapi to python-xbox in Xbox integration (#155536)

This commit is contained in:
Manu
2025-11-03 13:51:40 +01:00
committed by GitHub
parent c72f2fd546
commit bcf2c4e9b6
21 changed files with 203 additions and 85 deletions

View File

@@ -1,8 +1,8 @@
"""API for xbox bound to Home Assistant OAuth."""
from xbox.webapi.authentication.manager import AuthenticationManager
from xbox.webapi.authentication.models import OAuth2TokenResponse
from xbox.webapi.common.signed_session import SignedSession
from pythonxbox.authentication.manager import AuthenticationManager
from pythonxbox.authentication.models import OAuth2TokenResponse
from pythonxbox.common.signed_session import SignedSession
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
from homeassistant.util.dt import utc_from_timestamp

View File

@@ -7,8 +7,8 @@ from dataclasses import dataclass
from enum import StrEnum
from typing import Any
from xbox.webapi.api.provider.people.models import Person
from xbox.webapi.api.provider.titlehub.models import Title
from pythonxbox.api.provider.people.models import Person
from pythonxbox.api.provider.titlehub.models import Title
from homeassistant.components.binary_sensor import (
DOMAIN as BINARY_SENSOR_DOMAIN,

View File

@@ -4,15 +4,15 @@ from __future__ import annotations
from typing import NamedTuple
from xbox.webapi.api.client import XboxLiveClient
from xbox.webapi.api.provider.catalog.const import HOME_APP_IDS, SYSTEM_PFN_ID_MAP
from xbox.webapi.api.provider.catalog.models import (
from pythonxbox.api.client import XboxLiveClient
from pythonxbox.api.provider.catalog.const import HOME_APP_IDS, SYSTEM_PFN_ID_MAP
from pythonxbox.api.provider.catalog.models import (
AlternateIdType,
CatalogResponse,
FieldsTemplate,
Image,
)
from xbox.webapi.api.provider.smartglass.models import (
from pythonxbox.api.provider.smartglass.models import (
InstalledPackage,
InstalledPackagesList,
)
@@ -157,7 +157,7 @@ async def build_item_response(
def item_payload(item: InstalledPackage, images: dict[str, list[Image]]):
"""Create response payload for a single media item."""
thumbnail = None
image = _find_media_image(images.get(item.one_store_product_id, []))
image = _find_media_image(images.get(item.one_store_product_id, [])) # type: ignore[arg-type]
if image is not None:
thumbnail = image.uri
if thumbnail[0] == "/":
@@ -165,9 +165,9 @@ def item_payload(item: InstalledPackage, images: dict[str, list[Image]]):
return BrowseMedia(
media_class=TYPE_MAP[item.content_type].cls,
media_content_id=item.one_store_product_id,
media_content_id=item.one_store_product_id, # type: ignore[arg-type]
media_content_type=TYPE_MAP[item.content_type].type,
title=item.name,
title=item.name, # type: ignore[arg-type]
can_play=True,
can_expand=False,
thumbnail=thumbnail,

View File

@@ -3,10 +3,10 @@
import logging
from typing import Any
from xbox.webapi.api.client import XboxLiveClient
from xbox.webapi.authentication.manager import AuthenticationManager
from xbox.webapi.authentication.models import OAuth2TokenResponse
from xbox.webapi.common.signed_session import SignedSession
from pythonxbox.api.client import XboxLiveClient
from pythonxbox.authentication.manager import AuthenticationManager
from pythonxbox.authentication.models import OAuth2TokenResponse
from pythonxbox.common.signed_session import SignedSession
from homeassistant.config_entries import ConfigFlowResult
from homeassistant.helpers import config_entry_oauth2_flow
@@ -54,7 +54,7 @@ class OAuth2FlowHandler(
client = XboxLiveClient(auth)
me = await client.people.get_friends_own_batch([client.xuid])
me = await client.people.get_friends_by_xuid(client.xuid)
await self.async_set_unique_id(client.xuid)
return self.async_create_entry(title=me.people[0].gamertag, data=data)

View File

@@ -8,22 +8,23 @@ from http import HTTPStatus
import logging
from httpx import HTTPStatusError, RequestError, TimeoutException
from xbox.webapi.api.client import XboxLiveClient
from xbox.webapi.api.provider.catalog.const import SYSTEM_PFN_ID_MAP
from xbox.webapi.api.provider.catalog.models import AlternateIdType, Product
from xbox.webapi.api.provider.people.models import Person
from xbox.webapi.api.provider.smartglass.models import (
from pythonxbox.api.client import XboxLiveClient
from pythonxbox.api.provider.catalog.const import SYSTEM_PFN_ID_MAP
from pythonxbox.api.provider.catalog.models import AlternateIdType, Product
from pythonxbox.api.provider.people.models import Person
from pythonxbox.api.provider.smartglass.models import (
SmartglassConsoleList,
SmartglassConsoleStatus,
)
from xbox.webapi.api.provider.titlehub.models import Title
from xbox.webapi.common.signed_session import SignedSession
from pythonxbox.api.provider.titlehub.models import Title
from pythonxbox.common.signed_session import SignedSession
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_entry_oauth2_flow, device_registry as dr
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.ssl import get_default_context
from . import api
from .const import DOMAIN
@@ -90,7 +91,7 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
session = config_entry_oauth2_flow.OAuth2Session(
self.hass, self.config_entry, implementation
)
signed_session = await self.hass.async_add_executor_job(SignedSession)
signed_session = SignedSession(ssl_context=get_default_context())
auth = api.AsyncConfigEntryAuth(signed_session, session)
self.client = XboxLiveClient(auth)
@@ -183,7 +184,7 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
# Update user presence
try:
batch = await self.client.people.get_friends_own_batch([self.client.xuid])
batch = await self.client.people.get_friends_by_xuid(self.client.xuid)
friends = await self.client.people.get_friends_own()
except TimeoutException as e:
raise UpdateFailed(

View File

@@ -6,9 +6,9 @@ from collections.abc import Callable, Mapping
from dataclasses import dataclass
from typing import Any
from xbox.webapi.api.provider.people.models import Person
from xbox.webapi.api.provider.smartglass.models import ConsoleType, SmartglassConsole
from xbox.webapi.api.provider.titlehub.models import Title
from pythonxbox.api.provider.people.models import Person
from pythonxbox.api.provider.smartglass.models import ConsoleType, SmartglassConsole
from pythonxbox.api.provider.titlehub.models import Title
from yarl import URL
from homeassistant.core import HomeAssistant

View File

@@ -6,8 +6,8 @@ from collections.abc import Callable
from dataclasses import dataclass
from enum import StrEnum
from xbox.webapi.api.provider.people.models import Person
from xbox.webapi.api.provider.titlehub.models import Title
from pythonxbox.api.provider.people.models import Person
from pythonxbox.api.provider.titlehub.models import Title
from homeassistant.components.image import ImageEntity, ImageEntityDescription
from homeassistant.core import HomeAssistant, callback

View File

@@ -11,7 +11,7 @@
],
"documentation": "https://www.home-assistant.io/integrations/xbox",
"iot_class": "cloud_polling",
"requirements": ["xbox-webapi==2.1.0"],
"requirements": ["python-xbox==0.1.0"],
"ssdp": [
{
"manufacturer": "Microsoft Corporation",

View File

@@ -4,8 +4,8 @@ from __future__ import annotations
from typing import Any
from xbox.webapi.api.provider.catalog.models import Image
from xbox.webapi.api.provider.smartglass.models import (
from pythonxbox.api.provider.catalog.models import Image
from pythonxbox.api.provider.smartglass.models import (
PlaybackState,
PowerState,
VolumeDirection,

View File

@@ -6,11 +6,11 @@ from contextlib import suppress
from dataclasses import dataclass
from pydantic import ValidationError
from xbox.webapi.api.client import XboxLiveClient
from xbox.webapi.api.provider.catalog.models import FieldsTemplate, Image
from xbox.webapi.api.provider.gameclips.models import GameclipsResponse
from xbox.webapi.api.provider.screenshots.models import ScreenshotResponse
from xbox.webapi.api.provider.smartglass.models import InstalledPackage
from pythonxbox.api.client import XboxLiveClient
from pythonxbox.api.provider.catalog.models import FieldsTemplate, Image
from pythonxbox.api.provider.gameclips.models import GameclipsResponse
from pythonxbox.api.provider.screenshots.models import ScreenshotResponse
from pythonxbox.api.provider.smartglass.models import InstalledPackage
from homeassistant.components.media_player import MediaClass
from homeassistant.components.media_source import (
@@ -149,9 +149,9 @@ class XboxSource(MediaSource):
items = [
XboxMediaItem(
item.user_caption
or dt_util.as_local(
dt_util.parse_datetime(item.date_recorded)
).strftime("%b. %d, %Y %I:%M %p"),
or dt_util.as_local(item.date_recorded).strftime(
"%b. %d, %Y %I:%M %p"
),
item.thumbnails[0].uri,
item.game_clip_uris[0].uri,
MediaClass.VIDEO,
@@ -201,7 +201,7 @@ class XboxSource(MediaSource):
def _build_game_item(item: InstalledPackage, images: dict[str, list[Image]]):
"""Build individual game."""
thumbnail = ""
image = _find_media_image(images.get(item.one_store_product_id, []))
image = _find_media_image(images.get(item.one_store_product_id, [])) # type: ignore[arg-type]
if image is not None:
thumbnail = image.uri
if thumbnail[0] == "/":

View File

@@ -6,7 +6,7 @@ import asyncio
from collections.abc import Iterable
from typing import Any
from xbox.webapi.api.provider.smartglass.models import InputKeyType, PowerState
from pythonxbox.api.provider.smartglass.models import InputKeyType, PowerState
from homeassistant.components.remote import (
ATTR_DELAY_SECS,

View File

@@ -8,8 +8,8 @@ from datetime import UTC, datetime
from enum import StrEnum
from typing import Any
from xbox.webapi.api.provider.people.models import Person
from xbox.webapi.api.provider.titlehub.models import Title
from pythonxbox.api.provider.people.models import Person
from pythonxbox.api.provider.titlehub.models import Title
from homeassistant.components.sensor import (
DOMAIN as SENSOR_DOMAIN,

6
requirements_all.txt generated
View File

@@ -2557,6 +2557,9 @@ python-telegram-bot[socks]==22.1
# homeassistant.components.vlc
python-vlc==3.0.18122
# homeassistant.components.xbox
python-xbox==0.1.0
# homeassistant.components.egardia
pythonegardia==1.0.52
@@ -3154,9 +3157,6 @@ wsdot==0.0.1
# homeassistant.components.wyoming
wyoming==1.7.2
# homeassistant.components.xbox
xbox-webapi==2.1.0
# homeassistant.components.xiaomi_ble
xiaomi-ble==1.2.0

View File

@@ -2120,6 +2120,9 @@ python-technove==2.0.0
# homeassistant.components.telegram_bot
python-telegram-bot[socks]==22.1
# homeassistant.components.xbox
python-xbox==0.1.0
# homeassistant.components.uptime_kuma
pythonkuma==0.3.1
@@ -2606,9 +2609,6 @@ wsdot==0.0.1
# homeassistant.components.wyoming
wyoming==1.7.2
# homeassistant.components.xbox
xbox-webapi==2.1.0
# homeassistant.components.xiaomi_ble
xiaomi-ble==1.2.0

View File

@@ -212,7 +212,6 @@ TODO = {
"0.12.3"
), # https://github.com/aio-libs/aiocache/blob/master/LICENSE all rights reserved?
"caldav": AwesomeVersion("1.6.0"), # None -- GPL -- ['GNU General Public License (GPL)', 'Apache Software License'] # https://github.com/python-caldav/caldav
"xbox-webapi": AwesomeVersion("2.1.0"), # None -- GPL -- ['MIT License']
}
# fmt: on

View File

@@ -5,13 +5,13 @@ from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
from xbox.webapi.api.provider.catalog.models import CatalogResponse
from xbox.webapi.api.provider.people.models import PeopleResponse
from xbox.webapi.api.provider.smartglass.models import (
from pythonxbox.api.provider.catalog.models import CatalogResponse
from pythonxbox.api.provider.people.models import PeopleResponse
from pythonxbox.api.provider.smartglass.models import (
SmartglassConsoleList,
SmartglassConsoleStatus,
)
from xbox.webapi.api.provider.titlehub.models import TitleHubResponse
from pythonxbox.api.provider.titlehub.models import TitleHubResponse
from homeassistant.components.application_credentials import (
ClientCredential,
@@ -130,7 +130,7 @@ def mock_xbox_live_client(signed_session) -> Generator[AsyncMock]:
)
client.people = AsyncMock()
client.people.get_friends_own_batch.return_value = PeopleResponse(
client.people.get_friends_by_xuid.return_value = PeopleResponse(
**load_json_object_fixture("people_batch.json", DOMAIN)
)
client.people.get_friends_own.return_value = PeopleResponse(

View File

@@ -20,6 +20,9 @@
"presenceState": "Online",
"presenceText": "Last seen 49m ago: Xbox App",
"presenceDevices": null,
"isFriend": true,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isBroadcasting": false,
"isCloaked": false,
"isQuarantined": false,
@@ -30,8 +33,8 @@
"search": null,
"titleHistory": null,
"multiplayerSummary": {
"InMultiplayerSession": 0,
"InParty": 0
"inMultiplayerSession": 0,
"inParty": 0
},
"recentPlayer": null,
"follower": null,
@@ -67,7 +70,17 @@
"mute": false,
"followerCount": 105,
"followingCount": 121,
"hasGamePass": true
"hasGamePass": true,
"isFriend": true,
"canBeFriended": true,
"canBeFollowed": true,
"friendCount": 150,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isFriendListShared": true,
"isFollowingCaller": false,
"isFollowedByCaller": false,
"isFavorite": true
},
"communityManagerTitles": null,
"socialManager": null,
@@ -99,6 +112,9 @@
"presenceState": "Online",
"presenceText": "Online",
"presenceDevices": null,
"isFriend": true,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isBroadcasting": false,
"isCloaked": null,
"isQuarantined": false,
@@ -109,8 +125,8 @@
"search": null,
"titleHistory": null,
"multiplayerSummary": {
"InMultiplayerSession": 0,
"InParty": 0
"inMultiplayerSession": 0,
"inParty": 0
},
"recentPlayer": null,
"follower": null,
@@ -146,7 +162,17 @@
"mute": false,
"followerCount": 78,
"followingCount": 83,
"hasGamePass": false
"hasGamePass": false,
"isFriend": true,
"canBeFriended": true,
"canBeFollowed": true,
"friendCount": 150,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isFriendListShared": true,
"isFollowingCaller": false,
"isFollowedByCaller": false,
"isFavorite": true
},
"communityManagerTitles": null,
"socialManager": null,
@@ -178,6 +204,9 @@
"presenceState": "Online",
"presenceText": "Amazon Instant Video",
"presenceDevices": null,
"isFriend": true,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isBroadcasting": false,
"isCloaked": null,
"isQuarantined": false,
@@ -188,8 +217,8 @@
"search": null,
"titleHistory": null,
"multiplayerSummary": {
"InMultiplayerSession": 0,
"InParty": 0
"inMultiplayerSession": 0,
"inParty": 0
},
"recentPlayer": null,
"follower": null,
@@ -236,7 +265,17 @@
"mute": false,
"followerCount": 27659,
"followingCount": 0,
"hasGamePass": false
"hasGamePass": false,
"isFriend": true,
"canBeFriended": true,
"canBeFollowed": true,
"friendCount": 150,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isFriendListShared": true,
"isFollowingCaller": false,
"isFollowedByCaller": false,
"isFavorite": true
},
"communityManagerTitles": null,
"socialManager": null,

View File

@@ -20,6 +20,9 @@
"presenceState": "Online",
"presenceText": "Last seen 49m ago: Xbox App",
"presenceDevices": null,
"isFriend": true,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isBroadcasting": false,
"isCloaked": false,
"isQuarantined": false,
@@ -30,8 +33,8 @@
"search": null,
"titleHistory": null,
"multiplayerSummary": {
"InMultiplayerSession": 0,
"InParty": 0
"inMultiplayerSession": 0,
"inParty": 0
},
"recentPlayer": null,
"follower": null,
@@ -67,7 +70,17 @@
"mute": false,
"followerCount": 105,
"followingCount": 121,
"hasGamePass": true
"hasGamePass": true,
"isFriend": true,
"canBeFriended": true,
"canBeFollowed": true,
"friendCount": 150,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isFriendListShared": true,
"isFollowingCaller": false,
"isFollowedByCaller": false,
"isFavorite": true
},
"communityManagerTitles": null,
"socialManager": null,
@@ -99,6 +112,9 @@
"presenceState": "Online",
"presenceText": "Online",
"presenceDevices": null,
"isFriend": true,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isBroadcasting": false,
"isCloaked": null,
"isQuarantined": false,
@@ -109,8 +125,8 @@
"search": null,
"titleHistory": null,
"multiplayerSummary": {
"InMultiplayerSession": 0,
"InParty": 0
"inMultiplayerSession": 0,
"inParty": 0
},
"recentPlayer": null,
"follower": null,
@@ -146,7 +162,17 @@
"mute": false,
"followerCount": 78,
"followingCount": 83,
"hasGamePass": false
"hasGamePass": false,
"isFriend": true,
"canBeFriended": true,
"canBeFollowed": true,
"friendCount": 150,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isFriendListShared": true,
"isFollowingCaller": false,
"isFollowedByCaller": false,
"isFavorite": true
},
"communityManagerTitles": null,
"socialManager": null,
@@ -178,6 +204,9 @@
"presenceState": "Online",
"presenceText": "Amazon Instant Video",
"presenceDevices": null,
"isFriend": true,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isBroadcasting": false,
"isCloaked": null,
"isQuarantined": false,
@@ -188,8 +217,8 @@
"search": null,
"titleHistory": null,
"multiplayerSummary": {
"InMultiplayerSession": 0,
"InParty": 0
"inMultiplayerSession": 0,
"inParty": 0
},
"recentPlayer": null,
"follower": null,
@@ -236,7 +265,17 @@
"mute": false,
"followerCount": 27659,
"followingCount": 0,
"hasGamePass": false
"hasGamePass": false,
"isFriend": true,
"canBeFriended": true,
"canBeFollowed": true,
"friendCount": 150,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isFriendListShared": true,
"isFollowingCaller": false,
"isFollowedByCaller": false,
"isFavorite": true
},
"communityManagerTitles": null,
"socialManager": null,

View File

@@ -18,6 +18,9 @@
"uniqueModernGamertag": "Ikken Hissatsuu",
"xboxOneRep": "GoodPlayer",
"presenceState": "Offline",
"isFriend": true,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"presenceText": "Offline",
"presenceDevices": null,
"isBroadcasting": false,
@@ -35,8 +38,22 @@
"search": null,
"titleHistory": null,
"multiplayerSummary": {
"InMultiplayerSession": 0,
"InParty": 0
"inMultiplayerSession": 0,
"inParty": 0,
"joinableActivities": [],
"partyDetails": [
{
"sessionRef": {
"scid": "7492baca-c1b4-440d-a391-b7ef364a8d40",
"templateName": "chat",
"name": "51935651-e782-45c6-854a-7a3858fc103a"
},
"status": "active",
"visibility": "open",
"joinRestriction": "followed",
"accepted": 1
}
]
},
"recentPlayer": {
"titles": [],
@@ -71,7 +88,17 @@
"mute": false,
"followerCount": 81,
"followingCount": 73,
"hasGamePass": false
"hasGamePass": false,
"isFriend": true,
"canBeFriended": true,
"canBeFollowed": true,
"friendCount": 150,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isFriendListShared": true,
"isFollowingCaller": false,
"isFollowedByCaller": false,
"isFavorite": true
},
"communityManagerTitles": null,
"socialManager": {
@@ -109,6 +136,9 @@
"presenceState": "Offline",
"presenceText": "Last seen 17h ago: Home",
"presenceDevices": null,
"isFriend": true,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isBroadcasting": false,
"isCloaked": null,
"isQuarantined": false,
@@ -124,8 +154,8 @@
"search": null,
"titleHistory": null,
"multiplayerSummary": {
"InMultiplayerSession": 0,
"InParty": 0
"inMultiplayerSession": 0,
"inParty": 0
},
"recentPlayer": {
"titles": [],
@@ -172,7 +202,17 @@
"mute": false,
"followerCount": 18,
"followingCount": 12,
"hasGamePass": false
"hasGamePass": false,
"isFriend": true,
"canBeFriended": true,
"canBeFollowed": true,
"friendCount": 150,
"isFriendRequestReceived": false,
"isFriendRequestSent": false,
"isFriendListShared": true,
"isFollowingCaller": false,
"isFollowedByCaller": false,
"isFavorite": true
},
"communityManagerTitles": null,
"socialManager": {

View File

@@ -7,9 +7,9 @@ from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from pythonxbox.api.provider.people.models import PeopleResponse
import respx
from syrupy.assertion import SnapshotAssertion
from xbox.webapi.api.provider.people.models import PeopleResponse
from homeassistant.components.xbox.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
@@ -104,7 +104,7 @@ async def test_load_image_from_url(
assert resp.content_type == "image/png"
assert resp.content_length == 4
xbox_live_client.people.get_friends_own_batch.return_value = PeopleResponse(
xbox_live_client.people.get_friends_by_xuid.return_value = PeopleResponse(
**await async_load_json_object_fixture(
hass, "people_batch gamerpic.json", DOMAIN
) # pyright: ignore[reportArgumentType]

View File

@@ -72,7 +72,7 @@ async def test_config_implementation_not_available(
[
("smartglass", "get_console_status"),
("catalog", "get_product_from_alternate_id"),
("people", "get_friends_own_batch"),
("people", "get_friends_by_xuid"),
("people", "get_friends_own"),
],
)