Add config flow to Media Extractor (#115717)

pull/118229/head
Joost Lekkerkerker 2024-05-27 10:43:49 +02:00 committed by GitHub
parent 87989a88cd
commit a2b1dd8a5f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 156 additions and 10 deletions

View File

@ -16,8 +16,10 @@ from homeassistant.components.media_player import (
MEDIA_PLAYER_PLAY_MEDIA_SCHEMA,
SERVICE_PLAY_MEDIA,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import (
DOMAIN as HOMEASSISTANT_DOMAIN,
HomeAssistant,
ServiceCall,
ServiceResponse,
@ -25,6 +27,7 @@ from homeassistant.core import (
)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType
from .const import (
@ -55,16 +58,49 @@ CONFIG_SCHEMA = vol.Schema(
)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Media Extractor from a config entry."""
return True
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the media extractor service."""
async def extract_media_url(call: ServiceCall) -> ServiceResponse:
"""Extract media url."""
youtube_dl = YoutubeDL(
{"quiet": True, "logger": _LOGGER, "format": call.data[ATTR_FORMAT_QUERY]}
if DOMAIN in config:
async_create_issue(
hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
breaks_in_ha_version="2024.11.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Media extractor",
},
)
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
)
)
async def extract_media_url(call: ServiceCall) -> ServiceResponse:
"""Extract media url."""
def extract_info() -> dict[str, Any]:
youtube_dl = YoutubeDL(
{
"quiet": True,
"logger": _LOGGER,
"format": call.data[ATTR_FORMAT_QUERY],
}
)
return cast(
dict[str, Any],
youtube_dl.extract_info(
@ -93,7 +129,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
def play_media(call: ServiceCall) -> None:
"""Get stream URL and send it to the play_media service."""
MediaExtractor(hass, config[DOMAIN], call.data).extract_and_send()
MediaExtractor(hass, config.get(DOMAIN, {}), call.data).extract_and_send()
default_format_query = config.get(DOMAIN, {}).get(
CONF_DEFAULT_STREAM_QUERY, DEFAULT_STREAM_QUERY

View File

@ -0,0 +1,32 @@
"""Config flow for Media Extractor integration."""
from __future__ import annotations
from typing import Any
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from .const import DOMAIN
class MediaExtractorConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Media Extractor."""
VERSION = 1
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
if user_input is not None:
return self.async_create_entry(title="Media extractor", data={})
return self.async_show_form(step_id="user", data_schema=vol.Schema({}))
async def async_step_import(
self, import_config: dict[str, Any]
) -> ConfigFlowResult:
"""Handle import."""
return self.async_create_entry(title="Media extractor", data={})

View File

@ -2,10 +2,12 @@
"domain": "media_extractor",
"name": "Media Extractor",
"codeowners": ["@joostlek"],
"config_flow": true,
"dependencies": ["media_player"],
"documentation": "https://www.home-assistant.io/integrations/media_extractor",
"iot_class": "calculated",
"loggers": ["yt_dlp"],
"quality_scale": "internal",
"requirements": ["yt-dlp==2024.04.09"]
"requirements": ["yt-dlp==2024.04.09"],
"single_config_entry": true
}

View File

@ -1,4 +1,11 @@
{
"config": {
"step": {
"user": {
"description": "[%key:common::config_flow::description::confirm_setup%]"
}
}
},
"services": {
"play_media": {
"name": "Play media",

View File

@ -318,6 +318,7 @@ FLOWS = {
"matter",
"meater",
"medcom_ble",
"media_extractor",
"melcloud",
"melnor",
"met",

View File

@ -3509,8 +3509,9 @@
"media_extractor": {
"name": "Media Extractor",
"integration_type": "hub",
"config_flow": false,
"iot_class": "calculated"
"config_flow": true,
"iot_class": "calculated",
"single_config_entry": true
},
"mediaroom": {
"name": "Mediaroom",

View File

@ -1,7 +1,8 @@
"""The tests for Media Extractor integration."""
"""Common fixtures for the Media Extractor tests."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
import pytest
@ -53,3 +54,12 @@ def empty_media_extractor_config() -> dict[str, Any]:
def audio_media_extractor_config() -> dict[str, Any]:
"""Media extractor config for audio."""
return {DOMAIN: {"default_query": AUDIO_QUERY}}
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.media_extractor.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry

View File

@ -0,0 +1,56 @@
"""Tests for the Media extractor config flow."""
from homeassistant.components.media_extractor.const import DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry
async def test_full_user_flow(hass: HomeAssistant, mock_setup_entry) -> None:
"""Test the full user configuration flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result.get("type") is FlowResultType.FORM
assert result.get("step_id") == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={},
)
assert result.get("type") is FlowResultType.CREATE_ENTRY
assert result.get("title") == "Media extractor"
assert result.get("data") == {}
assert result.get("options") == {}
assert len(mock_setup_entry.mock_calls) == 1
async def test_single_instance_allowed(hass: HomeAssistant) -> None:
"""Test we abort if already setup."""
mock_config_entry = MockConfigEntry(domain=DOMAIN)
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data={}
)
assert result.get("type") is FlowResultType.ABORT
assert result.get("reason") == "single_instance_allowed"
async def test_import_flow(hass: HomeAssistant, mock_setup_entry) -> None:
"""Test import flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}
)
assert result.get("type") is FlowResultType.CREATE_ENTRY
assert result.get("title") == "Media extractor"
assert result.get("data") == {}
assert result.get("options") == {}
assert len(mock_setup_entry.mock_calls) == 1

View File

@ -36,6 +36,7 @@ async def test_play_media_service_is_registered(hass: HomeAssistant) -> None:
assert hass.services.has_service(DOMAIN, SERVICE_PLAY_MEDIA)
assert hass.services.has_service(DOMAIN, SERVICE_EXTRACT_MEDIA_URL)
assert len(hass.config_entries.async_entries(DOMAIN))
@pytest.mark.parametrize(