Make integration setup optional (#48381)

This commit is contained in:
Franck Nijhof 2021-03-29 22:53:47 +02:00 committed by GitHub
parent dda9f957b6
commit 52475c108f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 39 additions and 77 deletions

View file

@ -8,7 +8,6 @@ from aiohttp.client_exceptions import ClientConnectorError
from async_timeout import timeout from async_timeout import timeout
from homeassistant.const import CONF_API_KEY from homeassistant.const import CONF_API_KEY
from homeassistant.core import Config, HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -26,12 +25,6 @@ _LOGGER = logging.getLogger(__name__)
PLATFORMS = ["sensor", "weather"] PLATFORMS = ["sensor", "weather"]
async def async_setup(hass: HomeAssistant, config: Config) -> bool:
"""Set up configured AccuWeather."""
hass.data.setdefault(DOMAIN, {})
return True
async def async_setup_entry(hass, config_entry) -> bool: async def async_setup_entry(hass, config_entry) -> bool:
"""Set up AccuWeather as config entry.""" """Set up AccuWeather as config entry."""
api_key = config_entry.data[CONF_API_KEY] api_key = config_entry.data[CONF_API_KEY]
@ -52,7 +45,7 @@ async def async_setup_entry(hass, config_entry) -> bool:
undo_listener = config_entry.add_update_listener(update_listener) undo_listener = config_entry.add_update_listener(update_listener)
hass.data[DOMAIN][config_entry.entry_id] = { hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = {
COORDINATOR: coordinator, COORDINATOR: coordinator,
UNDO_UPDATE_LISTENER: undo_listener, UNDO_UPDATE_LISTENER: undo_listener,
} }

View file

@ -34,7 +34,6 @@ from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -49,11 +48,6 @@ SERVICE_REFRESH_SCHEMA = vol.Schema(
PLATFORMS = ["sensor", "switch"] PLATFORMS = ["sensor", "switch"]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the AdGuard Home components."""
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up AdGuard Home from a config entry.""" """Set up AdGuard Home from a config entry."""
session = async_get_clientsession(hass, entry.data[CONF_VERIFY_SSL]) session = async_get_clientsession(hass, entry.data[CONF_VERIFY_SSL])

View file

@ -38,11 +38,6 @@ _LOGGER = logging.getLogger(__name__)
PLATFORMS = ["sensor"] PLATFORMS = ["sensor"]
async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the AirNow component."""
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up AirNow from a config entry.""" """Set up AirNow from a config entry."""
api_key = entry.data[CONF_API_KEY] api_key = entry.data[CONF_API_KEY]

View file

@ -20,11 +20,6 @@ CONFIG_SCHEMA = vol.Schema(
) )
async def async_setup(hass, config):
"""Old way of setting up deCONZ integrations."""
return True
async def async_setup_entry(hass, config_entry): async def async_setup_entry(hass, config_entry):
"""Set up a deCONZ bridge for a config entry. """Set up a deCONZ bridge for a config entry.

View file

@ -9,16 +9,10 @@ from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import ConfigType
from .const import DATA_ELGATO_CLIENT, DOMAIN from .const import DATA_ELGATO_CLIENT, DOMAIN
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Elgato Key Light components."""
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Elgato Key Light from a config entry.""" """Set up Elgato Key Light from a config entry."""
session = async_get_clientsession(hass) session = async_get_clientsession(hass)

View file

@ -42,7 +42,7 @@ from homeassistant.helpers.json import JSONEncoder
from homeassistant.helpers.service import async_set_service_schema from homeassistant.helpers.service import async_set_service_schema
from homeassistant.helpers.storage import Store from homeassistant.helpers.storage import Store
from homeassistant.helpers.template import Template from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
# Import config flow so that it's added to the registry # Import config flow so that it's added to the registry
from .entry_data import RuntimeEntryData from .entry_data import RuntimeEntryData
@ -56,11 +56,6 @@ STORAGE_VERSION = 1
CONFIG_SCHEMA = vol.Schema({}, extra=vol.ALLOW_EXTRA) CONFIG_SCHEMA = vol.Schema({}, extra=vol.ALLOW_EXTRA)
async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
"""Stub to allow setting up this component."""
return True
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
"""Set up the esphome component.""" """Set up the esphome component."""
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})

View file

@ -24,7 +24,6 @@ from homeassistant.helpers.device_registry import (
EVENT_DEVICE_REGISTRY_UPDATED, EVENT_DEVICE_REGISTRY_UPDATED,
async_entries_for_config_entry, async_entries_for_config_entry,
) )
from homeassistant.helpers.typing import HomeAssistantType
from . import device_automation, discovery from . import device_automation, discovery
from .const import ( from .const import (
@ -38,11 +37,6 @@ from .const import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistantType, config: dict):
"""Set up the Tasmota component."""
return True
async def async_setup_entry(hass, entry): async def async_setup_entry(hass, entry):
"""Set up Tasmota from a config entry.""" """Set up Tasmota from a config entry."""
websocket_api.async_register_command(hass, websocket_remove_device) websocket_api.async_register_command(hass, websocket_remove_device)

View file

@ -16,7 +16,6 @@ from homeassistant.const import ATTR_NAME, CONF_HOST
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
CoordinatorEntity, CoordinatorEntity,
DataUpdateCoordinator, DataUpdateCoordinator,
@ -37,11 +36,6 @@ PLATFORMS = (LIGHT_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the WLED components."""
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up WLED from a config entry.""" """Set up WLED from a config entry."""

View file

@ -169,7 +169,7 @@ async def _async_setup_component(
return False return False
if integration.disabled: if integration.disabled:
log_error(f"dependency is disabled - {integration.disabled}") log_error(f"Dependency is disabled - {integration.disabled}")
return False return False
# Validate all dependencies exist and there are no circular dependencies # Validate all dependencies exist and there are no circular dependencies
@ -219,6 +219,8 @@ async def _async_setup_component(
SLOW_SETUP_WARNING, SLOW_SETUP_WARNING,
) )
task = None
result = True
try: try:
if hasattr(component, "async_setup"): if hasattr(component, "async_setup"):
task = component.async_setup(hass, processed_config) # type: ignore task = component.async_setup(hass, processed_config) # type: ignore
@ -228,13 +230,14 @@ async def _async_setup_component(
task = hass.loop.run_in_executor( task = hass.loop.run_in_executor(
None, component.setup, hass, processed_config # type: ignore None, component.setup, hass, processed_config # type: ignore
) )
else: elif not hasattr(component, "async_setup_entry"):
log_error("No setup function defined.") log_error("No setup or config entry setup function defined.")
hass.data[DATA_SETUP_STARTED].pop(domain) hass.data[DATA_SETUP_STARTED].pop(domain)
return False return False
async with hass.timeout.async_timeout(SLOW_SETUP_MAX_WAIT, domain): if task:
result = await task async with hass.timeout.async_timeout(SLOW_SETUP_MAX_WAIT, domain):
result = await task
except asyncio.TimeoutError: except asyncio.TimeoutError:
_LOGGER.error( _LOGGER.error(
"Setup of %s is taking longer than %s seconds." "Setup of %s is taking longer than %s seconds."

View file

@ -2,7 +2,6 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from typing import Any
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -14,11 +13,6 @@ from .const import DOMAIN
PLATFORMS = ["light"] PLATFORMS = ["light"]
async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
"""Set up the NEW_NAME component."""
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up NEW_NAME from a config entry.""" """Set up NEW_NAME from a config entry."""
# TODO Store an API object for your platforms to access # TODO Store an API object for your platforms to access

View file

@ -20,8 +20,6 @@ async def test_form(hass: HomeAssistant) -> None:
"homeassistant.components.NEW_DOMAIN.config_flow.PlaceholderHub.authenticate", "homeassistant.components.NEW_DOMAIN.config_flow.PlaceholderHub.authenticate",
return_value=True, return_value=True,
), patch( ), patch(
"homeassistant.components.NEW_DOMAIN.async_setup", return_value=True
) as mock_setup, patch(
"homeassistant.components.NEW_DOMAIN.async_setup_entry", "homeassistant.components.NEW_DOMAIN.async_setup_entry",
return_value=True, return_value=True,
) as mock_setup_entry: ) as mock_setup_entry:
@ -42,7 +40,6 @@ async def test_form(hass: HomeAssistant) -> None:
"username": "test-username", "username": "test-username",
"password": "test-password", "password": "test-password",
} }
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1

View file

@ -2,7 +2,6 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from typing import Any
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -14,11 +13,6 @@ from .const import DOMAIN
PLATFORMS = ["light"] PLATFORMS = ["light"]
async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
"""Set up the NEW_NAME component."""
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up NEW_NAME from a config entry.""" """Set up NEW_NAME from a config entry."""
# TODO Store an API object for your platforms to access # TODO Store an API object for your platforms to access

View file

@ -566,7 +566,7 @@ class MockModule:
if platform_schema_base is not None: if platform_schema_base is not None:
self.PLATFORM_SCHEMA_BASE = platform_schema_base self.PLATFORM_SCHEMA_BASE = platform_schema_base
if setup is not None: if setup:
# We run this in executor, wrap it in function # We run this in executor, wrap it in function
self.setup = lambda *args: setup(*args) self.setup = lambda *args: setup(*args)

View file

@ -75,9 +75,7 @@ async def test_form(hass):
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["errors"] == {} assert result["errors"] == {}
with patch("pyairnow.WebServiceAPI._get", return_value=MOCK_RESPONSE,), patch( with patch("pyairnow.WebServiceAPI._get", return_value=MOCK_RESPONSE), patch(
"homeassistant.components.airnow.async_setup", return_value=True
) as mock_setup, patch(
"homeassistant.components.airnow.async_setup_entry", "homeassistant.components.airnow.async_setup_entry",
return_value=True, return_value=True,
) as mock_setup_entry: ) as mock_setup_entry:
@ -90,7 +88,6 @@ async def test_form(hass):
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result2["data"] == CONFIG assert result2["data"] == CONFIG
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1

View file

@ -539,8 +539,6 @@ async def test_flow_hassio_discovery(hass):
assert result["description_placeholders"] == {"addon": "Mock Addon"} assert result["description_placeholders"] == {"addon": "Mock Addon"}
with patch( with patch(
"homeassistant.components.deconz.async_setup", return_value=True
) as mock_setup, patch(
"homeassistant.components.deconz.async_setup_entry", "homeassistant.components.deconz.async_setup_entry",
return_value=True, return_value=True,
) as mock_setup_entry: ) as mock_setup_entry:
@ -555,7 +553,6 @@ async def test_flow_hassio_discovery(hass):
CONF_PORT: 80, CONF_PORT: 80,
CONF_API_KEY: API_KEY, CONF_API_KEY: API_KEY,
} }
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1

View file

@ -3,7 +3,7 @@
import asyncio import asyncio
import os import os
import threading import threading
from unittest.mock import Mock, patch from unittest.mock import AsyncMock, Mock, patch
import pytest import pytest
import voluptuous as vol import voluptuous as vol
@ -618,3 +618,29 @@ async def test_async_get_loaded_integrations(hass):
"myintegration", "myintegration",
"device_tracker", "device_tracker",
} }
async def test_integration_no_setup(hass, caplog):
"""Test we fail integration setup without setup functions."""
mock_integration(
hass,
MockModule("test_integration_without_setup", setup=False),
)
result = await setup.async_setup_component(
hass, "test_integration_without_setup", {}
)
assert not result
assert "No setup or config entry setup function defined" in caplog.text
async def test_integration_only_setup_entry(hass):
"""Test we have an integration with only a setup entry method."""
mock_integration(
hass,
MockModule(
"test_integration_only_entry",
setup=False,
async_setup_entry=AsyncMock(return_value=True),
),
)
assert await setup.async_setup_component(hass, "test_integration_only_entry", {})