> ## Documentation Index
> Fetch the complete documentation index at: https://docs.westyx.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK - SSE Live Updates

> How SSE live propagation works in the sync and async Python SDK.

The SDK can hold a persistent `GET /v1/stream` connection open in the background. Whenever the Nexus backend emits a change event (`config.updated`, `flag.toggled`, `secret.created`, `resync`, ...), the SDK triggers an immediate full sync - so the cache is updated within milliseconds of the change, instead of waiting for the next TTL tick.

Both `NexusClient` and `AsyncNexusClient` support SSE; they just use different concurrency primitives (a daemon thread vs an `asyncio.Task`).

## Auth

Python uses the **`X-Nexus-API-Key` header** on all endpoints, including `/stream`, via `httpx`'s streaming support. The key is never passed as a `?key=` query parameter. (All Nexus SDKs send the key in this header - the browser SDKs do too, using fetch-based streaming rather than the `EventSource` API.)

## Enabling it

The stream starts automatically after the initial sync (`stream=True` is the default in `NexusConfig`).

```python theme={null}
from westyx_nexus import NexusClient, NexusConfig

nexus = NexusClient.create(NexusConfig(
    base_url="...",
    api_key="...",
    stream=True,   # default - explicit here for clarity
))
# SSE is already running in a daemon thread.
```

To run in pure TTL-polling mode:

```python theme={null}
nexus = NexusClient.create(NexusConfig(
    base_url="...",
    api_key="...",
    stream=False,
))
```

## What happens under the hood

1. The SDK opens an HTTP streaming response via `httpx.Client.stream("GET", ...)` (or `AsyncClient.stream` for async) with `Accept: text/event-stream` and the `X-Nexus-API-Key` header.
2. The server immediately sends a `resync` event, which causes the SDK to call `sync()` once to align with the current state.
3. The connection stays open. Every change event triggers another full sync.
4. While the stream is up, the polling TTL is bumped to 60 s as a safety net.

## Reconnection

Any transport error (server restart, TCP drop, network blip) triggers a reconnect with **exponential backoff**: 1s - 2s - 4s - 8s - 16s - 30s (capped at 30s).

After **three consecutive transport errors** the SDK does not fall back permanently. Instead it sleeps the `sse_reconnect_cooldown` schedule (default 5 - 10 - 20 - 40 - 60 minutes, staying at 60 minutes thereafter) and then retries the SSE connection automatically. Your application is never notified, and `stream_connected` stays `False` during the cooldown period.

```
nexus.stream_connected   # False during cooldown, True again once reconnected
```

The reconnect schedule is configurable via `sse_reconnect_cooldown` in `NexusConfig`. See [Configuration](configuration) for details.

## `429 Too Many Requests`

Every project tier has a per-service stream connection limit:

| Tier   | Connections |
| ------ | ----------- |
| `free` | 5           |
| `xs`   | 10          |
| `s`    | 25          |
| `m`    | 50          |
| `l`    | 150         |
| `xl`   | 500         |

### Behaviour (v0.6.0+)

The Python SDK handles 429 with **presence-conditional refinement** of `Retry-After`:

* **With a parseable `Retry-After`**: the SDK parses the delta-seconds value, **clamps it to `[5, 300]` seconds**, fires `observer.on_fallback(FALLBACK_REASON_RATE_LIMITED)`, sleeps the indicated delay on the stream's stop event, and then **resumes the reconnect loop**. The 429 does **not** count toward `_MAX_STREAM_ERRORS`.
* **Without a parseable `Retry-After`**: the SDK fires `observer.on_fallback(FALLBACK_REASON_RATE_LIMITED)` and schedules a reconnect using the `sse_reconnect_cooldown` schedule (default 5 - 10 - 20 - 40 - 60 minutes). TTL polling covers the gap during the cooldown, and the stream retries automatically.

Both clients implement the same logic using their respective sleep primitives:

* `AsyncNexusClient` - `await asyncio.wait_for(self._stream_stop.wait(), timeout=retry_after)`, so an in-flight `close()` / `disconnect_stream()` wakes the sleeping task immediately.
* `NexusClient` (sync) - `self._stream_stop.wait(timeout=retry_after)` on a `threading.Event`, with the same prompt-shutdown property.

```python theme={null}
from westyx_nexus import FALLBACK_REASON_RATE_LIMITED

class RateLimitObserver:
    def __init__(self) -> None:
        self.rate_limit_episodes = 0
        self.connected = False

    def on_connected(self) -> None:
        self.connected = True

    def on_fallback(self, reason: str) -> None:
        if reason == FALLBACK_REASON_RATE_LIMITED:
            self.rate_limit_episodes += 1
```

## Disabling SSE at runtime

<CodeGroup>
  ```python title="Sync" theme={null}
  nexus.disconnect_stream()
  ```

  ```python title="Async" theme={null}
  await nexus.disconnect_stream()
  ```
</CodeGroup>

Reconnect later with `connect_stream()` (sync) or `nexus.connect_stream()` (async).

## What events trigger a sync

| Event                                                 | secret keys | public keys          |
| ----------------------------------------------------- | ----------- | -------------------- |
| `resync`                                              | yes         | yes                  |
| `config.created` / `.updated` / `.deleted`            | yes         | yes                  |
| `flag.created` / `.updated` / `.toggled` / `.deleted` | yes         | yes                  |
| `secret.created` / `.updated` / `.deleted`            | yes         | filtered server-side |

## Lifecycle

The SSE thread (sync) or task (async) is bound to the client. Calling `close()` stops it cleanly along with the rest of the client teardown. Always call `close()` on shutdown:

```python theme={null}
# Sync - signal handler
import signal
def handle_sigterm(*_):
    nexus.close()
    sys.exit(0)
signal.signal(signal.SIGTERM, handle_sigterm)

# Async - FastAPI lifespan handles this for you
# Or manually:
async with await AsyncNexusClient.create(config) as nexus:
    ...
```

## Inspecting status

```python theme={null}
nexus.stream_connected   # True | False
```

| Value   | Meaning                                                                                                                         |
| ------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `True`  | SSE stream is up and pushing events                                                                                             |
| `False` | Stream is not active - either never started (`stream=False`), 3-strike fallback to polling, or `disconnect_stream()` was called |
