182 files

This commit is contained in:
Home Assistant Version Control
2026-08-17 12:11:35 +00:00
parent 7dddf6bb13
commit ece15a1c1b
183 changed files with 11457 additions and 3092 deletions
@@ -15,8 +15,8 @@ from .const import (
CONF_TASKS,
DEFAULT_WARNING_DAYS,
DOMAIN,
GLOBAL_UNIQUE_ID,
)
from .helpers.aggregate import get_object_entries, merged_tasks
from .helpers.schedule import Schedule, read_legacy_fields
_LOGGER = logging.getLogger(__name__)
@@ -73,8 +73,7 @@ def _build_export_object(
# Merge static + Store dynamic data for each task
rd = getattr(entry, "runtime_data", None)
store = getattr(rd, "store", None) if rd else None
static_tasks = entry.data.get(CONF_TASKS, {})
tasks_data = store.merge_all_tasks(static_tasks) if store is not None else static_tasks
tasks_data = merged_tasks(entry)
ct_tasks = (coordinator_data or {}).get(CONF_TASKS, {})
tasks = []
@@ -116,6 +115,9 @@ def _build_export_object(
"entity_slug": tdata.get("entity_slug"),
"adaptive_config": tdata.get("adaptive_config"),
"checklist": tdata.get("checklist") or [],
# In-cycle ticks (#73) — merged_tasks overlays them from the Store;
# exporting them keeps half-done checklists across backup/restore.
"checklist_progress": tdata.get("checklist_progress"),
"schedule_time": tdata.get("schedule_time"),
# v2.17+ / #83 task fields — persisted and user-facing, so a JSON
# backup must restore them (same field-completeness contract as #67
@@ -197,15 +199,11 @@ def _build_export_object(
}
def object_entries(hass: HomeAssistant, entry_ids: set[str] | None = None) -> list[ConfigEntry]:
"""The maintenance OBJECT entries (never the global hub), optionally
narrowed to a selection. Shared by every exporter so JSON/YAML/CSV apply
the same selective-export filter. ``entry_ids=None`` means all objects."""
return [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.unique_id != GLOBAL_UNIQUE_ID and (entry_ids is None or entry.entry_id in entry_ids)
]
# The maintenance OBJECT entries (never the global hub). Shared by every
# exporter so JSON/YAML/CSV apply the same selective-export filter — the
# implementation lives in helpers.aggregate, this module keeps the name its
# importers (csv_handler, doc_archive, WS adopt handlers) bind to.
object_entries = get_object_entries
def build_export_data(
@@ -284,3 +282,49 @@ def export_maintenance_data(
"""
data = build_export_data(hass, include_history=include_history)
return serialize_export(data, fmt)
# Global settings the export deliberately leaves behind: HA user ids are
# instance-bound (and the panel-access allowlist is security-relevant), and
# the adopted-task stash is transient re-adopt state.
_NON_PORTABLE_SETTINGS = ("admin_panel_user_ids", "adopted_task_notes")
def build_settings_export(hass: HomeAssistant) -> dict[str, Any]:
"""The SECOND export: the global entry's settings.
``build_export_data`` deliberately carries only objects — groups, saved
views, vacation config, notification/budget settings and the feature
toggles live on the global entry and export through here instead
(2026-08 round-trip audit decision). Instance-bound keys are excluded
(see ``_NON_PORTABLE_SETTINGS``).
"""
from .const import (
CONF_GROUPS,
CONF_SAVED_FILTER_VIEWS,
CONF_VACATION_BUFFER_DAYS,
CONF_VACATION_ENABLED,
CONF_VACATION_END,
CONF_VACATION_EXEMPT_TASK_IDS,
CONF_VACATION_START,
)
from .helpers.global_options import get_global_options
from .helpers.settings_registry import ALLOWED_SETTING_KEYS
opts = get_global_options(hass)
settings: dict[str, Any] = {
k: opts[k] for k in ALLOWED_SETTING_KEYS if k in opts and k not in _NON_PORTABLE_SETTINGS
}
# Structured sections with their own WS surfaces (not in the registry).
for key in (
CONF_GROUPS,
CONF_SAVED_FILTER_VIEWS,
CONF_VACATION_ENABLED,
CONF_VACATION_START,
CONF_VACATION_END,
CONF_VACATION_BUFFER_DAYS,
CONF_VACATION_EXEMPT_TASK_IDS,
):
if key in opts:
settings[key] = opts[key]
return {"version": 1, "global_settings": settings}