Make Axis utilise forward_entry_setups (#75178)

pull/76119/head
Robert Svensson 2022-07-28 11:41:03 +02:00 committed by Franck Nijhof
parent 15f87ca0a1
commit 3b8650d053
No known key found for this signature in database
GPG Key ID: D62583BA8AB11CA3
5 changed files with 62 additions and 82 deletions

View File

@ -4,28 +4,36 @@ import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DEVICE, CONF_MAC, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.entity_registry import async_migrate_entries
from .const import DOMAIN as AXIS_DOMAIN
from .device import AxisNetworkDevice
from .const import DOMAIN as AXIS_DOMAIN, PLATFORMS
from .device import AxisNetworkDevice, get_axis_device
from .errors import AuthenticationRequired, CannotConnect
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up the Axis component."""
"""Set up the Axis integration."""
hass.data.setdefault(AXIS_DOMAIN, {})
device = AxisNetworkDevice(hass, config_entry)
if not await device.async_setup():
return False
hass.data[AXIS_DOMAIN][config_entry.unique_id] = device
try:
api = await get_axis_device(hass, config_entry.data)
except CannotConnect as err:
raise ConfigEntryNotReady from err
except AuthenticationRequired as err:
raise ConfigEntryAuthFailed from err
device = hass.data[AXIS_DOMAIN][config_entry.unique_id] = AxisNetworkDevice(
hass, config_entry, api
)
await device.async_update_device_registry()
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
device.async_setup_events()
config_entry.add_update_listener(device.async_new_address_callback)
config_entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.shutdown)
)

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from collections.abc import Mapping
from ipaddress import ip_address
from types import MappingProxyType
from typing import Any
from urllib.parse import urlsplit
@ -32,7 +33,7 @@ from .const import (
DEFAULT_VIDEO_SOURCE,
DOMAIN as AXIS_DOMAIN,
)
from .device import AxisNetworkDevice, get_device
from .device import AxisNetworkDevice, get_axis_device
from .errors import AuthenticationRequired, CannotConnect
AXIS_OUI = {"00:40:8c", "ac:cc:8e", "b8:a4:4f"}
@ -66,13 +67,7 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN):
if user_input is not None:
try:
device = await get_device(
self.hass,
host=user_input[CONF_HOST],
port=user_input[CONF_PORT],
username=user_input[CONF_USERNAME],
password=user_input[CONF_PASSWORD],
)
device = await get_axis_device(self.hass, MappingProxyType(user_input))
serial = device.vapix.serial_number
await self.async_set_unique_id(format_mac(serial))

View File

@ -1,6 +1,8 @@
"""Axis network device abstraction."""
import asyncio
from types import MappingProxyType
from typing import Any
import async_timeout
import axis
@ -24,7 +26,6 @@ from homeassistant.const import (
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_send
@ -50,15 +51,15 @@ from .errors import AuthenticationRequired, CannotConnect
class AxisNetworkDevice:
"""Manages a Axis device."""
def __init__(self, hass, config_entry):
def __init__(self, hass, config_entry, api):
"""Initialize the device."""
self.hass = hass
self.config_entry = config_entry
self.available = True
self.api = api
self.api = None
self.fw_version = None
self.product_type = None
self.available = True
self.fw_version = api.vapix.firmware_version
self.product_type = api.vapix.product_type
@property
def host(self):
@ -184,7 +185,7 @@ class AxisNetworkDevice:
sw_version=self.fw_version,
)
async def use_mqtt(self, hass: HomeAssistant, component: str) -> None:
async def async_use_mqtt(self, hass: HomeAssistant, component: str) -> None:
"""Set up to use MQTT."""
try:
status = await self.api.vapix.mqtt.get_client_status()
@ -209,50 +210,18 @@ class AxisNetworkDevice:
# Setup and teardown methods
async def async_setup(self):
"""Set up the device."""
try:
self.api = await get_device(
self.hass,
host=self.host,
port=self.port,
username=self.username,
password=self.password,
def async_setup_events(self):
"""Set up the device events."""
if self.option_events:
self.api.stream.connection_status_callback.append(
self.async_connection_status_callback
)
self.api.enable_events(event_callback=self.async_event_callback)
self.api.stream.start()
except CannotConnect as err:
raise ConfigEntryNotReady from err
except AuthenticationRequired as err:
raise ConfigEntryAuthFailed from err
self.fw_version = self.api.vapix.firmware_version
self.product_type = self.api.vapix.product_type
async def start_platforms():
await asyncio.gather(
*(
self.hass.config_entries.async_forward_entry_setup(
self.config_entry, platform
)
for platform in PLATFORMS
)
)
if self.option_events:
self.api.stream.connection_status_callback.append(
self.async_connection_status_callback
)
self.api.enable_events(event_callback=self.async_event_callback)
self.api.stream.start()
if self.api.vapix.mqtt:
async_when_setup(self.hass, MQTT_DOMAIN, self.use_mqtt)
self.hass.async_create_task(start_platforms())
self.config_entry.add_update_listener(self.async_new_address_callback)
return True
if self.api.vapix.mqtt:
async_when_setup(self.hass, MQTT_DOMAIN, self.async_use_mqtt)
@callback
def disconnect_from_stream(self):
@ -274,14 +243,21 @@ class AxisNetworkDevice:
)
async def get_device(
hass: HomeAssistant, host: str, port: int, username: str, password: str
async def get_axis_device(
hass: HomeAssistant,
config: MappingProxyType[str, Any],
) -> axis.AxisDevice:
"""Create a Axis device."""
session = get_async_client(hass, verify_ssl=False)
device = axis.AxisDevice(
Configuration(session, host, port=port, username=username, password=password)
Configuration(
session,
config[CONF_HOST],
port=config[CONF_PORT],
username=config[CONF_USERNAME],
password=config[CONF_PASSWORD],
)
)
try:
@ -291,11 +267,13 @@ async def get_device(
return device
except axis.Unauthorized as err:
LOGGER.warning("Connected to device at %s but not registered", host)
LOGGER.warning(
"Connected to device at %s but not registered", config[CONF_HOST]
)
raise AuthenticationRequired from err
except (asyncio.TimeoutError, axis.RequestError) as err:
LOGGER.error("Error connecting to the Axis device at %s", host)
LOGGER.error("Error connecting to the Axis device at %s", config[CONF_HOST])
raise CannotConnect from err
except axis.AxisException as err:

View File

@ -123,7 +123,7 @@ async def test_flow_fails_faulty_credentials(hass):
assert result["step_id"] == SOURCE_USER
with patch(
"homeassistant.components.axis.config_flow.get_device",
"homeassistant.components.axis.config_flow.get_axis_device",
side_effect=config_flow.AuthenticationRequired,
):
result = await hass.config_entries.flow.async_configure(
@ -149,7 +149,7 @@ async def test_flow_fails_cannot_connect(hass):
assert result["step_id"] == SOURCE_USER
with patch(
"homeassistant.components.axis.config_flow.get_device",
"homeassistant.components.axis.config_flow.get_axis_device",
side_effect=config_flow.CannotConnect,
):
result = await hass.config_entries.flow.async_configure(

View File

@ -441,7 +441,7 @@ async def test_device_reset(hass):
async def test_device_not_accessible(hass):
"""Failed setup schedules a retry of setup."""
with patch.object(axis.device, "get_device", side_effect=axis.errors.CannotConnect):
with patch.object(axis, "get_axis_device", side_effect=axis.errors.CannotConnect):
await setup_axis_integration(hass)
assert hass.data[AXIS_DOMAIN] == {}
@ -449,7 +449,7 @@ async def test_device_not_accessible(hass):
async def test_device_trigger_reauth_flow(hass):
"""Failed authentication trigger a reauthentication flow."""
with patch.object(
axis.device, "get_device", side_effect=axis.errors.AuthenticationRequired
axis, "get_axis_device", side_effect=axis.errors.AuthenticationRequired
), patch.object(hass.config_entries.flow, "async_init") as mock_flow_init:
await setup_axis_integration(hass)
mock_flow_init.assert_called_once()
@ -458,7 +458,7 @@ async def test_device_trigger_reauth_flow(hass):
async def test_device_unknown_error(hass):
"""Unknown errors are handled."""
with patch.object(axis.device, "get_device", side_effect=Exception):
with patch.object(axis, "get_axis_device", side_effect=Exception):
await setup_axis_integration(hass)
assert hass.data[AXIS_DOMAIN] == {}
@ -468,7 +468,7 @@ async def test_new_event_sends_signal(hass):
entry = Mock()
entry.data = ENTRY_CONFIG
axis_device = axis.device.AxisNetworkDevice(hass, entry)
axis_device = axis.device.AxisNetworkDevice(hass, entry, Mock())
with patch.object(axis.device, "async_dispatcher_send") as mock_dispatch_send:
axis_device.async_event_callback(action=OPERATION_INITIALIZED, event_id="event")
@ -484,8 +484,7 @@ async def test_shutdown():
entry = Mock()
entry.data = ENTRY_CONFIG
axis_device = axis.device.AxisNetworkDevice(hass, entry)
axis_device.api = Mock()
axis_device = axis.device.AxisNetworkDevice(hass, entry, Mock())
await axis_device.shutdown(None)
@ -497,7 +496,7 @@ async def test_get_device_fails(hass):
with patch(
"axis.vapix.Vapix.request", side_effect=axislib.Unauthorized
), pytest.raises(axis.errors.AuthenticationRequired):
await axis.device.get_device(hass, host="", port="", username="", password="")
await axis.device.get_axis_device(hass, ENTRY_CONFIG)
async def test_get_device_device_unavailable(hass):
@ -505,7 +504,7 @@ async def test_get_device_device_unavailable(hass):
with patch(
"axis.vapix.Vapix.request", side_effect=axislib.RequestError
), pytest.raises(axis.errors.CannotConnect):
await axis.device.get_device(hass, host="", port="", username="", password="")
await axis.device.get_axis_device(hass, ENTRY_CONFIG)
async def test_get_device_unknown_error(hass):
@ -513,4 +512,4 @@ async def test_get_device_unknown_error(hass):
with patch(
"axis.vapix.Vapix.request", side_effect=axislib.AxisException
), pytest.raises(axis.errors.AuthenticationRequired):
await axis.device.get_device(hass, host="", port="", username="", password="")
await axis.device.get_axis_device(hass, ENTRY_CONFIG)