From c07f02534b26a2b6a6a21ccde4b4684d37d570d1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 2 May 2024 19:35:16 -0500 Subject: [PATCH] Migrate bluetooth to use the singleton helper (#116629) --- .../components/bluetooth/__init__.py | 8 +++---- homeassistant/components/bluetooth/api.py | 13 ++++------ .../components/bluetooth/config_flow.py | 4 ++-- homeassistant/components/bluetooth/models.py | 8 ------- tests/components/bluetooth/test_init.py | 24 ++++++++++++------- 5 files changed, 25 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/bluetooth/__init__.py b/homeassistant/components/bluetooth/__init__.py index acc38cad58b..49fadd1892e 100644 --- a/homeassistant/components/bluetooth/__init__.py +++ b/homeassistant/components/bluetooth/__init__.py @@ -51,8 +51,9 @@ from homeassistant.helpers.event import async_call_later from homeassistant.helpers.issue_registry import async_delete_issue from homeassistant.loader import async_get_bluetooth -from . import models, passive_update_processor +from . import passive_update_processor from .api import ( + _get_manager, async_address_present, async_ble_device_from_address, async_discovered_service_info, @@ -76,7 +77,6 @@ from .const import ( CONF_ADAPTER, CONF_DETAILS, CONF_PASSIVE, - DATA_MANAGER, DOMAIN, FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS, LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS, @@ -230,10 +230,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass, integration_matcher, bluetooth_adapters, bluetooth_storage, slot_manager ) set_manager(manager) - await storage_setup_task await manager.async_setup() - hass.data[DATA_MANAGER] = models.MANAGER = manager hass.async_create_background_task( _async_start_adapter_discovery(hass, manager, bluetooth_adapters), @@ -314,7 +312,7 @@ async def async_update_device( async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up a config entry for a bluetooth scanner.""" - manager: HomeAssistantBluetoothManager = hass.data[DATA_MANAGER] + manager = _get_manager(hass) address = entry.unique_id assert address is not None adapter = await manager.async_get_adapter_from_address_or_recover(address) diff --git a/homeassistant/components/bluetooth/api.py b/homeassistant/components/bluetooth/api.py index b1a6bc87728..505651edafd 100644 --- a/homeassistant/components/bluetooth/api.py +++ b/homeassistant/components/bluetooth/api.py @@ -15,10 +15,12 @@ from habluetooth import ( BluetoothScannerDevice, BluetoothScanningMode, HaBleakScannerWrapper, + get_manager, ) from home_assistant_bluetooth import BluetoothServiceInfoBleak from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback +from homeassistant.helpers.singleton import singleton from .const import DATA_MANAGER from .manager import HomeAssistantBluetoothManager @@ -29,9 +31,10 @@ if TYPE_CHECKING: from bleak.backends.device import BLEDevice +@singleton(DATA_MANAGER) def _get_manager(hass: HomeAssistant) -> HomeAssistantBluetoothManager: """Get the bluetooth manager.""" - return cast(HomeAssistantBluetoothManager, hass.data[DATA_MANAGER]) + return cast(HomeAssistantBluetoothManager, get_manager()) @hass_callback @@ -68,8 +71,6 @@ def async_discovered_service_info( hass: HomeAssistant, connectable: bool = True ) -> Iterable[BluetoothServiceInfoBleak]: """Return the discovered devices list.""" - if DATA_MANAGER not in hass.data: - return [] return _get_manager(hass).async_discovered_service_info(connectable) @@ -78,8 +79,6 @@ def async_last_service_info( hass: HomeAssistant, address: str, connectable: bool = True ) -> BluetoothServiceInfoBleak | None: """Return the last service info for an address.""" - if DATA_MANAGER not in hass.data: - return None return _get_manager(hass).async_last_service_info(address, connectable) @@ -88,8 +87,6 @@ def async_ble_device_from_address( hass: HomeAssistant, address: str, connectable: bool = True ) -> BLEDevice | None: """Return BLEDevice for an address if its present.""" - if DATA_MANAGER not in hass.data: - return None return _get_manager(hass).async_ble_device_from_address(address, connectable) @@ -106,8 +103,6 @@ def async_address_present( hass: HomeAssistant, address: str, connectable: bool = True ) -> bool: """Check if an address is present in the bluetooth device list.""" - if DATA_MANAGER not in hass.data: - return False return _get_manager(hass).async_address_present(address, connectable) diff --git a/homeassistant/components/bluetooth/config_flow.py b/homeassistant/components/bluetooth/config_flow.py index 90d2624fb0f..37eefd2f265 100644 --- a/homeassistant/components/bluetooth/config_flow.py +++ b/homeassistant/components/bluetooth/config_flow.py @@ -14,6 +14,7 @@ from bluetooth_adapters import ( adapter_model, get_adapters, ) +from habluetooth import get_manager import voluptuous as vol from homeassistant.components import onboarding @@ -25,7 +26,6 @@ from homeassistant.helpers.schema_config_entry_flow import ( ) from homeassistant.helpers.typing import DiscoveryInfoType -from . import models from .const import CONF_ADAPTER, CONF_DETAILS, CONF_PASSIVE, DOMAIN from .util import adapter_title @@ -185,4 +185,4 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN): @callback def async_supports_options_flow(cls, config_entry: ConfigEntry) -> bool: """Return options flow support for this handler.""" - return bool(models.MANAGER and models.MANAGER.supports_passive_scan) + return bool((manager := get_manager()) and manager.supports_passive_scan) diff --git a/homeassistant/components/bluetooth/models.py b/homeassistant/components/bluetooth/models.py index a14aaf1d379..a97056e1f4b 100644 --- a/homeassistant/components/bluetooth/models.py +++ b/homeassistant/components/bluetooth/models.py @@ -4,17 +4,9 @@ from __future__ import annotations from collections.abc import Callable from enum import Enum -from typing import TYPE_CHECKING from home_assistant_bluetooth import BluetoothServiceInfoBleak -if TYPE_CHECKING: - from .manager import HomeAssistantBluetoothManager - - -MANAGER: HomeAssistantBluetoothManager | None = None - - BluetoothChange = Enum("BluetoothChange", "ADVERTISEMENT") BluetoothCallback = Callable[[BluetoothServiceInfoBleak, BluetoothChange], None] ProcessAdvertisementCallback = Callable[[BluetoothServiceInfoBleak], bool] diff --git a/tests/components/bluetooth/test_init.py b/tests/components/bluetooth/test_init.py index 8c26745d541..ebc50779c9c 100644 --- a/tests/components/bluetooth/test_init.py +++ b/tests/components/bluetooth/test_init.py @@ -8,7 +8,7 @@ from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch from bleak import BleakError from bleak.backends.scanner import AdvertisementData, BLEDevice from bluetooth_adapters import DEFAULT_ADDRESS -from habluetooth import scanner +from habluetooth import scanner, set_manager from habluetooth.wrappers import HaBleakScannerWrapper import pytest @@ -1154,6 +1154,7 @@ async def test_async_discovered_device_api( ) -> None: """Test the async_discovered_device API.""" mock_bt = [] + set_manager(None) with ( patch( "homeassistant.components.bluetooth.async_get_bluetooth", @@ -1169,8 +1170,10 @@ async def test_async_discovered_device_api( }, ), ): - assert not bluetooth.async_discovered_service_info(hass) - assert not bluetooth.async_address_present(hass, "44:44:22:22:11:22") + with pytest.raises(RuntimeError, match="BluetoothManager has not been set"): + assert not bluetooth.async_discovered_service_info(hass) + with pytest.raises(RuntimeError, match="BluetoothManager has not been set"): + assert not bluetooth.async_address_present(hass, "44:44:22:22:11:22") await async_setup_with_default_adapter(hass) with patch.object(hass.config_entries.flow, "async_init"): @@ -2744,6 +2747,7 @@ async def test_async_ble_device_from_address( hass: HomeAssistant, mock_bleak_scanner_start: MagicMock, macos_adapter: None ) -> None: """Test the async_ble_device_from_address api.""" + set_manager(None) mock_bt = [] with ( patch( @@ -2760,11 +2764,15 @@ async def test_async_ble_device_from_address( }, ), ): - assert not bluetooth.async_discovered_service_info(hass) - assert not bluetooth.async_address_present(hass, "44:44:22:22:11:22") - assert ( - bluetooth.async_ble_device_from_address(hass, "44:44:33:11:23:45") is None - ) + with pytest.raises(RuntimeError, match="BluetoothManager has not been set"): + assert not bluetooth.async_discovered_service_info(hass) + with pytest.raises(RuntimeError, match="BluetoothManager has not been set"): + assert not bluetooth.async_address_present(hass, "44:44:22:22:11:22") + with pytest.raises(RuntimeError, match="BluetoothManager has not been set"): + assert ( + bluetooth.async_ble_device_from_address(hass, "44:44:33:11:23:45") + is None + ) await async_setup_with_default_adapter(hass)