File size: 2,724 Bytes
a8039c4
32a601f
a8039c4
 
 
42e32ed
 
a8039c4
42e32ed
7eccfb0
 
 
42e32ed
7eccfb0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3f7b296
 
 
 
 
 
7eccfb0
 
 
 
42e32ed
 
a8039c4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
"""Root entry point — a thin shim over the FISHBOWL Gradio shell.

The app itself lives in ``src.ui.fishbowl.app`` (Unit 9).  This shim keeps ``uv run
app.py`` working and preserves the no-API-key / offline behaviour: the deterministic
stub drives the cast so the demo is reproducible on stage with no credentials.
"""

from __future__ import annotations

import os
import sys
from pathlib import Path


def _load_dotenv(filename: str = ".env") -> None:
    """Load ``KEY=VALUE`` pairs from a sibling ``.env`` into the environment, if present.

    Real environment variables win (``setdefault``) — this only fills gaps, never
    overrides what the shell or CI already set.  Parsed in Python, not the shell, so
    values with shell-special characters load correctly: a Neon ``DATABASE_URL``'s
    ``&``, a JSON ``MEMORY_INDEX_CONFIG`` — all of which silently abort ``source .env``.
    Absent ``.env`` → no-op, preserving the offline-by-default behaviour (no keys, stub).
    """
    path = Path(__file__).resolve().parent / filename
    if not path.exists():
        return
    for raw in path.read_text(encoding="utf-8").splitlines():
        line = raw.strip()
        if not line or line.startswith("#"):
            continue
        if line.startswith("export "):
            line = line[len("export ") :]
        key, sep, value = line.partition("=")
        if not sep:
            continue
        key, value = key.strip(), value.strip()
        if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'":
            value = value[1:-1]  # strip matching surrounding quotes
        # Skip empty values: a blank `KEY=` in .env means "not configured", and
        # setting it to "" would shadow `os.getenv(KEY, default)` callers (e.g.
        # Gradio's GRADIO_SERVER_PORT) that expect absent → default.
        if key and value:
            os.environ.setdefault(key, value)


# Load .env BEFORE importing the app: the registry/router/ledger/memory all read the
# environment at import and on Summon, so credentials must be present first.  Skipped
# under pytest so the test suite stays hermetically offline (no .env bleed into tests).
if "pytest" not in sys.modules:
    _load_dotenv()
    # Initialise logging + tracing before the app imports so every layer's logger
    # and spans are wired from the first import (ADR-0024).  Reads MAL_* env vars;
    # skipped under pytest so the suite stays hermetic (instrumentation auto-configures).
    from src import observability as _obs  # noqa: E402

    _obs.configure()

from src.ui.fishbowl.app import demo, launch  # noqa: E402  (must follow _load_dotenv)

__all__ = ["demo", "launch", "_load_dotenv"]

if __name__ == "__main__":
    launch()