diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bbf7295be99..51429bdf94b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - --skip="./.*,*.csv,*.json" - --quiet-level=2 exclude_types: [csv, json] - exclude: ^tests/fixtures/ + exclude: ^tests/fixtures/|homeassistant/generated/ - repo: https://github.com/PyCQA/flake8 rev: 4.0.1 hooks: diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index bea08722eb0..b7e7a353633 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -22,6 +22,7 @@ from homeassistant.exceptions import ( TemplateError, Unauthorized, ) +from homeassistant.generated import supported_brands from homeassistant.helpers import config_validation as cv, entity, template from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.event import ( @@ -68,6 +69,7 @@ def async_register_commands( async_reg(hass, handle_unsubscribe_events) async_reg(hass, handle_validate_config) async_reg(hass, handle_subscribe_entities) + async_reg(hass, handle_supported_brands) def pong_message(iden: int) -> dict[str, Any]: @@ -691,3 +693,25 @@ async def handle_validate_config( result[key] = {"valid": True, "error": None} connection.send_result(msg["id"], result) + + +@decorators.websocket_command( + { + vol.Required("type"): "supported_brands", + } +) +@decorators.async_response +async def handle_supported_brands( + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] +) -> None: + """Handle supported brands command.""" + data = {} + for integration in await asyncio.gather( + *[ + async_get_integration(hass, integration) + for integration in supported_brands.HAS_SUPPORTED_BRANDS + ] + ): + data[integration.domain] = integration.manifest["supported_brands"] + + connection.send_result(msg["id"], data) diff --git a/homeassistant/generated/supported_brands.py b/homeassistant/generated/supported_brands.py new file mode 100644 index 00000000000..589e0462cf7 --- /dev/null +++ b/homeassistant/generated/supported_brands.py @@ -0,0 +1,15 @@ +"""Automatically generated by hassfest. + +To update, run python3 -m script.hassfest +""" + +# fmt: off + +HAS_SUPPORTED_BRANDS = ( + "denonavr", + "hunterdouglas_powerview", + "motion_blinds", + "overkiz", + "renault", + "wemo" +) diff --git a/script/hassfest/__main__.py b/script/hassfest/__main__.py index 4bc30583d47..233abda4ed8 100644 --- a/script/hassfest/__main__.py +++ b/script/hassfest/__main__.py @@ -20,6 +20,7 @@ from . import ( requirements, services, ssdp, + supported_brands, translations, usb, zeroconf, @@ -39,6 +40,7 @@ INTEGRATION_PLUGINS = [ requirements, services, ssdp, + supported_brands, translations, usb, zeroconf, diff --git a/script/hassfest/model.py b/script/hassfest/model.py index fc38e1db592..d4e1fbf806a 100644 --- a/script/hassfest/model.py +++ b/script/hassfest/model.py @@ -112,6 +112,11 @@ class Integration: """List of dependencies.""" return self.manifest.get("dependencies", []) + @property + def supported_brands(self) -> dict[str]: + """Return dict of supported brands.""" + return self.manifest.get("supported_brands", {}) + @property def integration_type(self) -> str: """Get integration_type.""" diff --git a/script/hassfest/supported_brands.py b/script/hassfest/supported_brands.py new file mode 100644 index 00000000000..6740260a04c --- /dev/null +++ b/script/hassfest/supported_brands.py @@ -0,0 +1,55 @@ +"""Generate supported_brands data.""" +from __future__ import annotations + +import json + +from .model import Config, Integration + +BASE = """ +\"\"\"Automatically generated by hassfest. + +To update, run python3 -m script.hassfest +\"\"\" + +# fmt: off + +HAS_SUPPORTED_BRANDS = ({}) +""".strip() + + +def generate_and_validate(integrations: dict[str, Integration], config: Config) -> str: + """Validate and generate supported_brands data.""" + + brands = [ + domain + for domain, integration in sorted(integrations.items()) + if integration.supported_brands + ] + + return BASE.format(json.dumps(brands, indent=4)[1:-1]) + + +def validate(integrations: dict[str, Integration], config: Config) -> None: + """Validate supported_brands data.""" + supported_brands_path = config.root / "homeassistant/generated/supported_brands.py" + config.cache["supported_brands"] = content = generate_and_validate( + integrations, config + ) + + if config.specific_integrations: + return + + if supported_brands_path.read_text(encoding="utf-8").strip() != content: + config.add_error( + "supported_brands", + "File supported_brands.py is not up to date. Run python3 -m script.hassfest", + fixable=True, + ) + + +def generate(integrations: dict[str, Integration], config: Config): + """Generate supported_brands data.""" + supported_brands_path = config.root / "homeassistant/generated/supported_brands.py" + supported_brands_path.write_text( + f"{config.cache['supported_brands']}\n", encoding="utf-8" + ) diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 0f4695596fc..f1065061c73 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -22,7 +22,13 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.loader import async_get_integration from homeassistant.setup import DATA_SETUP_TIME, async_setup_component -from tests.common import MockEntity, MockEntityPlatform, async_mock_service +from tests.common import ( + MockEntity, + MockEntityPlatform, + MockModule, + async_mock_service, + mock_integration, +) STATE_KEY_SHORT_NAMES = { "entity_id": "e", @@ -1749,3 +1755,36 @@ async def test_validate_config_invalid(websocket_client, key, config, error): assert msg["type"] == const.TYPE_RESULT assert msg["success"] assert msg["result"] == {key: {"valid": False, "error": error}} + + +async def test_supported_brands(hass, websocket_client): + """Test supported brands.""" + mock_integration( + hass, + MockModule("test", partial_manifest={"supported_brands": {"hello": "World"}}), + ) + mock_integration( + hass, + MockModule( + "abcd", partial_manifest={"supported_brands": {"something": "Something"}} + ), + ) + + with patch( + "homeassistant.generated.supported_brands.HAS_SUPPORTED_BRANDS", + ("abcd", "test"), + ): + await websocket_client.send_json({"id": 7, "type": "supported_brands"}) + msg = await websocket_client.receive_json() + + assert msg["id"] == 7 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + assert msg["result"] == { + "abcd": { + "something": "Something", + }, + "test": { + "hello": "World", + }, + }