Extract instance ID helper from updater (#35043)
This commit is contained in:
parent
b3e637ca7a
commit
e54e9279e3
8 changed files with 72 additions and 37 deletions
|
@ -1,9 +1,7 @@
|
||||||
"""Support to check for available updates."""
|
"""Support to check for available updates."""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from distutils.version import StrictVersion
|
from distutils.version import StrictVersion
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
|
||||||
|
|
||||||
import async_timeout
|
import async_timeout
|
||||||
from distro import linux_distribution # pylint: disable=import-error
|
from distro import linux_distribution # pylint: disable=import-error
|
||||||
|
@ -25,7 +23,6 @@ CONF_COMPONENT_REPORTING = "include_used_components"
|
||||||
DOMAIN = "updater"
|
DOMAIN = "updater"
|
||||||
|
|
||||||
UPDATER_URL = "https://updater.home-assistant.io/"
|
UPDATER_URL = "https://updater.home-assistant.io/"
|
||||||
UPDATER_UUID_FILE = ".uuid"
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
|
@ -52,26 +49,6 @@ class Updater:
|
||||||
self.newest_version = newest_version
|
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):
|
async def async_setup(hass, config):
|
||||||
"""Set up the updater component."""
|
"""Set up the updater component."""
|
||||||
if "dev" in current_version:
|
if "dev" in current_version:
|
||||||
|
@ -80,7 +57,7 @@ async def async_setup(hass, config):
|
||||||
|
|
||||||
conf = config.get(DOMAIN, {})
|
conf = config.get(DOMAIN, {})
|
||||||
if conf.get(CONF_REPORTING):
|
if conf.get(CONF_REPORTING):
|
||||||
huuid = await hass.async_add_job(_load_uuid, hass)
|
huuid = await hass.helpers.instance_id.async_get()
|
||||||
else:
|
else:
|
||||||
huuid = None
|
huuid = None
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import uuid
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.loader import bind_hass
|
|
||||||
|
|
||||||
from .singleton import singleton
|
from .singleton import singleton
|
||||||
from .typing import HomeAssistantType
|
from .typing import HomeAssistantType
|
||||||
|
@ -367,7 +366,6 @@ class DeviceRegistry:
|
||||||
self._async_update_device(dev_id, area_id=None)
|
self._async_update_device(dev_id, area_id=None)
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
|
||||||
@singleton(DATA_REGISTRY)
|
@singleton(DATA_REGISTRY)
|
||||||
async def async_get_registry(hass: HomeAssistantType) -> DeviceRegistry:
|
async def async_get_registry(hass: HomeAssistantType) -> DeviceRegistry:
|
||||||
"""Create entity registry."""
|
"""Create entity registry."""
|
||||||
|
|
|
@ -34,7 +34,6 @@ from homeassistant.const import (
|
||||||
)
|
)
|
||||||
from homeassistant.core import Event, callback, split_entity_id, valid_entity_id
|
from homeassistant.core import Event, callback, split_entity_id, valid_entity_id
|
||||||
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
|
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
|
||||||
from homeassistant.loader import bind_hass
|
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
from homeassistant.util.yaml import load_yaml
|
from homeassistant.util.yaml import load_yaml
|
||||||
|
|
||||||
|
@ -491,7 +490,6 @@ class EntityRegistry:
|
||||||
self.async_remove(entity_id)
|
self.async_remove(entity_id)
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
|
||||||
@singleton(DATA_REGISTRY)
|
@singleton(DATA_REGISTRY)
|
||||||
async def async_get_registry(hass: HomeAssistantType) -> EntityRegistry:
|
async def async_get_registry(hass: HomeAssistantType) -> EntityRegistry:
|
||||||
"""Create entity registry."""
|
"""Create entity registry."""
|
||||||
|
|
31
homeassistant/helpers/instance_id.py
Normal file
31
homeassistant/helpers/instance_id.py
Normal file
|
@ -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"]
|
|
@ -4,6 +4,7 @@ import functools
|
||||||
from typing import Awaitable, Callable, TypeVar, cast
|
from typing import Awaitable, Callable, TypeVar, cast
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.loader import bind_hass
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ def singleton(data_key: str) -> Callable[[FUNC], FUNC]:
|
||||||
def wrapper(func: FUNC) -> FUNC:
|
def wrapper(func: FUNC) -> FUNC:
|
||||||
"""Wrap a function with caching logic."""
|
"""Wrap a function with caching logic."""
|
||||||
|
|
||||||
|
@bind_hass
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
async def wrapped(hass: HomeAssistant) -> T:
|
async def wrapped(hass: HomeAssistant) -> T:
|
||||||
obj_or_evt = hass.data.get(data_key)
|
obj_or_evt = hass.data.get(data_key)
|
||||||
|
|
|
@ -20,29 +20,32 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
async def async_migrator(
|
async def async_migrator(
|
||||||
hass,
|
hass, old_path, store, *, old_conf_load_func=None, old_conf_migrate_func=None,
|
||||||
old_path,
|
|
||||||
store,
|
|
||||||
*,
|
|
||||||
old_conf_load_func=json_util.load_json,
|
|
||||||
old_conf_migrate_func=None,
|
|
||||||
):
|
):
|
||||||
"""Migrate old data to a store and then load data.
|
"""Migrate old data to a store and then load data.
|
||||||
|
|
||||||
async def old_conf_migrate_func(old_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():
|
def load_old_config():
|
||||||
"""Load old config."""
|
"""Load old config."""
|
||||||
if not os.path.isfile(old_path):
|
if not os.path.isfile(old_path):
|
||||||
return None
|
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)
|
config = await hass.async_add_executor_job(load_old_config)
|
||||||
|
|
||||||
if config is None:
|
if config is None:
|
||||||
return await store.async_load()
|
return None
|
||||||
|
|
||||||
if old_conf_migrate_func is not None:
|
if old_conf_migrate_func is not None:
|
||||||
config = await old_conf_migrate_func(config)
|
config = await old_conf_migrate_func(config)
|
||||||
|
|
|
@ -37,7 +37,7 @@ def mock_get_newest_version_fixture():
|
||||||
@pytest.fixture(name="mock_get_uuid", autouse=True)
|
@pytest.fixture(name="mock_get_uuid", autouse=True)
|
||||||
def mock_get_uuid_fixture():
|
def mock_get_uuid_fixture():
|
||||||
"""Fixture to mock get_uuid."""
|
"""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
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
|
|
26
tests/helpers/test_instance_id.py
Normal file
26
tests/helpers/test_instance_id.py
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue