From 5cd73170de404cd1eff9b0dfa11152a7da64a332 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 13 Jan 2022 10:36:44 +0100 Subject: [PATCH] Add type hints to media_player (part 1) (#64005) * Add type hints to media_player (part 1) * Fix roku to match --- .../components/media_player/__init__.py | 29 +++++++++++-------- homeassistant/components/roku/media_player.py | 2 +- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 2992d86f44b..84eb2cb7493 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -4,6 +4,7 @@ from __future__ import annotations import asyncio import base64 import collections +from collections.abc import Callable from contextlib import suppress from dataclasses import dataclass import datetime as dt @@ -12,7 +13,7 @@ import hashlib from http import HTTPStatus import logging import secrets -from typing import final +from typing import Any, cast, final from urllib.parse import urlparse from aiohttp import web @@ -63,6 +64,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401 from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.network import get_url +from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass from .const import ( @@ -208,7 +210,7 @@ def is_on(hass, entity_id=None): ) -def _rename_keys(**keys): +def _rename_keys(**keys: Any) -> Callable[[dict[str, Any]], dict[str, Any]]: """Create validator that renames keys. Necessary because the service schema names do not match the command parameters. @@ -216,7 +218,7 @@ def _rename_keys(**keys): Async friendly. """ - def rename(value): + def rename(value: dict[str, Any]) -> dict[str, Any]: for to_key, from_key in keys.items(): if from_key in value: value[to_key] = value.pop(from_key) @@ -225,7 +227,7 @@ def _rename_keys(**keys): return rename -async def async_setup(hass, config): +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Track states and offer events for media_players.""" component = hass.data[DOMAIN] = EntityComponent( logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL @@ -508,7 +510,7 @@ class MediaPlayerEntity(Entity): return None - async def async_get_media_image(self): + async def async_get_media_image(self) -> tuple[bytes | None, str | None]: """Fetch media image of current playing image.""" if (url := self.media_image_url) is None: return None, None @@ -520,7 +522,7 @@ class MediaPlayerEntity(Entity): media_content_type: str, media_content_id: str, media_image_id: str | None = None, - ) -> tuple[str | None, str | None]: + ) -> tuple[bytes | None, str | None]: """ Optionally fetch internally accessible image for media browser. @@ -965,13 +967,15 @@ class MediaPlayerEntity(Entity): """Remove this player from any group.""" await self.hass.async_add_executor_job(self.unjoin_player) - async def _async_fetch_image_from_cache(self, url): + async def _async_fetch_image_from_cache( + self, url: str + ) -> tuple[bytes | None, str | None]: """Fetch image. Images are cached in memory (the images are typically 10-100kB in size). """ - cache_images = ENTITY_IMAGE_CACHE[CACHE_IMAGES] - cache_maxsize = ENTITY_IMAGE_CACHE[CACHE_MAXSIZE] + cache_images = cast(collections.OrderedDict, ENTITY_IMAGE_CACHE[CACHE_IMAGES]) + cache_maxsize = cast(int, ENTITY_IMAGE_CACHE[CACHE_MAXSIZE]) if urlparse(url).hostname is None: url = f"{get_url(self.hass)}{url}" @@ -981,7 +985,7 @@ class MediaPlayerEntity(Entity): async with cache_images[url][CACHE_LOCK]: if CACHE_CONTENT in cache_images[url]: - return cache_images[url][CACHE_CONTENT] + return cache_images[url][CACHE_CONTENT] # type:ignore[no-any-return] (content, content_type) = await self._async_fetch_image(url) @@ -992,7 +996,7 @@ class MediaPlayerEntity(Entity): return content, content_type - async def _async_fetch_image(self, url): + async def _async_fetch_image(self, url: str) -> tuple[bytes | None, str | None]: """Retrieve an image.""" content, content_type = (None, None) websession = async_get_clientsession(self.hass) @@ -1037,7 +1041,7 @@ class MediaPlayerImageView(HomeAssistantView): url + "/browse_media/{media_content_type}/{media_content_id}", ] - def __init__(self, component): + def __init__(self, component: EntityComponent) -> None: """Initialize a media player view.""" self.component = component @@ -1057,6 +1061,7 @@ class MediaPlayerImageView(HomeAssistantView): ) return web.Response(status=status) + assert isinstance(player, MediaPlayerEntity) authenticated = ( request[KEY_AUTHENTICATED] or request.query.get("token") == player.access_token diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 10483d980f9..570d5456d5d 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -257,7 +257,7 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): media_content_type: str, media_content_id: str, media_image_id: str | None = None, - ) -> tuple[str | None, str | None]: + ) -> tuple[bytes | None, str | None]: """Fetch media browser image to serve via proxy.""" if media_content_type == MEDIA_TYPE_APP and media_content_id: image_url = self.coordinator.roku.app_icon_url(media_content_id)