Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert to runtime_data #354

Merged
merged 8 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions custom_components/bermuda/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING

from homeassistant.exceptions import ConfigEntryNotReady
Expand All @@ -20,16 +21,25 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

type BermudaConfigEntry = ConfigEntry[BermudaData]


@dataclass
class BermudaData:
"""Holds global data for Bermuda."""

coordinator: BermudaDataUpdateCoordinator


CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
async def async_setup_entry(hass: HomeAssistant, entry: BermudaConfigEntry):
"""Set up this integration using UI."""
if hass.data.get(DOMAIN) is None:
_LOGGER.info(STARTUP_MESSAGE)

coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = BermudaDataUpdateCoordinator(hass, entry)
coordinator = BermudaDataUpdateCoordinator(hass, entry)
entry.runtime_data = BermudaData(coordinator)

await coordinator.async_refresh()

Expand All @@ -44,10 +54,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):


async def async_remove_config_entry_device(
hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry
hass: HomeAssistant, config_entry: BermudaConfigEntry, device_entry: DeviceEntry
) -> bool:
"""Remove a config entry from a device."""
coordinator: BermudaDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator: BermudaDataUpdateCoordinator = config_entry.runtime_data.coordinator
address = None
for ident in device_entry.identifiers:
try:
Expand All @@ -73,14 +83,13 @@ async def async_remove_config_entry_device(
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: BermudaConfigEntry) -> bool:
"""Handle removal of an entry."""
if unload_result := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
_LOGGER.debug("Unloaded platforms.")
hass.data[DOMAIN].pop(entry.entry_id)
return unload_result


async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def async_reload_entry(hass: HomeAssistant, entry: BermudaConfigEntry) -> None:
"""Reload config entry."""
await hass.config_entries.async_reload(entry.entry_id)
10 changes: 5 additions & 5 deletions custom_components/bermuda/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components.bluetooth import MONOTONIC_TIME, BluetoothServiceInfoBleak
from homeassistant.config_entries import ConfigEntry, OptionsFlowWithConfigEntry
from homeassistant.config_entries import OptionsFlowWithConfigEntry
from homeassistant.core import callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.selector import (
Expand Down Expand Up @@ -50,9 +50,9 @@
from .util import rssi_to_metres

if TYPE_CHECKING:
from homeassistant.config_entries import ConfigEntry
from homeassistant.data_entry_flow import FlowResult

from . import BermudaConfigEntry
from .bermuda_device import BermudaDevice
from .coordinator import BermudaDataUpdateCoordinator

Expand Down Expand Up @@ -122,7 +122,7 @@ def async_get_options_flow(config_entry):
class BermudaOptionsFlowHandler(OptionsFlowWithConfigEntry):
"""Config flow options handler for bermuda."""

def __init__(self, config_entry: ConfigEntry) -> None:
def __init__(self, config_entry: BermudaConfigEntry) -> None:
"""Initialize HACS options flow."""
super().__init__(config_entry)
self.coordinator: BermudaDataUpdateCoordinator
Expand All @@ -135,7 +135,7 @@ def __init__(self, config_entry: ConfigEntry) -> None:

async def async_step_init(self, user_input=None): # pylint: disable=unused-argument
"""Manage the options."""
self.coordinator = self.hass.data[DOMAIN][self.config_entry.entry_id]
self.coordinator = self.config_entry.runtime_data.coordinator
self.devices = self.coordinator.devices

messages = {}
Expand Down Expand Up @@ -234,7 +234,7 @@ async def async_step_selectdevices(self, user_input=None):
return await self._update_options()

# Grab the co-ordinator's device list so we can build a selector from it.
self.devices = self.hass.data[DOMAIN][self.config_entry.entry_id].devices
self.devices = self.config_entry.runtime_data.coordinator.devices

# Where we store the options before building the selector
options_list = []
Expand Down
22 changes: 15 additions & 7 deletions custom_components/bermuda/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@
if TYPE_CHECKING:
from habluetooth import BluetoothServiceInfoBleak
from homeassistant.components.bluetooth.manager import HomeAssistantBluetoothManager
from homeassistant.config_entries import ConfigEntry

from . import BermudaConfigEntry
from .bermuda_device_scanner import BermudaDeviceScanner

Cancellable = Callable[[], None]
Expand Down Expand Up @@ -118,11 +118,10 @@ class BermudaDataUpdateCoordinator(DataUpdateCoordinator):
def __init__(
self,
hass: HomeAssistant,
entry: ConfigEntry,
entry: BermudaConfigEntry,
) -> None:
"""Initialize."""
self.platforms = []

self.config_entry = entry

self.sensor_interval = entry.options.get(CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL)
Expand Down Expand Up @@ -153,6 +152,7 @@ def __init__(
self.metadevices: dict[str, BermudaDevice] = {}

self._ad_listener_cancel: Cancellable | None = None
self.last_config_entry_update: datetime | None = None

@callback
def handle_state_changes(ev: Event[EventStateChangedData]):
Expand Down Expand Up @@ -521,7 +521,6 @@ async def _async_update_data(self):
device.name = clean_charbuf(service_info.device.name)
if device.local_name is None and service_info.advertisement.local_name:
device.local_name = clean_charbuf(service_info.advertisement.local_name)

device.manufacturer = device.manufacturer or service_info.manufacturer
device.connectable = service_info.connectable

Expand Down Expand Up @@ -949,7 +948,6 @@ def _refresh_area_by_min_distance(self, device: BermudaDevice):
# FIXME: Asserts should be avoided in non-tests as running python in optimized mode will skip them
assert device.is_scanner is not True # noqa
closest_scanner: BermudaDeviceScanner | None = None

for scanner in device.scanners.values():
# Check each scanner and keep note of the closest one based on rssi_distance.
# Note that rssi_distance is smoothed/filtered, and might be None if the last
Expand Down Expand Up @@ -1031,7 +1029,8 @@ def _refresh_scanners(self, scanners: list[BluetoothScannerDevice] | None = None
# FIXME: Really? This can't possibly be a sensible nesting of loops.
# should probably look at the API. Anyway, we are checking any devices
# that have a "mac" or "bluetooth" connection,
for dev_entry in self.hass.data["device_registry"].devices.data.values():
devreg = dr.async_get(self.hass)
for dev_entry in devreg.devices.data.values():
for dev_connection in dev_entry.connections:
if dev_connection[0] in ["mac", "bluetooth"]:
found_address = format_mac(dev_connection[1])
Expand Down Expand Up @@ -1101,6 +1100,7 @@ def async_call_update_entry() -> None:

We do this via add_job to ensure it runs in the event loop.
"""
self.last_config_entry_update = now()
self.hass.config_entries.async_update_entry(
self.config_entry,
data={
Expand All @@ -1109,7 +1109,15 @@ def async_call_update_entry() -> None:
},
)

self.hass.add_job(async_call_update_entry)
# To prevent strain on the system, let's only update if
# A) we have a new scanner
# B) It has been 30 minutes since our last update
if (
len(self.config_entry.data.get(CONFDATA_SCANNERS, {})) != len(confdata_scanners)
or self.last_config_entry_update is None
or (now() - self.last_config_entry_update).total_seconds() > 1800
):
self.hass.add_job(async_call_update_entry)

return True

Expand Down
8 changes: 4 additions & 4 deletions custom_components/bermuda/device_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,25 @@
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect

from .const import DOMAIN, SIGNAL_DEVICE_NEW
from .const import SIGNAL_DEVICE_NEW
from .entity import BermudaEntity

if TYPE_CHECKING:
from collections.abc import Mapping

from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import BermudaConfigEntry
from .coordinator import BermudaDataUpdateCoordinator


async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: BermudaConfigEntry,
async_add_devices: AddEntitiesCallback,
) -> None:
"""Load Device Tracker entities for a config entry."""
coordinator: BermudaDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator: BermudaDataUpdateCoordinator = entry.runtime_data.coordinator

created_devices = [] # list of devices we've already created entities for

Expand Down
7 changes: 3 additions & 4 deletions custom_components/bermuda/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@
from .const import DOMAIN

if TYPE_CHECKING:
from homeassistant.config_entries import ConfigEntry

from . import BermudaConfigEntry
from .coordinator import BermudaDataUpdateCoordinator


async def async_get_config_entry_diagnostics(hass: HomeAssistant, entry: ConfigEntry) -> dict[str, Any]:
async def async_get_config_entry_diagnostics(hass: HomeAssistant, entry: BermudaConfigEntry) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator: BermudaDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator: BermudaDataUpdateCoordinator = entry.runtime_data.coordinator

# We can call this with our own config_entry because the diags step doesn't
# actually use it.
Expand Down
8 changes: 3 additions & 5 deletions custom_components/bermuda/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@
)

if TYPE_CHECKING:
from homeassistant.config_entries import ConfigEntry

from . import BermudaConfigEntry
from .coordinator import BermudaDataUpdateCoordinator

# from . import BermudaDevice


Expand All @@ -39,7 +37,7 @@ class BermudaEntity(CoordinatorEntity):
def __init__(
self,
coordinator: BermudaDataUpdateCoordinator,
config_entry: ConfigEntry,
config_entry: BermudaConfigEntry,
address: str,
) -> None:
super().__init__(coordinator)
Expand Down Expand Up @@ -161,7 +159,7 @@ class BermudaGlobalEntity(CoordinatorEntity):
def __init__(
self,
coordinator: BermudaDataUpdateCoordinator,
config_entry: ConfigEntry,
config_entry: BermudaConfigEntry,
) -> None:
super().__init__(coordinator)
self.coordinator = coordinator
Expand Down
7 changes: 3 additions & 4 deletions custom_components/bermuda/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,26 @@
_LOGGER,
ADDR_TYPE_IBEACON,
ADDR_TYPE_PRIVATE_BLE_DEVICE,
DOMAIN,
SIGNAL_DEVICE_NEW,
)
from .entity import BermudaEntity, BermudaGlobalEntity

if TYPE_CHECKING:
from collections.abc import Mapping

from homeassistant import config_entries
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import BermudaConfigEntry
from .coordinator import BermudaDataUpdateCoordinator


async def async_setup_entry(
hass: HomeAssistant,
entry: config_entries.ConfigEntry,
entry: BermudaConfigEntry,
async_add_devices: AddEntitiesCallback,
) -> None:
"""Setup sensor platform."""
coordinator: BermudaDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator: BermudaDataUpdateCoordinator = entry.runtime_data.coordinator

created_devices = [] # list of already-created devices

Expand Down
3 changes: 2 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

from unittest.mock import patch
from homeassistant.config_entries import ConfigEntryState

import pytest
from homeassistant.core import HomeAssistant
Expand Down Expand Up @@ -99,5 +100,5 @@ async def setup_bermuda_entry(hass: HomeAssistant):
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test", title=NAME)
config_entry.add_to_hass(hass)
await async_setup_component(hass, DOMAIN, {})
assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN]
assert config_entry.state == ConfigEntryState.LOADED
return config_entry
7 changes: 3 additions & 4 deletions tests/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from custom_components.bermuda.coordinator import BermudaDataUpdateCoordinator

from .const import MOCK_CONFIG
from homeassistant.config_entries import ConfigEntryState

# from pytest_homeassistant_custom_component.common import AsyncMock

Expand All @@ -24,16 +25,14 @@ async def test_setup_unload_and_reload_entry(
hass: HomeAssistant, bypass_get_data, setup_bermuda_entry: MockConfigEntry
):
"""Test entry setup and unload."""
assert isinstance(hass.data[DOMAIN][setup_bermuda_entry.entry_id], BermudaDataUpdateCoordinator)

# Reload the entry and assert that the data from above is still there
assert await hass.config_entries.async_reload(setup_bermuda_entry.entry_id)
assert DOMAIN in hass.data and setup_bermuda_entry.entry_id in hass.data[DOMAIN]
assert isinstance(hass.data[DOMAIN][setup_bermuda_entry.entry_id], BermudaDataUpdateCoordinator)
assert setup_bermuda_entry.state == ConfigEntryState.LOADED

# Unload the entry and verify that the data has been removed
assert await hass.config_entries.async_unload(setup_bermuda_entry.entry_id)
assert setup_bermuda_entry.entry_id not in hass.data[DOMAIN]
assert setup_bermuda_entry.state == ConfigEntryState.NOT_LOADED


async def test_setup_entry_exception(hass, error_on_get_data):
Expand Down
Loading