-
-
Notifications
You must be signed in to change notification settings - Fork 35k
Add config flow to nederlandse_spoorwegen #148027
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 68 commits
13ac3cf
1239ec9
4f1f081
796eb5e
a9f48aa
84e3f47
d25bab9
f378ca3
1720b5c
54683e0
474ce35
c3f387e
eba5047
a2db49f
a81fcf3
73b0691
ef6e055
2999b35
6607c23
394fd48
ef8b75d
c867d19
9df3b57
60cc1db
324ac65
ade6a48
a4448b0
c23b2dc
ac1f29c
bb56710
cdc6e9d
938529f
3c65282
a2abe2c
694075b
0bf830f
5da5cfa
ed6682d
b78cf88
68e2957
06e92c7
e1798f8
b2e567d
5f83cb4
5eff5fc
243ef7f
e8fbcdb
5e0ba85
f2cad24
0c5496e
10f88da
ece9a9e
5ffafb4
0e600e3
892c1eb
a50f5f3
70362dd
17d396a
5afc872
fbee5b8
ca26aab
7e4fa6a
c1ffe7f
48c38ea
b4c99b9
93f9fb9
ff87004
db95fab
c7ff0d9
50fb0e8
2f587c3
f4e35bd
5fe9983
e5a8cbf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,197 @@ | ||
"""The nederlandse_spoorwegen component.""" | ||
|
||
from __future__ import annotations | ||
|
||
import asyncio | ||
import contextlib | ||
from dataclasses import dataclass | ||
import logging | ||
from types import MappingProxyType | ||
from typing import Any | ||
|
||
from homeassistant.config_entries import ConfigEntry, ConfigSubentry | ||
from homeassistant.const import CONF_API_KEY, CONF_NAME, Platform | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers import config_validation as cv | ||
from homeassistant.helpers.typing import ConfigType | ||
|
||
from .api import NSAPIWrapper | ||
from .const import CONF_FROM, CONF_ROUTES, CONF_TIME, CONF_TO, CONF_VIA, DOMAIN | ||
from .coordinator import NSDataUpdateCoordinator | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
# This integration can only be configured via config entries | ||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) | ||
|
||
|
||
# Define runtime data structure for this integration | ||
@dataclass | ||
class NSRuntimeData: | ||
"""Runtime data for the Nederlandse Spoorwegen integration.""" | ||
|
||
coordinator: NSDataUpdateCoordinator | ||
stations: list[Any] | None = None # Full station objects with code and names | ||
stations_updated: str | None = None | ||
|
||
|
||
type NSConfigEntry = ConfigEntry[NSRuntimeData] | ||
|
||
PLATFORMS = [Platform.SENSOR] | ||
|
||
|
||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: | ||
"""Set up the Nederlandse Spoorwegen component.""" | ||
return True | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: NSConfigEntry) -> bool: | ||
"""Set up Nederlandse Spoorwegen from a config entry.""" | ||
api_key = entry.data.get(CONF_API_KEY) | ||
if not api_key: | ||
raise ValueError("API key is required") | ||
heindrichpaul marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
api_wrapper = NSAPIWrapper(hass, api_key) | ||
|
||
coordinator = NSDataUpdateCoordinator(hass, api_wrapper, entry) | ||
|
||
entry.runtime_data = NSRuntimeData(coordinator=coordinator) | ||
|
||
await _async_migrate_legacy_routes(hass, entry) | ||
|
||
entry.async_on_unload(entry.add_update_listener(async_reload_entry)) | ||
|
||
with contextlib.suppress(asyncio.CancelledError): | ||
heindrichpaul marked this conversation as resolved.
Show resolved
Hide resolved
|
||
await coordinator.async_config_entry_first_refresh() | ||
|
||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
return True | ||
|
||
|
||
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: | ||
"""Reload NS integration when options are updated.""" | ||
await hass.config_entries.async_reload(entry.entry_id) | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: NSConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) | ||
|
||
|
||
async def async_remove_entry(hass: HomeAssistant, entry: NSConfigEntry) -> None: | ||
"""Handle removal of a config entry.""" | ||
heindrichpaul marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
async def _async_migrate_legacy_routes( | ||
hass: HomeAssistant, entry: NSConfigEntry | ||
) -> None: | ||
"""Migrate legacy routes from configuration data into subentries. | ||
|
||
This handles routes stored in entry.data[CONF_ROUTES] from legacy YAML config. | ||
One-time migration to avoid duplicate imports. | ||
""" | ||
Comment on lines
+70
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, so instead of importing the yaml into a config entry and only then migrating that config entry into subentries, we should migrate to subentries directly instead. Less moving objects, less room for error :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm actually already doing exactly what you suggested. The current
There's no intermediate step of creating separate config entries first. The flow is:
@joostlek There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes and no, you're running that code in def setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None: This is the current entrypoint of the integration and we should use this (or the async counterpart) to start an import flow, and then the code in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @joostlek I have now added the logic to the async_setup as well. I have also run multiple tests locally and it imports the routes and config without issues |
||
if entry.options.get("routes_migrated", False): | ||
return | ||
|
||
legacy_routes = entry.data.get(CONF_ROUTES, []) | ||
|
||
hass.config_entries.async_update_entry( | ||
entry, options={**entry.options, "routes_migrated": True} | ||
) | ||
|
||
if not legacy_routes: | ||
return | ||
|
||
migrated_count = 0 | ||
|
||
for route in legacy_routes: | ||
try: | ||
if not all(key in route for key in (CONF_NAME, CONF_FROM, CONF_TO)): | ||
_LOGGER.warning( | ||
"Skipping invalid route missing required fields: %s", route | ||
) | ||
continue | ||
|
||
# Create subentry data with centralized station code normalization | ||
subentry_data = { | ||
CONF_NAME: route[CONF_NAME], | ||
CONF_FROM: NSAPIWrapper.normalize_station_code(route[CONF_FROM]), | ||
CONF_TO: NSAPIWrapper.normalize_station_code(route[CONF_TO]), | ||
} | ||
|
||
# Add optional fields if present | ||
if route.get(CONF_VIA): | ||
subentry_data[CONF_VIA] = NSAPIWrapper.normalize_station_code( | ||
route[CONF_VIA] | ||
) | ||
|
||
if route.get(CONF_TIME): | ||
subentry_data[CONF_TIME] = route[CONF_TIME] | ||
|
||
# Create unique_id with centralized station code normalization | ||
unique_id_parts = [ | ||
NSAPIWrapper.normalize_station_code(route[CONF_FROM]), | ||
NSAPIWrapper.normalize_station_code(route[CONF_TO]), | ||
NSAPIWrapper.normalize_station_code(route.get(CONF_VIA, "")), | ||
] | ||
unique_id = "_".join(part for part in unique_id_parts if part) | ||
|
||
# Create the subentry | ||
subentry = ConfigSubentry( | ||
data=MappingProxyType(subentry_data), | ||
subentry_type="route", | ||
title=route[CONF_NAME], | ||
unique_id=unique_id, | ||
) | ||
|
||
# Add the subentry to the config entry | ||
hass.config_entries.async_add_subentry(entry, subentry) | ||
migrated_count += 1 | ||
|
||
except (KeyError, ValueError) as ex: | ||
_LOGGER.warning( | ||
"Error migrating route %s: %s", route.get(CONF_NAME, "unknown"), ex | ||
) | ||
|
||
# Clean up legacy routes from data | ||
new_data = {**entry.data} | ||
if CONF_ROUTES in new_data: | ||
new_data.pop(CONF_ROUTES) | ||
|
||
# Update the config entry to remove legacy routes | ||
hass.config_entries.async_update_entry(entry, data=new_data) | ||
|
||
|
||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: | ||
"""Migrate old entry.""" | ||
_LOGGER.debug( | ||
"Migrating configuration from version %s.%s", | ||
config_entry.version, | ||
config_entry.minor_version, | ||
) | ||
|
||
if config_entry.version > 1: | ||
# This means the user has downgraded from a future version | ||
return False | ||
|
||
if config_entry.version == 1: | ||
new_data = {**config_entry.data} | ||
|
||
if config_entry.minor_version < 1: | ||
# Future migrations can be added here for schema changes | ||
pass | ||
|
||
# Update the config entry with new data and version | ||
hass.config_entries.async_update_entry( | ||
config_entry, | ||
data=new_data, | ||
minor_version=1, | ||
version=1, | ||
) | ||
|
||
_LOGGER.debug( | ||
"Migration to configuration version %s.%s successful", | ||
config_entry.version, | ||
config_entry.minor_version, | ||
) | ||
return True |
Uh oh!
There was an error while loading. Please reload this page.