Use runtime_data instead of hass.data for Jellyfin (#122410)

* Use runtime_data instead of hass.data

* Process review
pull/122693/head
Jan Stienstra 2024-07-24 08:53:01 +02:00 committed by GitHub
parent 6dd43be6ac
commit 99aa68c93f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 29 additions and 43 deletions

View File

@ -12,11 +12,11 @@ from .const import CONF_CLIENT_DEVICE_ID, DOMAIN, PLATFORMS
from .coordinator import JellyfinDataUpdateCoordinator, SessionsDataUpdateCoordinator from .coordinator import JellyfinDataUpdateCoordinator, SessionsDataUpdateCoordinator
from .models import JellyfinData from .models import JellyfinData
type JellyfinConfigEntry = ConfigEntry[JellyfinData]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: JellyfinConfigEntry) -> bool:
"""Set up Jellyfin from a config entry.""" """Set up Jellyfin from a config entry."""
hass.data.setdefault(DOMAIN, {})
if CONF_CLIENT_DEVICE_ID not in entry.data: if CONF_CLIENT_DEVICE_ID not in entry.data:
entry_data = entry.data.copy() entry_data = entry.data.copy()
entry_data[CONF_CLIENT_DEVICE_ID] = entry.entry_id entry_data[CONF_CLIENT_DEVICE_ID] = entry.entry_id
@ -45,7 +45,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
for coordinator in coordinators.values(): for coordinator in coordinators.values():
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id] = JellyfinData( entry.runtime_data = JellyfinData(
client_device_id=entry.data[CONF_CLIENT_DEVICE_ID], client_device_id=entry.data[CONF_CLIENT_DEVICE_ID],
jellyfin_client=client, jellyfin_client=client,
coordinators=coordinators, coordinators=coordinators,
@ -56,21 +56,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: JellyfinConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unloaded = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) unloaded = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unloaded: if unloaded:
data = hass.data[DOMAIN].pop(entry.entry_id) entry.runtime_data.jellyfin_client.stop()
data.jellyfin_client.stop()
return unloaded return unloaded
async def async_remove_config_entry_device( async def async_remove_config_entry_device(
hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry hass: HomeAssistant, config_entry: JellyfinConfigEntry, device_entry: dr.DeviceEntry
) -> bool: ) -> bool:
"""Remove device from a config entry.""" """Remove device from a config entry."""
data = hass.data[DOMAIN][config_entry.entry_id] data = config_entry.runtime_data
coordinator = data.coordinators["sessions"] coordinator = data.coordinators["sessions"]
return not device_entry.identifiers.intersection( return not device_entry.identifiers.intersection(

View File

@ -9,15 +9,15 @@ from typing import Any
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ( from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow, ConfigFlow,
ConfigFlowResult, ConfigFlowResult,
OptionsFlow, OptionsFlowWithConfigEntry,
) )
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.util.uuid import random_uuid_hex from homeassistant.util.uuid import random_uuid_hex
from . import JellyfinConfigEntry
from .client_wrapper import CannotConnect, InvalidAuth, create_client, validate_input from .client_wrapper import CannotConnect, InvalidAuth, create_client, validate_input
from .const import CONF_CLIENT_DEVICE_ID, DOMAIN, SUPPORTED_AUDIO_CODECS from .const import CONF_CLIENT_DEVICE_ID, DOMAIN, SUPPORTED_AUDIO_CODECS
@ -56,7 +56,7 @@ class JellyfinConfigFlow(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the Jellyfin config flow.""" """Initialize the Jellyfin config flow."""
self.client_device_id: str | None = None self.client_device_id: str | None = None
self.entry: ConfigEntry | None = None self.entry: JellyfinConfigEntry | None = None
async def async_step_user( async def async_step_user(
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
@ -146,18 +146,16 @@ class JellyfinConfigFlow(ConfigFlow, domain=DOMAIN):
@staticmethod @staticmethod
@callback @callback
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: def async_get_options_flow(
config_entry: JellyfinConfigEntry,
) -> OptionsFlowWithConfigEntry:
"""Create the options flow.""" """Create the options flow."""
return OptionsFlowHandler(config_entry) return OptionsFlowHandler(config_entry)
class OptionsFlowHandler(OptionsFlow): class OptionsFlowHandler(OptionsFlowWithConfigEntry):
"""Handle an option flow for jellyfin.""" """Handle an option flow for jellyfin."""
def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry
async def async_step_init( async def async_step_init(
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:

View File

@ -8,7 +8,6 @@ from typing import Any, TypeVar
from jellyfin_apiclient_python import JellyfinClient from jellyfin_apiclient_python import JellyfinClient
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
@ -23,8 +22,6 @@ JellyfinDataT = TypeVar(
class JellyfinDataUpdateCoordinator(DataUpdateCoordinator[JellyfinDataT], ABC): class JellyfinDataUpdateCoordinator(DataUpdateCoordinator[JellyfinDataT], ABC):
"""Data update coordinator for the Jellyfin integration.""" """Data update coordinator for the Jellyfin integration."""
config_entry: ConfigEntry
def __init__( def __init__(
self, self,
hass: HomeAssistant, hass: HomeAssistant,

View File

@ -5,21 +5,19 @@ from __future__ import annotations
from typing import Any from typing import Any
from homeassistant.components.diagnostics import async_redact_data from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD from homeassistant.const import CONF_PASSWORD
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .const import DOMAIN from . import JellyfinConfigEntry
from .models import JellyfinData
TO_REDACT = {CONF_PASSWORD} TO_REDACT = {CONF_PASSWORD}
async def async_get_config_entry_diagnostics( async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry hass: HomeAssistant, entry: JellyfinConfigEntry
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Return diagnostics for a config entry.""" """Return diagnostics for a config entry."""
data: JellyfinData = hass.data[DOMAIN][entry.entry_id] data = entry.runtime_data
sessions = data.coordinators["sessions"] sessions = data.coordinators["sessions"]
return { return {

View File

@ -12,27 +12,26 @@ from homeassistant.components.media_player import (
MediaType, MediaType,
) )
from homeassistant.components.media_player.browse_media import BrowseMedia from homeassistant.components.media_player.browse_media import BrowseMedia
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.dt import parse_datetime from homeassistant.util.dt import parse_datetime
from . import JellyfinConfigEntry
from .browse_media import build_item_response, build_root_response from .browse_media import build_item_response, build_root_response
from .client_wrapper import get_artwork_url from .client_wrapper import get_artwork_url
from .const import CONTENT_TYPE_MAP, DOMAIN, LOGGER from .const import CONTENT_TYPE_MAP, DOMAIN, LOGGER
from .coordinator import JellyfinDataUpdateCoordinator from .coordinator import JellyfinDataUpdateCoordinator
from .entity import JellyfinEntity from .entity import JellyfinEntity
from .models import JellyfinData
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: JellyfinConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Jellyfin media_player from a config entry.""" """Set up Jellyfin media_player from a config entry."""
jellyfin_data: JellyfinData = hass.data[DOMAIN][entry.entry_id] jellyfin_data = entry.runtime_data
coordinator = jellyfin_data.coordinators["sessions"] coordinator = jellyfin_data.coordinators["sessions"]
@callback @callback

View File

@ -17,9 +17,9 @@ from homeassistant.components.media_source.models import (
MediaSourceItem, MediaSourceItem,
PlayMedia, PlayMedia,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from . import JellyfinConfigEntry
from .const import ( from .const import (
COLLECTION_TYPE_MOVIES, COLLECTION_TYPE_MOVIES,
COLLECTION_TYPE_MUSIC, COLLECTION_TYPE_MUSIC,
@ -48,7 +48,6 @@ from .const import (
PLAYABLE_ITEM_TYPES, PLAYABLE_ITEM_TYPES,
SUPPORTED_COLLECTION_TYPES, SUPPORTED_COLLECTION_TYPES,
) )
from .models import JellyfinData
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -56,8 +55,8 @@ _LOGGER = logging.getLogger(__name__)
async def async_get_media_source(hass: HomeAssistant) -> MediaSource: async def async_get_media_source(hass: HomeAssistant) -> MediaSource:
"""Set up Jellyfin media source.""" """Set up Jellyfin media source."""
# Currently only a single Jellyfin server is supported # Currently only a single Jellyfin server is supported
entry = hass.config_entries.async_entries(DOMAIN)[0] entry: JellyfinConfigEntry = hass.config_entries.async_entries(DOMAIN)[0]
jellyfin_data: JellyfinData = hass.data[DOMAIN][entry.entry_id] jellyfin_data = entry.runtime_data
return JellyfinSource(hass, jellyfin_data.jellyfin_client, entry) return JellyfinSource(hass, jellyfin_data.jellyfin_client, entry)
@ -68,7 +67,7 @@ class JellyfinSource(MediaSource):
name: str = "Jellyfin" name: str = "Jellyfin"
def __init__( def __init__(
self, hass: HomeAssistant, client: JellyfinClient, entry: ConfigEntry self, hass: HomeAssistant, client: JellyfinClient, entry: JellyfinConfigEntry
) -> None: ) -> None:
"""Initialize the Jellyfin media source.""" """Initialize the Jellyfin media source."""
super().__init__(DOMAIN) super().__init__(DOMAIN)

View File

@ -6,15 +6,13 @@ from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from .const import DOMAIN from . import JellyfinConfigEntry
from .coordinator import JellyfinDataT from .coordinator import JellyfinDataT
from .entity import JellyfinEntity from .entity import JellyfinEntity
from .models import JellyfinData
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
@ -46,14 +44,14 @@ SENSOR_TYPES: dict[str, JellyfinSensorEntityDescription] = {
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: JellyfinConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Jellyfin sensor based on a config entry.""" """Set up Jellyfin sensor based on a config entry."""
jellyfin_data: JellyfinData = hass.data[DOMAIN][entry.entry_id] data = entry.runtime_data
async_add_entities( async_add_entities(
JellyfinSensor(jellyfin_data.coordinators[coordinator_type], description) JellyfinSensor(data.coordinators[coordinator_type], description)
for coordinator_type, description in SENSOR_TYPES.items() for coordinator_type, description in SENSOR_TYPES.items()
) )

View File

@ -68,12 +68,10 @@ async def test_load_unload_config_entry(
await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_config_entry.entry_id in hass.data[DOMAIN]
assert mock_config_entry.state is ConfigEntryState.LOADED assert mock_config_entry.state is ConfigEntryState.LOADED
await hass.config_entries.async_unload(mock_config_entry.entry_id) await hass.config_entries.async_unload(mock_config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_config_entry.entry_id not in hass.data[DOMAIN]
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED assert mock_config_entry.state is ConfigEntryState.NOT_LOADED