Move the in-process startup-state singleton out of app.main into
app.api.startup_status so app.system can read it without a circular
import. No behavior change - main mutates the same shared object.
Also lands the design spec and implementation plan for the
system/health endpoint work (#145).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Introduces the flat ErrorMessage envelope builder with BAPI_SEND_REPORT /
BAPI_SEND_TRACE gating (report/trace off by default), plus the design
spec and implementation plan for the consistent-error-responses work.
Fixes#148Fixes#123
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaced by the /ws WebSocket channel. Deletes the SSE connection manager,
the vendored sse_starlette, and the unused /bitcoin/block-sub and
/system/hardware-info-sub streaming endpoints. Also removes the
test_block_sub_error.py regression test, which exclusively covered the
now-deleted handle_block_sub per-request generator (handle_block_sub_redis,
which broadcasts over /ws, is retained and unaffected).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First commit of the SSE -> WebSocket migration (#252): the WebSocket
connection manager and its {type:auth,token} JWT handshake. Also lands
the migration's design spec and implementation plan.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BaseChannelListener.listen() only called pubsub.unsubscribe() in its
finally block: the pubsub connection and the listener's own Redis client
were never released, leaking a connection for every install/uninstall
(_watcher) and every recreation of the app-status listener. The Celery
task notifiers were likewise never closed, and used the deprecated
Redis.close() instead of aclose().
- add aclose() to BaseChannelNotifier and BaseChannelListener
- listen() now closes the pubsub and its Redis client in finally
- close the notifiers in the app_manage / app_status_update tasks and
switch to aclose()
- back off in the app-status watch loop so a Redis outage no longer
spins, and drop a leftover debug print
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
get_event_loop() is deprecated on Python 3.11+ when there is no running
loop and is slated to change behaviour further.
- SSEManager.setup() ran at import time (app.api.utils) via
get_event_loop(); this only worked because uvicorn imports the app
inside its loop and would break when imported without a running loop
(e.g. a Celery worker). Start the broadcast consumer lazily from
within a running loop instead.
- everywhere else the pattern was get_event_loop().create_task(x)
inside a coroutine; replace with asyncio.create_task(x).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
'from redis.asyncio import ... TimeoutError' shadowed the builtin
TimeoutError. redis's TimeoutError is a RedisError subclass, not a
builtin subclass, so the 'except TimeoutError' guarding
asyncio.wait_for never matched: timed-out commands fell through to the
generic handler, the child process was never terminated (leak), and
the caller got a misleading 'unable to execute' error instead of a
timeout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The WebUI only listens for the app_state_update_message SSE event to
populate the Apps tab. In bitcoin-only mode the warmup data was sent
under installed_app_status, which no client listens to, so the Apps
tab was stuck on the loading screen whenever the app status cache was
warm. Installing LND made it work again because the lightning warmup
path already used the correct event (raspiblitz#3608, raspiblitz#5141).
Also hardens the warmup path:
- reset the warmup_running flag on errors so a single failure no
longer starves all future SSE clients of warmup data
- convert per-source exceptions in the bitcoin-only warmup gather
instead of discarding the whole data set
- don't fall through to the partial-data branches when the API is
fully initialized with lightning disabled
- remove the now-unused INSTALLED_APP_STATUS event and the dead
cached_status_raw variable
Adds regression tests plus a conftest.py providing test env defaults
so the suite runs without a developer .env file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This is a feature that allows to fetch the app status via a celery
task which stores the result in the Redis database and notifies the API
of the change. The API sends a notification to connected clients via the
SSE mechanism.
Using a background task allows to avoid crashing the whole API if the
script call fails.
The by default the cache is refreshed every 30 minutes. This can be
changed by setting the `BAPI_APP_STATUS_UPDATE_INTERVAL_MIN` environment
variable.
refs #123
- add a result type similar to the Rust Result type from
https://github.com/rustedpy/result
- add a Report class to propagate error information back on the stack
with return types instead of exceptions
refs #123
If the environment variable BAPI_ENV_PATH is set, the config system will
try to read configs from the given path instead of .env in pwd
If no file is found .env in pwd will be used as a fallback
Env variables will always override settings in .env files.