From e54e9279e3860190ca8fc33c3c6be4d33d2dcce4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 4 May 2020 11:23:12 -0700 Subject: [PATCH] Extract instance ID helper from updater (#35043) --- homeassistant/components/updater/__init__.py | 25 +--------------- homeassistant/helpers/device_registry.py | 2 -- homeassistant/helpers/entity_registry.py | 2 -- homeassistant/helpers/instance_id.py | 31 ++++++++++++++++++++ homeassistant/helpers/singleton.py | 2 ++ homeassistant/helpers/storage.py | 19 +++++++----- tests/components/updater/test_init.py | 2 +- tests/helpers/test_instance_id.py | 26 ++++++++++++++++ 8 files changed, 72 insertions(+), 37 deletions(-) create mode 100644 homeassistant/helpers/instance_id.py create mode 100644 tests/helpers/test_instance_id.py diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 5771a4f0cfe..869e3c55271 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -1,9 +1,7 @@ """Support to check for available updates.""" from datetime import timedelta from distutils.version import StrictVersion -import json import logging -import uuid import async_timeout from distro import linux_distribution # pylint: disable=import-error @@ -25,7 +23,6 @@ CONF_COMPONENT_REPORTING = "include_used_components" DOMAIN = "updater" UPDATER_URL = "https://updater.home-assistant.io/" -UPDATER_UUID_FILE = ".uuid" CONFIG_SCHEMA = vol.Schema( { @@ -52,26 +49,6 @@ class Updater: self.newest_version = newest_version -def _create_uuid(hass, filename=UPDATER_UUID_FILE): - """Create UUID and save it in a file.""" - with open(hass.config.path(filename), "w") as fptr: - _uuid = uuid.uuid4().hex - fptr.write(json.dumps({"uuid": _uuid})) - return _uuid - - -def _load_uuid(hass, filename=UPDATER_UUID_FILE): - """Load UUID from a file or return None.""" - try: - with open(hass.config.path(filename)) as fptr: - jsonf = json.loads(fptr.read()) - return uuid.UUID(jsonf["uuid"], version=4).hex - except (ValueError, AttributeError): - return None - except FileNotFoundError: - return _create_uuid(hass, filename) - - async def async_setup(hass, config): """Set up the updater component.""" if "dev" in current_version: @@ -80,7 +57,7 @@ async def async_setup(hass, config): conf = config.get(DOMAIN, {}) if conf.get(CONF_REPORTING): - huuid = await hass.async_add_job(_load_uuid, hass) + huuid = await hass.helpers.instance_id.async_get() else: huuid = None diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 139c9fe2b84..7b49f6bd363 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -7,7 +7,6 @@ import uuid import attr from homeassistant.core import callback -from homeassistant.loader import bind_hass from .singleton import singleton from .typing import HomeAssistantType @@ -367,7 +366,6 @@ class DeviceRegistry: self._async_update_device(dev_id, area_id=None) -@bind_hass @singleton(DATA_REGISTRY) async def async_get_registry(hass: HomeAssistantType) -> DeviceRegistry: """Create entity registry.""" diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index f276cc49850..0146852b93e 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -34,7 +34,6 @@ from homeassistant.const import ( ) from homeassistant.core import Event, callback, split_entity_id, valid_entity_id from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED -from homeassistant.loader import bind_hass from homeassistant.util import slugify from homeassistant.util.yaml import load_yaml @@ -491,7 +490,6 @@ class EntityRegistry: self.async_remove(entity_id) -@bind_hass @singleton(DATA_REGISTRY) async def async_get_registry(hass: HomeAssistantType) -> EntityRegistry: """Create entity registry.""" diff --git a/homeassistant/helpers/instance_id.py b/homeassistant/helpers/instance_id.py new file mode 100644 index 00000000000..1df039da47a --- /dev/null +++ b/homeassistant/helpers/instance_id.py @@ -0,0 +1,31 @@ +"""Helper to create a unique instance ID.""" +from typing import Dict, Optional +import uuid + +from homeassistant.core import HomeAssistant + +from . import singleton, storage + +DATA_KEY = "core.uuid" +DATA_VERSION = 1 + +LEGACY_UUID_FILE = ".uuid" + + +@singleton.singleton(DATA_KEY) +async def async_get(hass: HomeAssistant) -> str: + """Get unique ID for the hass instance.""" + store = storage.Store(hass, DATA_VERSION, DATA_KEY, True) + + data: Optional[Dict[str, str]] = await storage.async_migrator( # type: ignore + hass, hass.config.path(LEGACY_UUID_FILE), store, + ) + + if data is not None: + return data["uuid"] + + data = {"uuid": uuid.uuid4().hex} + + await store.async_save(data) + + return data["uuid"] diff --git a/homeassistant/helpers/singleton.py b/homeassistant/helpers/singleton.py index 8d0f6873c69..82b666be40a 100644 --- a/homeassistant/helpers/singleton.py +++ b/homeassistant/helpers/singleton.py @@ -4,6 +4,7 @@ import functools from typing import Awaitable, Callable, TypeVar, cast from homeassistant.core import HomeAssistant +from homeassistant.loader import bind_hass T = TypeVar("T") @@ -19,6 +20,7 @@ def singleton(data_key: str) -> Callable[[FUNC], FUNC]: def wrapper(func: FUNC) -> FUNC: """Wrap a function with caching logic.""" + @bind_hass @functools.wraps(func) async def wrapped(hass: HomeAssistant) -> T: obj_or_evt = hass.data.get(data_key) diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index 00df728fb36..b5ac942bf2f 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -20,29 +20,32 @@ _LOGGER = logging.getLogger(__name__) @bind_hass async def async_migrator( - hass, - old_path, - store, - *, - old_conf_load_func=json_util.load_json, - old_conf_migrate_func=None, + hass, old_path, store, *, old_conf_load_func=None, old_conf_migrate_func=None, ): """Migrate old data to a store and then load data. async def old_conf_migrate_func(old_data) """ + store_data = await store.async_load() + + # If we already have store data we have already migrated in the past. + if store_data is not None: + return store_data def load_old_config(): """Load old config.""" if not os.path.isfile(old_path): return None - return old_conf_load_func(old_path) + if old_conf_load_func is not None: + return old_conf_load_func(old_path) + + return json_util.load_json(old_path) config = await hass.async_add_executor_job(load_old_config) if config is None: - return await store.async_load() + return None if old_conf_migrate_func is not None: config = await old_conf_migrate_func(config) diff --git a/tests/components/updater/test_init.py b/tests/components/updater/test_init.py index 0d710907f59..604a71f08b4 100644 --- a/tests/components/updater/test_init.py +++ b/tests/components/updater/test_init.py @@ -37,7 +37,7 @@ def mock_get_newest_version_fixture(): @pytest.fixture(name="mock_get_uuid", autouse=True) def mock_get_uuid_fixture(): """Fixture to mock get_uuid.""" - with patch("homeassistant.components.updater._load_uuid") as mock: + with patch("homeassistant.helpers.instance_id.async_get") as mock: yield mock diff --git a/tests/helpers/test_instance_id.py b/tests/helpers/test_instance_id.py new file mode 100644 index 00000000000..36e87b31a43 --- /dev/null +++ b/tests/helpers/test_instance_id.py @@ -0,0 +1,26 @@ +"""Tests for instance ID helper.""" +from tests.async_mock import patch + + +async def test_get_id_empty(hass, hass_storage): + """Get unique ID.""" + uuid = await hass.helpers.instance_id.async_get() + assert uuid is not None + # Assert it's stored + assert hass_storage["core.uuid"]["data"]["uuid"] == uuid + + +async def test_get_id_migrate(hass, hass_storage): + """Migrate existing file.""" + with patch( + "homeassistant.util.json.load_json", return_value={"uuid": "1234"} + ), patch("os.path.isfile", return_value=True), patch("os.remove") as mock_remove: + uuid = await hass.helpers.instance_id.async_get() + + assert uuid == "1234" + + # Assert it's stored + assert hass_storage["core.uuid"]["data"]["uuid"] == uuid + + # assert old deleted + assert len(mock_remove.mock_calls) == 1