"""Abstract sidebar-menu override persistence API. The original ``menu_overrides`` document remains the administrator's default layout. User layouts are stored as a separate, complete snapshot so that a user can customise the menu without changing the default for anybody else. The router overlays a user's snapshot on top of the default and lets default rows for node ids not present in that snapshot flow through (new administrator menus therefore appear for existing users). """ from __future__ import annotations from abc import ABC, abstractmethod from typing import Any class MenuOverrideStore(ABC): @abstractmethod async def list_overrides(self) -> list[dict[str, Any]]: """Return every stored override row (order unspecified).""" raise NotImplementedError @abstractmethod async def replace_all(self, rows: list[dict[str, Any]], *, updated_by: str | None = None) -> list[dict[str, Any]]: """Atomically replace the entire override set with ``rows``. Each row is ``{node_id, parent_id, sort_order, label, disabled}``. Returns the persisted rows. """ raise NotImplementedError @abstractmethod async def list_user_overrides(self, user_id: str) -> list[dict[str, Any]] | None: """Return one user's snapshot, or ``None`` when no snapshot exists.""" raise NotImplementedError @abstractmethod async def replace_user_overrides( self, user_id: str, rows: list[dict[str, Any]], *, updated_by: str | None = None, ) -> list[dict[str, Any]] | None: """Replace one user's snapshot. An empty list removes the snapshot and returns ``None``. This is the reset-to-default operation and intentionally differs from the admin ``replace_all`` document, where an empty list means an empty default. """ raise NotImplementedError