Flask Quickstart
Protect a Flask route with Baldur. No Redis, no Docker, no environment variables. The in-memory fallback covers the whole first run.
Supports Python 3.11–3.13. Assumes you have built a Flask app before.
1. Install
pip install baldur-framework[flask]
2. Wire Baldur into the app
init_flask(app) calls baldur.init() once and installs the request hooks
(rate-limit, backpressure, circuit-breaker pre-flight). The /demo route is
protected by the marquee @baldur.protected facade:
"""Minimal Flask app wired with Baldur.
``init_flask(app)`` calls ``baldur.init()`` exactly once and installs the
request hooks (rate-limit + backpressure + circuit-breaker pre-flight). The
``/demo`` route is protected by the marquee ``@baldur.protected`` facade, with
``dlq=True`` opting the route into dead-letter capture of final failures.
Zero infrastructure: in-memory fallback, no Redis, no env vars.
Run the server with ``flask run`` from this directory, then
``curl http://127.0.0.1:5000/demo``. See ``docs/getting-started/flask.md``.
"""
from __future__ import annotations
from flask import Flask, jsonify
import baldur
from baldur.adapters.flask import init_flask
app = Flask(__name__)
init_flask(app)
@app.get("/demo")
@baldur.protected("demo", dlq=True)
def demo():
"""Return a JSON payload through Baldur's resilience pipeline."""
return jsonify({"status": "ok", "service": "demo"})
dlq=True opts the route into the dead-letter queue:
a final failure is captured with a snapshot of the call's arguments so it can be
replayed once the dependency recovers. Capture stores your request data, which
is why it is opt-in per call
rather than on by default.
Baldur does not authenticate your routes
The request hooks add resilience (rate-limit, backpressure, circuit breaker), not authentication. Your app keeps owning endpoint auth (Flask-Login, your own decorators). Baldur's own operational surface (the built-in admin server and Web Console) is access-controlled separately by a key you configure; see that guide for the role model. The Django adapter additionally registers Django permission groups for its own admin/API views; there is no equivalent host-app role system on Flask.
3. Run it
Start the server and call the route:
flask run
curl http://127.0.0.1:5000/demo
# {"status": "ok", "service": "demo"}
That's it. The response just travelled through a circuit breaker.
Open the console
baldur.init() also started Baldur's admin server on loopback, so the built-in
Web Console is already serving — no
extra step, and nothing to configure:
open http://127.0.0.1:9090/ # any browser; the page is served on loopback only
If another process already holds that port, Baldur does not take it over: it
logs admin.autostart_failed and your app keeps serving without the console.
See Baldur's events
Baldur logs to stdout when your app has no root handler; when it has one
(logging.basicConfig, a root entry in LOGGING), Baldur's events go
through your handlers in your format. Set BALDUR_LOG_LEVEL=INFO to watch
circuit breaker and rate-limit events as you exercise the endpoint:
export BALDUR_LOG_LEVEL=INFO # circuit opened/closed, rate-limit blocks, ...
Call init_flask(app) before the first use of app.logger: Flask attaches
its own handler to app.logger only when nothing up the tree would show the
record, so in this order the app's records are written once.
Verify without a browser
The quickstart ships a smoke test that drives the route through Flask's in-process test client — no server, no infra:
pytest examples/quickstart_flask/test_smoke.py
Browse the full runnable app:
examples/quickstart_flask/.
Going to production
The in-memory fallback is single-process only
The zero-config path uses Baldur's in-memory cache. It keeps state in a
per-process store, so copying this quickstart into a multi-worker
deployment (gunicorn --workers N) does not degrade gracefully:
idempotency keys, rate-limit counters, and circuit breaker state diverge
silently per worker. That breaks correctness, not just scale. The
in-memory store also grows unbounded. This is a hazard, not a tuning knob:
give Baldur a shared backend before you run more than one worker.
Point Baldur at Redis so all workers share state. No code changes needed: set one environment variable before starting the server:
pip install baldur-framework[flask,redis]
export BALDUR_REDIS_URL=redis://localhost:6379/0
export BALDUR_ENVIRONMENT=production
Those two variables are the only addition the production path needs over the
quickstart path. Declaring the environment is what turns the hazard above into a
rule Baldur enforces: with BALDUR_ENVIRONMENT=production set and
BALDUR_REDIS_URL missing, baldur.init() refuses to start rather than let a
shared guarantee degrade to per-worker memory. For a deliberate single-process
deployment on in-memory state, BALDUR_TEST_MODE=true opts out of that check,
and of Baldur's other production configuration checks with it.