# Periodic refresh into the Key-Value Store A typical use of the Scheduler + Store pair: every N seconds, the Scheduler fires a `RefreshCatalog` event; a manager handles it, computes a fresh value, and writes it into the Store via `Set`. Other managers read the cached value with `Get` instead of recomputing. This pattern keeps expensive lookups out of the request path. --- ## Events ```python # events/catalog.py from edri.dataclass.event import Event, event from edri.dataclass.response import Response, response @response class CatalogSnapshotResponse(Response): items: list[str] @event class RefreshCatalog(Event): """Internal event — emitted by the Scheduler.""" @event class GetCatalog(Event): """Request the latest cached snapshot.""" response: CatalogSnapshotResponse ``` `RefreshCatalog` has no response; it's a fire-and-forget signal. --- ## Manager The manager has two jobs: 1. **Producer** — handle `RefreshCatalog` ticks, recompute, write into the Store. 2. **Consumer** — handle `GetCatalog` requests by asking the Store and completing the original event when the Store replies. EDRI threads the original event through `Get.data`, so the response handler can fish it back out. ```python # managers/catalog.py from edri.abstract import ManagerBase, request, response from edri.events.edri.store import Get, Set from events.catalog import GetCatalog, RefreshCatalog CACHE_KEY = "catalog_snapshot" class CatalogManager(ManagerBase): @request def refresh(self, event: RefreshCatalog) -> None: items = self._load_items_from_source() self.router_queue.put(Set(name=CACHE_KEY, value=items)) self.logger.debug("Catalog refreshed: %d items", len(items)) @request def serve(self, event: GetCatalog) -> None: # Ask the Store for the cached value. The original GetCatalog rides # along on Get.data so we can match it up when the reply arrives. self.router_queue.put(Get(name=CACHE_KEY, data=event)) @response def on_store_reply(self, event: Get) -> None: original: GetCatalog | None = event.data # type: ignore[assignment] if original is None: return # Not for us — some other consumer triggered this Get. original.response.items = event.response.value or [] self.router_queue.put(original) def _load_items_from_source(self) -> list[str]: # Replace with the real expensive lookup. return ["alpha", "beta", "gamma"] ``` Notes: * `@response` declares a handler for replies (here, the `Get` reply from the Store). * The Store ignores `Get.data` — it only fills `response.value` — but `data` survives the round trip because it's a normal dataclass field. * Re-emitting `original` returns the completed `GetCatalog` to the router for delivery back to the API client (or whichever caller produced it). --- ## Entry point ```python # main.py from datetime import datetime, timedelta from edri import EDRI from edri.utility.manager.scheduler import Job from events.catalog import RefreshCatalog from managers.catalog import CatalogManager if __name__ == "__main__": edri = EDRI() # Specialized managers. edri.start_store() edri.start_scheduler(jobs=[ Job( event=RefreshCatalog(), when=datetime.now(), # fire immediately on start repeat=timedelta(seconds=30), # then every 30 s ), ]) # Application manager. edri.add_component(CatalogManager(edri.router_queue)) edri.run() ``` What happens at runtime: 1. `start_store()` brings up the Key-Value Store. 2. `start_scheduler(jobs=[...])` registers the recurring `RefreshCatalog` job. 3. On the first tick (and every 30 s after), the Scheduler emits `RefreshCatalog`. 4. `CatalogManager.refresh` computes a new snapshot and writes it to the Store. 5. A consumer emits `GetCatalog`; `CatalogManager.serve` asks the Store; `on_store_reply` returns the completed event. --- ## Scheduling jobs at runtime You don't have to declare jobs at startup. From inside a manager, send a `Set` event to the scheduler: ```python from edri.events.edri.scheduler import Set as SchedulerSet self.router_queue.put( SchedulerSet( event=RefreshCatalog(), when=datetime.now() + timedelta(minutes=5), repeat=timedelta(hours=1), identifier="catalog-refresh", ) ) ``` `Cancel`, `Update`, and `SetUpdate` are documented in {ref}`scheduler-control-events`.