Add config flow to Media Extractor (#115717)
parent
87989a88cd
commit
a2b1dd8a5f
|
@ -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
|
||||
|
|
|
@ -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={})
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "[%key:common::config_flow::description::confirm_setup%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"play_media": {
|
||||
"name": "Play media",
|
||||
|
|
|
@ -318,6 +318,7 @@ FLOWS = {
|
|||
"matter",
|
||||
"meater",
|
||||
"medcom_ble",
|
||||
"media_extractor",
|
||||
"melcloud",
|
||||
"melnor",
|
||||
"met",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue