degenbot.config =============== .. py:module:: degenbot.config .. autoapi-nested-parse:: Python's view of the Rust-owned configuration cascade. ``degenbot-config`` owns the operator file, the environment, and the resolution order end to end (ADR-062 D7/D10). The Python driver is a consumer of that verdict, never a second authority: this module translates the FFI surface into the shapes Python callers use and adds nothing to the resolution itself. The verdict is ONE frozen object -- :func:`resolved_config` -- built from the load published at FFI module init. The helpers here are translation (a wider ``int``/``str`` signature, the ``RpcNotConfiguredError`` the driver catches, the retired-keyword refusal) over that one object; none of them re-derives a layer or re-reads a key. A config key the driver wants that has no helper yet is a named property off the typed seam projection, ``resolved_config().values.
.`` -- reading one does not require an edit anywhere, which is the point of the verdict. The cascade has four layers -- an explicit override, the environment, the operator file, and a declared default -- and reports the layer that won. The config is installed once at FFI module init, so a process resolves the same file and the same environment as the Rust console no matter which entry path started it. Two doors exist, and using the wrong one is a tautology: ``degenbot._ffi.resolve_hypothetical`` answers HOW the cascade resolves a captured environment + file (a pure function that installs nothing, reachable only from the raw FFI seam and deliberately not re-exported here), while the installed :func:`resolved_config` answers WHAT this process installed. Package Contents ---------------- .. py:function:: resolved_config() -> degenbot._ffi.ResolvedConfig Return the whole resolved configuration for this process. The one object the driver reads: every declared key with its typed value (:attr:`~degenbot._ffi.ResolvedConfig.values`), the layer each came from (:attr:`~degenbot._ffi.ResolvedConfig.provenance`), and the resolutions that take a capability or an override. Frozen, and built from the load published at FFI module init, so it cannot drift from what the console read. :returns: The installed verdict. .. py:exception:: RpcNotConfiguredError Bases: :py:obj:`ValueError` No endpoint configured for a chain in any cascade layer. Subclasses :class:`ValueError` so callers that already catch a misconfigured endpoint keep working. The core's refusal names the scope, the transports it consulted, the layers each was read through, and what to declare or export, so a fresh environment fails fast with a pointer at what to set instead of silently degrading. .. py:function:: resolve_node(chain_id: degenbot.types.aliases.ChainId, scope: str, *, node: str | None = None) -> degenbot._ffi.ResolvedNodeUri Resolve one endpoint for ``chain_id`` and report the layer that won. The full verdict: the endpoint plus its ``Source`` (``default``, ``file``, ``env``, or ``cli``), so a diagnostic can name the layer rather than re-deriving it. ``scope`` is the caller's capability -- ``"request"`` or ``"subscription"`` -- and a subscription never selects an ``http`` entry. :param chain_id: The chain the endpoint serves. :param scope: ``"request"`` or ``"subscription"``. :param node: The explicit override, classified by its own value. :returns: The endpoint and the layer that supplied it. :raises RpcNotConfiguredError: When no layer supplied an endpoint for the chain. .. py:function:: resolve_http_rpc_uri(chain_id: degenbot.types.aliases.ChainId, /, *, node: str | None = None, **retired: object) -> str Resolve the request-scope endpoint for ``chain_id``. Request scope is what a pool read, an ``eth_callMany``, or a transaction submission needs; it prefers an IPC socket, then a WS endpoint, then HTTP. A caller that must not depend on a subscription (a CLI ``pool update``) resolves through this one rather than :func:`resolve_rpc_uris`. :param chain_id: The chain the endpoint serves. :param node: The explicit override, classified by its own value. :param \*\*retired: A pre-0.6 per-transport override spelling. Carrying a value raises :class:`TypeError` that names the scheme-classified ``node`` argument replacing it; the catch-all exists so the hard cutover fails loudly instead of silently ignoring a spelling no layer reads. :returns: The resolved request endpoint as a string. :raises RpcNotConfiguredError: When no layer supplied one. .. py:function:: resolve_ws_rpc_uri(chain_id: degenbot.types.aliases.ChainId, /, *, node: str | None = None, **retired: object) -> str Resolve the subscription-scope endpoint for ``chain_id``. The subscription scope is the transports a feed can hold open -- IPC or WS -- and never an HTTP entry, so a poll-only endpoint is refused here instead of being handed to a subscriber that would silently degrade. :param chain_id: The chain the endpoint serves. :param node: The explicit override, classified by its own value. :param \*\*retired: A pre-0.6 per-transport override spelling. Carrying a value raises :class:`TypeError` that names the scheme-classified ``node`` argument replacing it; the catch-all exists so the hard cutover fails loudly instead of silently ignoring a spelling no layer reads. :returns: The resolved subscription endpoint as a string. :raises RpcNotConfiguredError: When no layer supplied one. .. py:function:: resolve_rpc_uris(chain_id: degenbot.types.aliases.ChainId, /, *, node: str | None = None, **retired: object) -> tuple[str, str] Resolve the ``(request, subscription)`` endpoint pair for ``chain_id``. The two capabilities resolve independently, each through the same four layers, so a session can take its request endpoint from the file and its subscription endpoint from the environment. There is no ``localhost`` default: a chain with no endpoint in any layer raises. :param chain_id: The chain the endpoints serve. :param node: The explicit override, classified by its own value. :param \*\*retired: A pre-0.6 per-transport override spelling. Carrying a value raises :class:`TypeError` that names the scheme-classified ``node`` argument replacing it; the catch-all exists so the hard cutover fails loudly instead of silently ignoring a spelling no layer reads. :returns: The resolved ``(request_uri, subscription_uri)`` pair. :raises RpcNotConfiguredError: When either capability is unresolved. .. py:function:: resolve_chain_id(chain_id: int | str | None = None) -> int Resolve the session chain id. Refuses with a :class:`ValueError` -- naming what to declare, export, or pass -- both when no layer named a chain and when the explicit value is not an integer. :param chain_id: The explicit override; the core parses it as text so a non-integer is refused by the layer that owns the spelling. :returns: The chain id the session targets. .. py:function:: resolve_database_path(database: str | None = None) -> str Resolve the database path a session opens. The winning value already has ``~`` and the XDG state home expanded by the resolver, so a caller needs no second expansion. :param database: The explicit override. :returns: The resolved database path. .. py:function:: config_file_path() -> str | None Return the operator file the loader selected. A free-form reader (the deployment-registry overlay) reads the same file the typed load read. ``None`` means the process has no file layer, which is contractually the schema defaults. :returns: The selected file's path, or ``None``. .. py:function:: declared_database_path() -> str Return the declared ``database.path`` key, with no cascade. The value the operator wrote, with no ``~`` expansion. A caller that wants the path a session opens asks :func:`resolve_database_path`. :returns: The declared ``database.path`` value.