From 6fd47d035ea658aa93f28b4cd741ed7cfb399a3b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 11 Jul 2022 08:40:52 -0700 Subject: [PATCH] Add basic Rhasspy integration (#74942) Co-authored-by: Franck Nijhof --- .strict-typing | 1 + CODEOWNERS | 2 + homeassistant/components/rhasspy/__init__.py | 15 +++++++ .../components/rhasspy/config_flow.py | 29 ++++++++++++ homeassistant/components/rhasspy/const.py | 3 ++ .../components/rhasspy/manifest.json | 9 ++++ homeassistant/components/rhasspy/strings.json | 12 +++++ .../components/rhasspy/translations/en.json | 12 +++++ homeassistant/generated/config_flows.py | 1 + mypy.ini | 11 +++++ tests/components/rhasspy/__init__.py | 1 + tests/components/rhasspy/test_config_flow.py | 44 +++++++++++++++++++ tests/components/rhasspy/test_init.py | 26 +++++++++++ 13 files changed, 166 insertions(+) create mode 100644 homeassistant/components/rhasspy/__init__.py create mode 100644 homeassistant/components/rhasspy/config_flow.py create mode 100644 homeassistant/components/rhasspy/const.py create mode 100644 homeassistant/components/rhasspy/manifest.json create mode 100644 homeassistant/components/rhasspy/strings.json create mode 100644 homeassistant/components/rhasspy/translations/en.json create mode 100644 tests/components/rhasspy/__init__.py create mode 100644 tests/components/rhasspy/test_config_flow.py create mode 100644 tests/components/rhasspy/test_init.py diff --git a/.strict-typing b/.strict-typing index becd5cdda9a..9792f401ac4 100644 --- a/.strict-typing +++ b/.strict-typing @@ -193,6 +193,7 @@ homeassistant.components.recorder.* homeassistant.components.remote.* homeassistant.components.renault.* homeassistant.components.resolution_center.* +homeassistant.components.rhasspy.* homeassistant.components.ridwell.* homeassistant.components.rituals_perfume_genie.* homeassistant.components.roku.* diff --git a/CODEOWNERS b/CODEOWNERS index 4e5d07f54d8..a473d07af9d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -865,6 +865,8 @@ build.json @home-assistant/supervisor /tests/components/rflink/ @javicalle /homeassistant/components/rfxtrx/ @danielhiversen @elupus @RobBie1221 /tests/components/rfxtrx/ @danielhiversen @elupus @RobBie1221 +/homeassistant/components/rhasspy/ @balloob @synesthesiam +/tests/components/rhasspy/ @balloob @synesthesiam /homeassistant/components/ridwell/ @bachya /tests/components/ridwell/ @bachya /homeassistant/components/ring/ @balloob diff --git a/homeassistant/components/rhasspy/__init__.py b/homeassistant/components/rhasspy/__init__.py new file mode 100644 index 00000000000..669d81952d4 --- /dev/null +++ b/homeassistant/components/rhasspy/__init__.py @@ -0,0 +1,15 @@ +"""The Rhasspy integration.""" +from __future__ import annotations + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Rhasspy from a config entry.""" + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + return True diff --git a/homeassistant/components/rhasspy/config_flow.py b/homeassistant/components/rhasspy/config_flow.py new file mode 100644 index 00000000000..69ed802f817 --- /dev/null +++ b/homeassistant/components/rhasspy/config_flow.py @@ -0,0 +1,29 @@ +"""Config flow for Rhasspy integration.""" +from __future__ import annotations + +from typing import Any + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.data_entry_flow import FlowResult + +from .const import DOMAIN + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Rhasspy.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + if user_input is None: + return self.async_show_form(step_id="user", data_schema=vol.Schema({})) + + return self.async_create_entry(title="Rhasspy", data={}) diff --git a/homeassistant/components/rhasspy/const.py b/homeassistant/components/rhasspy/const.py new file mode 100644 index 00000000000..127f20032ac --- /dev/null +++ b/homeassistant/components/rhasspy/const.py @@ -0,0 +1,3 @@ +"""Constants for the Rhasspy integration.""" + +DOMAIN = "rhasspy" diff --git a/homeassistant/components/rhasspy/manifest.json b/homeassistant/components/rhasspy/manifest.json new file mode 100644 index 00000000000..8b11e231b8c --- /dev/null +++ b/homeassistant/components/rhasspy/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "rhasspy", + "name": "Rhasspy", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/rhasspy", + "dependencies": ["intent"], + "codeowners": ["@balloob", "@synesthesiam"], + "iot_class": "local_push" +} diff --git a/homeassistant/components/rhasspy/strings.json b/homeassistant/components/rhasspy/strings.json new file mode 100644 index 00000000000..4d2111ebd8a --- /dev/null +++ b/homeassistant/components/rhasspy/strings.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "description": "Do you want to enable Rhasspy support?" + } + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + } + } +} diff --git a/homeassistant/components/rhasspy/translations/en.json b/homeassistant/components/rhasspy/translations/en.json new file mode 100644 index 00000000000..af826fbf27d --- /dev/null +++ b/homeassistant/components/rhasspy/translations/en.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Already configured. Only a single configuration possible." + }, + "step": { + "user": { + "description": "Do you want to enable Rhasspy support?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index d3be2c5e674..3c02842e856 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -290,6 +290,7 @@ FLOWS = { "recollect_waste", "renault", "rfxtrx", + "rhasspy", "ridwell", "ring", "risco", diff --git a/mypy.ini b/mypy.ini index 65b51e9d1c9..1fb12c3a47e 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1886,6 +1886,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.rhasspy.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.ridwell.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/tests/components/rhasspy/__init__.py b/tests/components/rhasspy/__init__.py new file mode 100644 index 00000000000..521c5166040 --- /dev/null +++ b/tests/components/rhasspy/__init__.py @@ -0,0 +1 @@ +"""Tests for the Rhasspy integration.""" diff --git a/tests/components/rhasspy/test_config_flow.py b/tests/components/rhasspy/test_config_flow.py new file mode 100644 index 00000000000..53c82c0cecd --- /dev/null +++ b/tests/components/rhasspy/test_config_flow.py @@ -0,0 +1,44 @@ +"""Test the Rhasspy config flow.""" +from unittest.mock import patch + +from homeassistant import config_entries +from homeassistant.components.rhasspy.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + +from tests.common import MockConfigEntry + + +async def test_form(hass: HomeAssistant) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + assert result["errors"] is None + + with patch( + "homeassistant.components.rhasspy.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + await hass.async_block_till_done() + + assert result2["type"] == FlowResultType.CREATE_ENTRY + assert result2["title"] == "Rhasspy" + assert result2["data"] == {} + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_single_entry(hass: HomeAssistant) -> None: + """Test we only allow single entry.""" + MockConfigEntry(domain=DOMAIN).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "single_instance_allowed" diff --git a/tests/components/rhasspy/test_init.py b/tests/components/rhasspy/test_init.py new file mode 100644 index 00000000000..e4f0b346347 --- /dev/null +++ b/tests/components/rhasspy/test_init.py @@ -0,0 +1,26 @@ +"""Tests for the Rhasspy integration.""" +from homeassistant.components.rhasspy.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def test_load_unload_config_entry(hass: HomeAssistant) -> None: + """Test the Rhasspy configuration entry loading/unloading.""" + mock_config_entry = MockConfigEntry( + title="Rhasspy", + domain=DOMAIN, + data={}, + ) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.LOADED + + await hass.config_entries.async_unload(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert not hass.data.get(DOMAIN) + assert mock_config_entry.state is ConfigEntryState.NOT_LOADED