def __init__(self, hass): """Initialize the dashboards collection.""" super().__init__( storage.Store(hass, DASHBOARDS_STORAGE_VERSION, DASHBOARDS_STORAGE_KEY), _LOGGER, )
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up configured zones as well as Home Assistant zone if necessary.""" component = entity_component.EntityComponent(_LOGGER, DOMAIN, hass) id_manager = collection.IDManager() yaml_collection = collection.IDLessCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager) collection.sync_entity_lifecycle(hass, DOMAIN, DOMAIN, component, yaml_collection, Zone.from_yaml) storage_collection = ZoneStorageCollection( storage.Store(hass, STORAGE_VERSION, STORAGE_KEY), logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) collection.sync_entity_lifecycle(hass, DOMAIN, DOMAIN, component, storage_collection, Zone) if config[DOMAIN]: # AIS dom config can be empty if config[DOMAIN] != [{}]: await yaml_collection.async_load(config[DOMAIN]) await storage_collection.async_load() collection.StorageCollectionWebsocket(storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS).async_setup(hass) async def reload_service_handler(service_call: ServiceCall) -> None: """Remove all zones and load new ones from config.""" conf = await component.async_prepare_reload(skip_reset=True) if conf is None: return await yaml_collection.async_load(conf[DOMAIN]) service.async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=RELOAD_SERVICE_SCHEMA, ) if component.get_entity("zone.home"): return True home_zone = Zone(_home_conf(hass)) home_zone.entity_id = ENTITY_ID_HOME await component.async_add_entities([home_zone]) async def core_config_updated(_: Event) -> None: """Handle core config updated.""" await home_zone.async_update_config(_home_conf(hass)) hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, core_config_updated) hass.data[DOMAIN] = storage_collection return True
async def test_removing_while_delay_in_progress(tmpdir): """Test removing while delay in progress.""" loop = asyncio.get_event_loop() hass = await async_test_home_assistant(loop) test_dir = await hass.async_add_executor_job(tmpdir.mkdir, "storage") with patch.object(storage, "STORAGE_DIR", test_dir): real_store = storage.Store(hass, 1, "remove_me") await real_store.async_save({"delay": "no"}) assert await hass.async_add_executor_job(os.path.exists, real_store.path) real_store.async_delay_save(lambda: {"delay": "yes"}, 1) await real_store.async_remove() assert not await hass.async_add_executor_job(os.path.exists, real_store.path) async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=1)) await hass.async_block_till_done() assert not await hass.async_add_executor_job(os.path.exists, real_store.path) await hass.async_stop()
def __init__(self, hass: HomeAssistant, ll_config: LovelaceConfig): """Initialize the storage collection.""" super().__init__( storage.Store(hass, RESOURCES_STORAGE_VERSION, RESOURCE_STORAGE_KEY), _LOGGER, ) self.ll_config = ll_config
async def async_setup(self): """Set up and migrate to storage.""" self.store = storage.Store(self.hass, DATA_VERSION, DATA_KEY) self.numbers = ( await storage.async_migrator( self.hass, self.hass.config.path(NUMBERS_FILE), self.store ) or {} )
async def test_not_delayed_saving_while_stopping(hass, hass_storage): """Test delayed saves don't write after the stop event has fired.""" store = storage.Store(hass, MOCK_VERSION, MOCK_KEY) hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() hass.state = CoreState.stopping store.async_delay_save(lambda: MOCK_DATA, 1) async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=2)) await hass.async_block_till_done() assert store.key not in hass_storage
async def test_saving_load_round_trip(tmpdir): """Test saving and loading round trip.""" loop = asyncio.get_running_loop() hass = await async_test_home_assistant(loop) hass.config.config_dir = await hass.async_add_executor_job( tmpdir.mkdir, "temp_storage") class NamedTupleSubclass(NamedTuple): """A NamedTuple subclass.""" name: str nts = NamedTupleSubclass("a") data = { "named_tuple_subclass": nts, "rgb_color": RGBColor(255, 255, 0), "set": {1, 2, 3}, "list": [1, 2, 3], "tuple": (1, 2, 3), "dict_with_int": { 1: 1, 2: 2 }, "dict_with_named_tuple": { 1: nts, 2: nts }, } store = storage.Store(hass, MOCK_VERSION_2, MOCK_KEY, minor_version=MOCK_MINOR_VERSION_1) await store.async_save(data) load = await store.async_load() assert load == { "dict_with_int": { "1": 1, "2": 2 }, "dict_with_named_tuple": { "1": ["a"], "2": ["a"] }, "list": [1, 2, 3], "named_tuple_subclass": ["a"], "rgb_color": [255, 255, 0], "set": [1, 2, 3], "tuple": [1, 2, 3], } await hass.async_stop(force=True)
async def test_custom_encoder(hass): """Test we can save and load data.""" class JSONEncoder(json.JSONEncoder): """Mock JSON encoder.""" def default(self, o): """Mock JSON encode method.""" return "9" store = storage.Store(hass, MOCK_VERSION, MOCK_KEY, encoder=JSONEncoder) await store.async_save(Mock()) data = await store.async_load() assert data == "9"
async def async_setup(self) -> None: """Set up tracking and migrate to storage.""" hass = self.hass self.store = storage.Store(hass, DATA_VERSION, DATA_KEY) # type: ignore[arg-type] numbers_path = hass.config.path(NUMBERS_FILE) self.numbers = (await storage.async_migrator(hass, numbers_path, self.store) or {}) async_track_state_added_domain(hass, self.track_domains, self._clear_exposed_cache) async_track_state_removed_domain(hass, self.track_domains, self._clear_exposed_cache)
def __init__(self, hass, config): """Initialize Lovelace config based on storage helper.""" if config is None: url_path = None storage_key = CONFIG_STORAGE_KEY_DEFAULT else: url_path = config[CONF_URL_PATH] storage_key = CONFIG_STORAGE_KEY.format(config["id"]) super().__init__(hass, url_path, config) self._store = storage.Store(hass, CONFIG_STORAGE_VERSION, storage_key) self._data = None
async def test_saving_on_stop(hass, hass_storage): """Test delayed saves trigger when we quit Home Assistant.""" store = storage.Store(hass, MOCK_VERSION, MOCK_KEY) store.async_delay_save(lambda: MOCK_DATA, 1) assert store.key not in hass_storage hass.bus.async_fire(EVENT_HOMEASSISTANT_FINAL_WRITE) await hass.async_block_till_done() assert hass_storage[store.key] == { "version": MOCK_VERSION, "key": MOCK_KEY, "data": MOCK_DATA, }
async def test_saving_on_stop(hass, hass_storage): """Test delayed saves trigger when we quit Home Assistant.""" store = storage.Store(hass, MOCK_VERSION, MOCK_KEY) await store.async_save(MOCK_DATA, delay=1) assert store.key not in hass_storage hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() assert hass_storage[store.key] == { 'version': MOCK_VERSION, 'key': MOCK_KEY, 'data': MOCK_DATA, }
async def start_http_server_and_save_config(hass: HomeAssistant, conf: dict, server: HomeAssistantHTTP) -> None: """Startup the http server and save the config.""" await server.start() # type: ignore # If we are set up successful, we store the HTTP settings for safe mode. store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) if CONF_TRUSTED_PROXIES in conf: conf[CONF_TRUSTED_PROXIES] = [ str(ip.network_address) for ip in conf[CONF_TRUSTED_PROXIES] ] await store.async_save(conf)
async def test_not_delayed_saving_after_stopping(hass, hass_storage): """Test delayed saves don't write after stop if issued before stopping Home Assistant.""" store = storage.Store(hass, MOCK_VERSION, MOCK_KEY) store.async_delay_save(lambda: MOCK_DATA, 10) assert store.key not in hass_storage hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) hass.state = CoreState.stopping await hass.async_block_till_done() assert store.key not in hass_storage async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=15)) await hass.async_block_till_done() assert store.key not in hass_storage
async def start_http_server_and_save_config(hass: HomeAssistant, conf: dict, server: HomeAssistantHTTP) -> None: """Startup the http server and save the config.""" await server.start() # If we are set up successful, we store the HTTP settings for safe mode. store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) if CONF_TRUSTED_PROXIES in conf: conf[CONF_TRUSTED_PROXIES] = [ str(cast(Union[IPv4Network, IPv6Network], ip).network_address) for ip in conf[CONF_TRUSTED_PROXIES] ] store.async_delay_save(lambda: conf, SAVE_DELAY)
async def start_server(event): """Start the server.""" hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_server) await server.start() # If we are set up successful, we store the HTTP settings for safe mode. store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) if CONF_TRUSTED_PROXIES in conf: conf_to_save = dict(conf) conf_to_save[CONF_TRUSTED_PROXIES] = [ str(ip.network_address) for ip in conf_to_save[CONF_TRUSTED_PROXIES] ] else: conf_to_save = conf await store.async_save(conf_to_save)
async def async_setup_platform(hass, config, add_devices, discovery_info=None): """Configure Heatzy API using Home Assistant configuration and fetch all Heatzy devices.""" # retrieve platform config username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) session = aiohttp_client.async_get_clientsession(hass) store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) authenticator = HeatzyAuthenticator(session, store, username, password) api = HeatzyAPI(session, authenticator) # fetch configured Heatzy devices devices = await api.async_get_devices() # add all Heatzy devices with HA implementation to home assistant add_devices(filter(None.__ne__, map(setup_heatzy_device(api), devices))) return True
async def test_saving_on_final_write(hass, hass_storage): """Test delayed saves trigger when we quit Home Assistant.""" store = storage.Store(hass, MOCK_VERSION, MOCK_KEY) store.async_delay_save(lambda: MOCK_DATA, 5) assert store.key not in hass_storage hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) hass.state = CoreState.stopping await hass.async_block_till_done() async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=10)) await hass.async_block_till_done() assert store.key not in hass_storage hass.bus.async_fire(EVENT_HOMEASSISTANT_FINAL_WRITE) await hass.async_block_till_done() assert hass_storage[store.key] == { "version": MOCK_VERSION, "key": MOCK_KEY, "data": MOCK_DATA, }
async def test_storage_collection(hass): """Test storage collection.""" store = storage.Store(hass, 1, "test-data") await store.async_save( { "items": [ {"id": "mock-1", "name": "Mock 1", "data": 1}, {"id": "mock-2", "name": "Mock 2", "data": 2}, ] } ) id_manager = collection.IDManager() coll = MockStorageCollection(store, LOGGER, id_manager) changes = track_changes(coll) await coll.async_load() assert id_manager.has_id("mock-1") assert id_manager.has_id("mock-2") assert len(changes) == 2 assert changes[0] == ( collection.CHANGE_ADDED, "mock-1", {"id": "mock-1", "name": "Mock 1", "data": 1}, ) assert changes[1] == ( collection.CHANGE_ADDED, "mock-2", {"id": "mock-2", "name": "Mock 2", "data": 2}, ) item = await coll.async_create_item({"name": "Mock 3"}) assert item["id"] == "mock_3" assert len(changes) == 3 assert changes[2] == ( collection.CHANGE_ADDED, "mock_3", {"id": "mock_3", "name": "Mock 3"}, ) updated_item = await coll.async_update_item("mock-2", {"name": "Mock 2 updated"}) assert id_manager.has_id("mock-2") assert updated_item == {"id": "mock-2", "name": "Mock 2 updated", "data": 2} assert len(changes) == 4 assert changes[3] == (collection.CHANGE_UPDATED, "mock-2", updated_item) with pytest.raises(ValueError): await coll.async_update_item("mock-2", {"id": "mock-2-updated"}) assert id_manager.has_id("mock-2") assert not id_manager.has_id("mock-2-updated") assert len(changes) == 4 await flush_store(store) assert await storage.Store(hass, 1, "test-data").async_load() == { "items": [ {"id": "mock-1", "name": "Mock 1", "data": 1}, {"id": "mock-2", "name": "Mock 2 updated", "data": 2}, {"id": "mock_3", "name": "Mock 3"}, ] }
async def async_get_last_config(hass: HomeAssistant) -> dict | None: """Return the last known working config.""" store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) return cast(Optional[dict], await store.async_load())
async def async_setup_entry(hass, entry): """Set up Almond config entry.""" websession = aiohttp_client.async_get_clientsession(hass) if entry.data["type"] == TYPE_LOCAL: auth = AlmondLocalAuth(entry.data["host"], websession) else: # OAuth2 implementation = await config_entry_oauth2_flow.async_get_config_entry_implementation( hass, entry) oauth_session = config_entry_oauth2_flow.OAuth2Session( hass, entry, implementation) auth = AlmondOAuth(entry.data["host"], websession, oauth_session) api = WebAlmondAPI(auth) agent = AlmondAgent(hass, api, entry) # Hass.io does its own configuration of Almond. if entry.data.get("is_hassio") or entry.data["type"] != TYPE_LOCAL: conversation.async_set_agent(hass, agent) return True # Configure Almond to connect to Home Assistant store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) data = await store.async_load() if data is None: data = {} user = None if "almond_user" in data: user = await hass.auth.async_get_user(data["almond_user"]) if user is None: user = await hass.auth.async_create_system_user( "Almond", [GROUP_ID_ADMIN]) data["almond_user"] = user.id await store.async_save(data) refresh_token = await hass.auth.async_create_refresh_token( user, # Almond will be fine as long as we restart once every 5 years access_token_expiration=timedelta(days=365 * 5), ) # Create long lived access token access_token = hass.auth.async_create_access_token(refresh_token) # Store token in Almond try: with async_timeout.timeout(10): await api.async_create_device({ "kind": "io.home-assistant", "hassUrl": hass.config.api.base_url, "accessToken": access_token, "refreshToken": "", # 5 years from now in ms. "accessTokenExpires": (time.time() + 60 * 60 * 24 * 365 * 5) * 1000, }) except (asyncio.TimeoutError, ClientError) as err: if isinstance(err, asyncio.TimeoutError): msg = "Request timeout" else: msg = err _LOGGER.warning("Unable to configure Almond: %s", msg) await hass.auth.async_remove_refresh_token(refresh_token) raise ConfigEntryNotReady # Clear all other refresh tokens for token in list(user.refresh_tokens.values()): if token.id != refresh_token.id: await hass.auth.async_remove_refresh_token(token) conversation.async_set_agent(hass, agent) return True
def store(hass): """Fixture of a store that prevents writing on Home Assistant stop.""" yield storage.Store(hass, MOCK_VERSION, MOCK_KEY)
async def test_not_saving_while_stopping(hass, hass_storage): """Test saves don't write when stopping Home Assistant.""" store = storage.Store(hass, MOCK_VERSION, MOCK_KEY) hass.state = CoreState.stopping await store.async_save(MOCK_DATA) assert store.key not in hass_storage
def store(hass): """Fixture of a store that prevents writing on Safegate Pro stop.""" yield storage.Store(hass, MOCK_VERSION, MOCK_KEY)
async def _configure_almond_for_ha(hass: HomeAssistant, entry: config_entries.ConfigEntry, api: WebAlmondAPI): """Configure Almond to connect to HA.""" try: if entry.data["type"] == TYPE_OAUTH2: # If we're connecting over OAuth2, we will only set up connection # with Home Assistant if we're remotely accessible. hass_url = network.get_url(hass, allow_internal=False, prefer_cloud=True) else: hass_url = network.get_url(hass) except network.NoURLAvailableError: # If no URL is available, we're not going to configure Almond to connect to HA. return _LOGGER.debug("Configuring Almond to connect to Home Assistant at %s", hass_url) store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) data = await store.async_load() if data is None: data = {} user = None if "almond_user" in data: user = await hass.auth.async_get_user(data["almond_user"]) if user is None: user = await hass.auth.async_create_system_user( "Almond", [GROUP_ID_ADMIN]) data["almond_user"] = user.id await store.async_save(data) refresh_token = await hass.auth.async_create_refresh_token( user, # Almond will be fine as long as we restart once every 5 years access_token_expiration=timedelta(days=365 * 5), ) # Create long lived access token access_token = hass.auth.async_create_access_token(refresh_token) # Store token in Almond try: with async_timeout.timeout(30): await api.async_create_device({ "kind": "io.home-assistant", "hassUrl": hass_url, "accessToken": access_token, "refreshToken": "", # 5 years from now in ms. "accessTokenExpires": (time.time() + 60 * 60 * 24 * 365 * 5) * 1000, }) except (asyncio.TimeoutError, ClientError) as err: if isinstance(err, asyncio.TimeoutError): msg = "Request timeout" else: msg = err _LOGGER.warning("Unable to configure Almond: %s", msg) await hass.auth.async_remove_refresh_token(refresh_token) raise ConfigEntryNotReady # Clear all other refresh tokens for token in list(user.refresh_tokens.values()): if token.id != refresh_token.id: await hass.auth.async_remove_refresh_token(token)
async def async_setup(hass: HomeAssistant, config: Dict) -> bool: """Set up configured zones as well as Home Assistant zone if necessary.""" component = entity_component.EntityComponent(_LOGGER, DOMAIN, hass) id_manager = collection.IDManager() yaml_collection = collection.IDLessCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager ) collection.attach_entity_component_collection( component, yaml_collection, lambda conf: Zone(conf, False) ) storage_collection = ZoneStorageCollection( storage.Store(hass, STORAGE_VERSION, STORAGE_KEY), logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) collection.attach_entity_component_collection( component, storage_collection, lambda conf: Zone(conf, True) ) if config[DOMAIN]: # AIS dom config can be empty if config[DOMAIN] != [{}]: await yaml_collection.async_load(config[DOMAIN]) await storage_collection.async_load() collection.StorageCollectionWebsocket( storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS ).async_setup(hass) async def _collection_changed(change_type: str, item_id: str, config: Dict) -> None: """Handle a collection change: clean up entity registry on removals.""" if change_type != collection.CHANGE_REMOVED: return ent_reg = await entity_registry.async_get_registry(hass) ent_reg.async_remove( cast(str, ent_reg.async_get_entity_id(DOMAIN, DOMAIN, item_id)) ) storage_collection.async_add_listener(_collection_changed) async def reload_service_handler(service_call: ServiceCall) -> None: """Remove all zones and load new ones from config.""" conf = await component.async_prepare_reload(skip_reset=True) if conf is None: return await yaml_collection.async_load(conf[DOMAIN]) service.async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=RELOAD_SERVICE_SCHEMA, ) if component.get_entity("zone.home"): return True home_zone = Zone( _home_conf(hass), True, ) home_zone.entity_id = ENTITY_ID_HOME await component.async_add_entities([home_zone]) async def core_config_updated(_: Event) -> None: """Handle core config updated.""" await home_zone.async_update_config(_home_conf(hass)) hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, core_config_updated) hass.data[DOMAIN] = storage_collection return True
def store_v_2_1(hass): """Fixture of a store that prevents writing on Home Assistant stop.""" return storage.Store(hass, MOCK_VERSION_2, MOCK_KEY, minor_version=MOCK_MINOR_VERSION_1)
def __init__(self, hass: HomeAssistant) -> None: """Initialize energy manager.""" self._hass = hass self._store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) self.data: EnergyPreferences | None = None self._update_listeners: list[Callable[[], Awaitable]] = []
async def test_storage_collection_websocket(hass, hass_ws_client): """Test exposing a storage collection via websockets.""" store = storage.Store(hass, 1, "test-data") coll = MockStorageCollection(store, LOGGER) changes = track_changes(coll) collection.StorageCollectionWebsocket( coll, "test_item/collection", "test_item", {vol.Required("name"): str, vol.Required("immutable_string"): str}, {vol.Optional("name"): str}, ).async_setup(hass) client = await hass_ws_client(hass) # Create invalid await client.send_json( { "id": 1, "type": "test_item/collection/create", "name": 1, # Forgot to add immutable_string } ) response = await client.receive_json() assert not response["success"] assert response["error"]["code"] == "invalid_format" assert len(changes) == 0 # Create await client.send_json( { "id": 2, "type": "test_item/collection/create", "name": "Initial Name", "immutable_string": "no-changes", } ) response = await client.receive_json() assert response["success"] assert response["result"] == { "id": "initial_name", "name": "Initial Name", "immutable_string": "no-changes", } assert len(changes) == 1 assert changes[0] == (collection.CHANGE_ADDED, "initial_name", response["result"]) # List await client.send_json({"id": 3, "type": "test_item/collection/list"}) response = await client.receive_json() assert response["success"] assert response["result"] == [ { "id": "initial_name", "name": "Initial Name", "immutable_string": "no-changes", } ] assert len(changes) == 1 # Update invalid data await client.send_json( { "id": 4, "type": "test_item/collection/update", "test_item_id": "initial_name", "immutable_string": "no-changes", } ) response = await client.receive_json() assert not response["success"] assert response["error"]["code"] == "invalid_format" assert len(changes) == 1 # Update invalid item await client.send_json( { "id": 5, "type": "test_item/collection/update", "test_item_id": "non-existing", "name": "Updated name", } ) response = await client.receive_json() assert not response["success"] assert response["error"]["code"] == "not_found" assert len(changes) == 1 # Update await client.send_json( { "id": 6, "type": "test_item/collection/update", "test_item_id": "initial_name", "name": "Updated name", } ) response = await client.receive_json() assert response["success"] assert response["result"] == { "id": "initial_name", "name": "Updated name", "immutable_string": "no-changes", } assert len(changes) == 2 assert changes[1] == (collection.CHANGE_UPDATED, "initial_name", response["result"]) # Delete invalid ID await client.send_json( {"id": 7, "type": "test_item/collection/update", "test_item_id": "non-existing"} ) response = await client.receive_json() assert not response["success"] assert response["error"]["code"] == "not_found" assert len(changes) == 2 # Delete await client.send_json( {"id": 8, "type": "test_item/collection/delete", "test_item_id": "initial_name"} ) response = await client.receive_json() assert response["success"] assert len(changes) == 3 assert changes[2] == ( collection.CHANGE_REMOVED, "initial_name", { "id": "initial_name", "immutable_string": "no-changes", "name": "Updated name", }, )