Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate file integration to config entry (#116861)
* File integration entry setup * Import to entry and tests * Add config flow * Exception handling and tests * Add config flow tests * Add issue for micration and deprecation * Check whole entry data for uniqueness * Revert changes change new notify entity * Follow up on code review * Keep name service option * Also keep sensor name * Make name unique * Follow up comment * No default timestamp needed * Remove default name as it is already set * Use links
- Loading branch information
Showing
13 changed files
with
866 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,101 @@ | ||
"""The file component.""" | ||
|
||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry | ||
from homeassistant.const import CONF_FILE_PATH, CONF_PLATFORM, Platform | ||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant | ||
from homeassistant.exceptions import ConfigEntryNotReady | ||
from homeassistant.helpers import ( | ||
config_validation as cv, | ||
discovery, | ||
issue_registry as ir, | ||
) | ||
from homeassistant.helpers.typing import ConfigType | ||
|
||
from .const import DOMAIN | ||
|
||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) | ||
|
||
PLATFORMS = [Platform.SENSOR] | ||
|
||
YAML_PLATFORMS = [Platform.NOTIFY, Platform.SENSOR] | ||
|
||
|
||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: | ||
"""Set up the file integration.""" | ||
|
||
if hass.config_entries.async_entries(DOMAIN): | ||
# We skip import in case we already have config entries | ||
return True | ||
# The YAML config was imported with HA Core 2024.6.0 and will be removed with | ||
# HA Core 2024.12 | ||
ir.async_create_issue( | ||
hass, | ||
HOMEASSISTANT_DOMAIN, | ||
f"deprecated_yaml_{DOMAIN}", | ||
breaks_in_ha_version="2024.12.0", | ||
is_fixable=False, | ||
issue_domain=DOMAIN, | ||
learn_more_url="https://www.home-assistant.io/integrations/file/", | ||
severity=ir.IssueSeverity.WARNING, | ||
translation_key="deprecated_yaml", | ||
translation_placeholders={ | ||
"domain": DOMAIN, | ||
"integration_title": "File", | ||
}, | ||
) | ||
|
||
# Import the YAML config into separate config entries | ||
for domain, items in config.items(): | ||
for item in items: | ||
if item[CONF_PLATFORM] == DOMAIN: | ||
item[CONF_PLATFORM] = domain | ||
hass.async_create_task( | ||
hass.config_entries.flow.async_init( | ||
DOMAIN, | ||
context={"source": SOURCE_IMPORT}, | ||
data=item, | ||
) | ||
) | ||
|
||
return True | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Set up a file component entry.""" | ||
config = dict(entry.data) | ||
filepath: str = config[CONF_FILE_PATH] | ||
if filepath and not await hass.async_add_executor_job( | ||
hass.config.is_allowed_path, filepath | ||
): | ||
raise ConfigEntryNotReady( | ||
translation_domain=DOMAIN, | ||
translation_key="dir_not_allowed", | ||
translation_placeholders={"filename": filepath}, | ||
) | ||
|
||
if entry.data[CONF_PLATFORM] in PLATFORMS: | ||
await hass.config_entries.async_forward_entry_setups( | ||
entry, [Platform(entry.data[CONF_PLATFORM])] | ||
) | ||
else: | ||
# The notify platform is not yet set up as entry, so | ||
# forward setup config through discovery to ensure setup notify service. | ||
# This is needed as long as the legacy service is not migrated | ||
hass.async_create_task( | ||
discovery.async_load_platform( | ||
hass, | ||
Platform.NOTIFY, | ||
DOMAIN, | ||
config, | ||
{}, | ||
) | ||
) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
return await hass.config_entries.async_unload_platforms( | ||
entry, [entry.data[CONF_PLATFORM]] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
"""Config flow for file integration.""" | ||
|
||
import os | ||
from typing import Any | ||
|
||
import voluptuous as vol | ||
|
||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult | ||
from homeassistant.const import ( | ||
CONF_FILE_PATH, | ||
CONF_FILENAME, | ||
CONF_NAME, | ||
CONF_PLATFORM, | ||
CONF_UNIT_OF_MEASUREMENT, | ||
CONF_VALUE_TEMPLATE, | ||
Platform, | ||
) | ||
from homeassistant.helpers.selector import ( | ||
BooleanSelector, | ||
BooleanSelectorConfig, | ||
TemplateSelector, | ||
TemplateSelectorConfig, | ||
TextSelector, | ||
TextSelectorConfig, | ||
TextSelectorType, | ||
) | ||
|
||
from .const import CONF_TIMESTAMP, DEFAULT_NAME, DOMAIN | ||
|
||
BOOLEAN_SELECTOR = BooleanSelector(BooleanSelectorConfig()) | ||
TEMPLATE_SELECTOR = TemplateSelector(TemplateSelectorConfig()) | ||
TEXT_SELECTOR = TextSelector(TextSelectorConfig(type=TextSelectorType.TEXT)) | ||
|
||
FILE_SENSOR_SCHEMA = vol.Schema( | ||
{ | ||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): TEXT_SELECTOR, | ||
vol.Required(CONF_FILE_PATH): TEXT_SELECTOR, | ||
vol.Optional(CONF_VALUE_TEMPLATE): TEMPLATE_SELECTOR, | ||
vol.Optional(CONF_UNIT_OF_MEASUREMENT): TEXT_SELECTOR, | ||
} | ||
) | ||
|
||
FILE_NOTIFY_SCHEMA = vol.Schema( | ||
{ | ||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): TEXT_SELECTOR, | ||
vol.Required(CONF_FILE_PATH): TEXT_SELECTOR, | ||
vol.Optional(CONF_TIMESTAMP, default=False): BOOLEAN_SELECTOR, | ||
} | ||
) | ||
|
||
|
||
class FileConfigFlowHandler(ConfigFlow, domain=DOMAIN): | ||
"""Handle a file config flow.""" | ||
|
||
VERSION = 1 | ||
|
||
async def validate_file_path(self, file_path: str) -> bool: | ||
"""Ensure the file path is valid.""" | ||
return await self.hass.async_add_executor_job( | ||
self.hass.config.is_allowed_path, file_path | ||
) | ||
|
||
async def async_step_user( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> ConfigFlowResult: | ||
"""Handle a flow initiated by the user.""" | ||
return self.async_show_menu( | ||
step_id="user", | ||
menu_options=["notify", "sensor"], | ||
) | ||
|
||
async def async_step_notify( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> ConfigFlowResult: | ||
"""Handle file notifier config flow.""" | ||
errors: dict[str, str] = {} | ||
if user_input: | ||
user_input[CONF_PLATFORM] = "notify" | ||
self._async_abort_entries_match(user_input) | ||
if not await self.validate_file_path(user_input[CONF_FILE_PATH]): | ||
errors[CONF_FILE_PATH] = "not_allowed" | ||
else: | ||
name: str = user_input.get(CONF_NAME, DEFAULT_NAME) | ||
title = f"{name} [{user_input[CONF_FILE_PATH]}]" | ||
return self.async_create_entry(data=user_input, title=title) | ||
|
||
return self.async_show_form( | ||
step_id="notify", data_schema=FILE_NOTIFY_SCHEMA, errors=errors | ||
) | ||
|
||
async def async_step_sensor( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> ConfigFlowResult: | ||
"""Handle file sensor config flow.""" | ||
errors: dict[str, str] = {} | ||
if user_input: | ||
user_input[CONF_PLATFORM] = "sensor" | ||
self._async_abort_entries_match(user_input) | ||
if not await self.validate_file_path(user_input[CONF_FILE_PATH]): | ||
errors[CONF_FILE_PATH] = "not_allowed" | ||
else: | ||
name: str = user_input.get(CONF_NAME, DEFAULT_NAME) | ||
title = f"{name} [{user_input[CONF_FILE_PATH]}]" | ||
return self.async_create_entry(data=user_input, title=title) | ||
|
||
return self.async_show_form( | ||
step_id="sensor", data_schema=FILE_SENSOR_SCHEMA, errors=errors | ||
) | ||
|
||
async def async_step_import( | ||
self, import_data: dict[str, Any] | None = None | ||
) -> ConfigFlowResult: | ||
"""Import `file`` config from configuration.yaml.""" | ||
assert import_data is not None | ||
self._async_abort_entries_match(import_data) | ||
platform = import_data[CONF_PLATFORM] | ||
name: str = import_data.get(CONF_NAME, DEFAULT_NAME) | ||
file_name: str | ||
if platform == Platform.NOTIFY: | ||
file_name = import_data.pop(CONF_FILENAME) | ||
file_path: str = os.path.join(self.hass.config.config_dir, file_name) | ||
import_data[CONF_FILE_PATH] = file_path | ||
else: | ||
file_path = import_data[CONF_FILE_PATH] | ||
title = f"{name} [{file_path}]" | ||
return self.async_create_entry(title=title, data=import_data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
"""Constants for the file integration.""" | ||
|
||
DOMAIN = "file" | ||
|
||
CONF_TIMESTAMP = "timestamp" | ||
|
||
DEFAULT_NAME = "File" | ||
FILE_ICON = "mdi:file" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.