# EDRI The **EDRI** class is the main orchestrator. A single instance wires together the Router, Managers, Broker, API, Store, Scheduler, Connector, and Watcher into a running application. ```{eval-rst} .. module:: edri :no-index: ``` --- ## Overview You create one `EDRI()` in your entry-point script, add Managers, start whatever services you need, then call `run()`. The class handles startup ordering, graceful shutdown on `SIGINT` / `SIGTERM`, and cleanup of all child processes. ```python from edri import EDRI edri = EDRI() edri.add_component(...) edri.start_api() edri.run() ``` --- ## API surface ### `EDRI()` Constructor — takes no arguments (for now). ```python edri = EDRI() ``` Initialises the shared `multiprocessing.Queue` that the Router, Managers, and all internal components use to pass events. ### `add_component(manager)` Register a {class}`~edri.abstract.ManagerBase` subclass. ```python edri.add_component(MyManager(edri.router_queue)) ``` The Manager will be started when `run()` is called. Each component's signature is checked for the restart kwargs that the {class}`~edri.router.health_checker.HealthChecker` needs; a warning is logged if any are missing. ### `start_store(path=None, *, failure_policy=PersistenceFailurePolicy.BUFFER, startup_timeout=30.0)` Start the built-in key-value Store (a specialised Manager). Optional — only call if your application needs it. Pass `path="/var/lib/edri/store.db"` for asynchronous SQLite persistence and startup restoration. The default `path=None` keeps all KV data in memory. See [Store persistence](specialized_managers.md#optional-sqlite-persistence) for failure handling, TTL, shutdown, and trusted-pickle requirements. Persistent startup waits up to `startup_timeout` seconds for restoration; increase it for a large database, or use `None` for no parent deadline. ```python edri.start_store() ``` See {doc}`/specialized_managers` for usage. ### `start_scheduler(jobs=None)` Start the Scheduler with optional initial {class}`~edri.utility.manager.scheduler.Job` instances, given either as a list or as a mapping whose keys become the job identifiers. ```python edri.start_scheduler(jobs=[...]) edri.start_scheduler(jobs={"heartbeat": ...}) ``` The Scheduler emits events at configured times/intervals. See {doc}`/specialized_managers`. ### `start_broker(middlewares=None)` Start the Broker component that runs the middleware pipeline. Usually called implicitly by `start_api()`. ```python edri.start_broker(middlewares=[...]) ``` ### `start_api(middlewares=None, json_encoder=None, jinja_extensions=None)` Start the ASGI Listener (API). This is the primary way to expose HTTP endpoints. ```python edri.start_api() ``` Parameters: - `middlewares` — list of {class}`~edri.api.Middleware` instances applied to every request/response. - `json_encoder` — custom {class}`json.JSONEncoder` subclass (defaults to EDRI's {class}`~edri.utility.json_encoder.CustomJSONEncoder`). - `jinja_extensions` — set of Jinja2 {class}`~jinja2.ext.Extension` classes for the HTML handler. If `start_broker` has not been called yet, `start_api` calls it first. ### `start_grpc(host=None, port=None)` Start the gRPC listener — EDRI's gRPC front end, alongside or instead of `start_api()`'s ASGI Listener. Runs as its own process, sharing the same Broker (started here if it hasn't been already). ```python edri.start_grpc() ``` Parameters: - `host` — interface to bind, defaults to `EDRI_GRPC_HOST` / `localhost`. - `port` — port to bind, defaults to `EDRI_GRPC_PORT` / `50051`. If `start_broker` has not been called yet, `start_grpc` calls it first. See {doc}`/grpc`. ### `start_connector(router_id=None)` Connect to the distributed Switch for cross-machine event routing. ```python from uuid import UUID edri.start_connector(router_id=UUID("...")) ``` See {doc}`/connector` and {doc}`/switch`. ### `start_watcher()` Enable the development hot-reload watcher. Watches source files and restarts component processes when they change. ```python edri.start_watcher() ``` See {doc}`/watcher`. ### `run()` Enter the main loop. This method: 1. Creates the {class}`~edri.router.Router`. 2. Starts all registered Managers (calls each component's `start()`). 3. Runs the Router's event-dispatch loop. 4. On {class}`KeyboardInterrupt` (SIGINT) or SIGTERM, shuts down gracefully: quits the Router, stops every Manager, and terminates all child processes (Store, Scheduler, Broker, Listener, Connector). ```python edri.run() ``` --- ## Startup order ```mermaid sequenceDiagram participant Main as Main thread participant Router participant Store participant Scheduler participant Broker participant Listener participant Connector participant Mgrs as Managers Main->>Main: EDRI() Main->>Main: add_component() × N Main->>Store: start_store() (optional) Main->>Scheduler: start_scheduler() (optional) Main->>Broker: start_broker() (optional) Main->>Listener: start_api() (optional) Main->>Connector: start_connector() (optional) Main->>Main: start_watcher() (optional) Main->>Router: run() Router->>Mgrs: component.start(router_queue) Router->>Router: event dispatch loop Note over Router: waits for KeyboardInterrupt / SIGTERM Router-->>Main: quit signal Main->>Mgrs: component.quit() Main->>(+)Store: terminate() Main->>(+)Scheduler: terminate() Main->>(+)Broker: terminate() Main->>(+)Listener: terminate() Main->>(+)Connector: terminate() ``` --- ## Minimal complete example ```python from datetime import datetime from edri import EDRI from edri.api.dataclass.api_event import api, ApiType from edri.dataclass.event import Event from edri.dataclass.response import Response, response from edri.abstract import ManagerBase, request from http import HTTPMethod @response class GreetingResponse(Response): message: str timestamp: str @api(url="/hello/{name}", method=HTTPMethod.GET, exclude={ApiType.HTML, ApiType.WS}) class Greeting(Event): name: str response: GreetingResponse class GreetingManager(ManagerBase): @request def greet(self, event: Greeting) -> None: event.response.message = f"Hello, {event.name}!" event.response.timestamp = datetime.now().isoformat() if __name__ == "__main__": edri = EDRI() edri.add_component(GreetingManager(edri.router_queue)) edri.start_api() edri.run() ``` ## Distributed example (with Connector) ```python from uuid import UUID from edri import EDRI from edri.abstract import ManagerBase, request from my_events import RemoteEvent class LocalManager(ManagerBase): @request def handle_remote(self, event: RemoteEvent) -> None: print(f"Received from remote node: {event.data}") if __name__ == "__main__": edri = EDRI() edri.add_component(LocalManager(edri.router_queue)) edri.start_connector(router_id=UUID("12345678-1234-5678-1234-567812345678")) edri.run() ``` --- ## See also - {doc}`/getting_started` — step-by-step tutorial - {doc}`/manager` — Manager base classes - {doc}`/router` — event dispatching - {doc}`/connector` — distributed routing - {doc}`/switch` — the TCP hub - {doc}`/watcher` — hot-reload