Production-oriented unified runtime for FastAPI + Streamlit
Project description
FluxLit (fluxlit)
Production-oriented unified runtime for FastAPI and Streamlit: one public port, one CLI, and a single FluxLit app object for APIs plus UI pages.
Documentation (hosted): https://fluxlit.readthedocs.io/en/stable/
Security: Vulnerability reporting and dependency audit notes are in SECURITY.md. CI runs pip-audit after installing the auth extra (core + PyJWT/cryptography).
| Topic | Read the Docs |
|---|---|
| Quick start | Quick start |
| Architecture & routing | Architecture |
Config & fluxlit.toml |
Configuration |
| Security & threat model | Security architecture |
| Auth patterns (JWT, OIDC BFF, Streamlit) | Auth recipes |
| Adopting auth in an existing app | Auth migration |
CLI (dev, run, doctor, build) |
CLI |
| Testing | Testing |
| Python API | API reference |
| Contributing | Contributing |
| Changelog | Changelog |
| Roadmap | Roadmap |
Also in the repo: Product & architecture plan (source for long-form product context; overview is on Read the Docs under Architecture).
Overview
FluxLit targets teams that want FastAPI for HTTP APIs and Streamlit for interactive UIs without wiring separate ports, ad hoc reverse proxies, and brittle dev scripts. The runtime uses a sidecar model: Streamlit runs in a subprocess; a Starlette ASGI gateway fronts both the API and the UI.
Requirements
- Python 3.10+
- Dependencies are declared in
pyproject.toml(FastAPI, Uvicorn, Streamlit, Typer, httpx, websockets, etc.).
Install
pip install fluxlit
JWT validation, JWKS, and OIDC/BFF helpers require the optional extra:
pip install "fluxlit[auth]"
With the extra installed you get JWT validation (HS256 and JWKS/RS256), OIDC discovery, and BFF-style login/token-exchange helpers. High-level entry points:
- FastAPI:
app.make_jwt_bearer()fromFLUXLIT_JWT_*env vars, orapp.attach_oidc_login(oidc_client)withFLUXLIT_PUBLIC_BASE_URLandFLUXLIT_OIDC_BFF_SECRET. - Streamlit: the page
clientis unauthenticated (for public routes and bootstrapping). For secured/api/...routes, useApiClient.for_fluxlit(bearer_token=...)orauth_header_factoryso every request sendsAuthorization. After an OIDC redirect,prepare_streamlit_api_client(st)performs the server-side code exchange and returns a client that carries the session bearer.
See Auth recipes, Security architecture, and Auth migration. A minimal runnable demo lives under examples/reference_auth/.
For local development of FluxLit itself:
git clone https://github.com/eddiethedean/fluxlit.git
cd fluxlit
pip install -e ".[dev]"
Quick start
Create a project (optional):
fluxlit new my-app
cd my-app
Define a FluxLit app (e.g. app.py):
from fluxlit import FluxLit
app = FluxLit(title="Admin Portal")
@app.api.get("/users")
def users():
return [{"name": "Ada"}]
@app.page("/")
def home(st, client):
st.title("Dashboard")
st.write(client.get("/users").json())
Run the unified server (default import path app:app, or target from fluxlit.toml / [tool.fluxlit]):
fluxlit dev
# or explicitly:
fluxlit dev app:app
- Browser: open the URL shown by Uvicorn (default
http://127.0.0.1:8000). - API: routes are mounted under
/api(e.g.GET /api/users). - OpenAPI / docs:
http://127.0.0.1:8000/api/docs(Swagger) and/api/openapi.json. - Health:
http://127.0.0.1:8000/api/healthz(hidden from OpenAPI).
From Streamlit code, ApiClient calls the API using a base URL that includes /api (set automatically as FLUXLIT_INTERNAL_API_BASE when using fluxlit dev / fluxlit run). Use paths like client.get("/users"), not client.get("/api/users").
For typed JSON responses, use ApiClient.get_model / post_model with Pydantic models.
If routes use Depends(JWTBearer) (or similar), build an authenticated client—see Calling secured APIs from Streamlit below.
How routing works
Browser
│
▼
┌──────────────────────────────────────┐
│ FluxLit gateway (Uvicorn, one port) │
├──────────────────────────────────────┤
│ /api/* → FastAPI (path prefix │
│ stripped inside the app) │
│ /* → Streamlit (HTTP + WS │
│ proxy to subprocess) │
└──────────────────────────────────────┘
Anything that is not under /api is forwarded to Streamlit (including WebSockets used by Streamlit). Reserve /api for your HTTP API.
Note: the API prefix is configurable via FluxlitSettings.api_mount_path (default /api).
Calling secured APIs from Streamlit
The client passed into @app.page handlers is a plain ApiClient with no Authorization header. That is intentional: use it for public endpoints, token issuance, or OIDC exchange, then create a client that attaches credentials for protected routes.
from fluxlit.client import ApiClient
@app.page("/")
def home(st, client):
token = st.session_state.get("access_token") # set after your login / BFF exchange
if not token:
st.info("Sign in first.")
return
with ApiClient.for_fluxlit(bearer_token=token) as api:
st.write(api.get("/me").json())
For OIDC + BFF flows, prepare_streamlit_api_client(st) (from fluxlit) combines the one-time auth_code exchange with an authenticated client—details and variants (auth_header_factory, env-driven JWT) are in the Auth recipes doc.
Install fluxlit[auth] for JWT/OIDC helpers used in those guides.
CLI
| Command | Description |
|---|---|
fluxlit dev [target] |
Development server: Streamlit subprocess + gateway. Default target: CLI arg → fluxlit.toml / pyproject.toml [tool.fluxlit] → app:app. |
fluxlit run [target] |
Same stack without auto-reload. |
fluxlit doctor [target] |
Environment checks (import, bind, deps, FLUXLIT_INTERNAL_API_BASE). Exits non-zero on failures unless --warnings-only. |
fluxlit build [target] |
Write a starter Dockerfile and .dockerignore (use --output / -o, --force to overwrite). |
fluxlit new <name> |
Scaffold a minimal app.py in a new directory. |
python -m fluxlit |
Equivalent entry to the fluxlit console script. |
Options for dev / run include --host, --port, --log-level, --proxy-headers, --forwarded-allow-ips.
fluxlit dev --reload reloads the API gateway process only via Uvicorn; the Streamlit subprocess is not restarted. Use --reload-scope=gateway (default) or restart fluxlit to pick up Streamlit UI changes.
Configuration
Precedence: CLI flags override environment variables; environment overrides project file defaults; then FluxlitSettings field defaults.
Project file
fluxlit.tomlin the current working directory (top-level keys), or[tool.fluxlit]inpyproject.tomliffluxlit.tomlis absent.
If both exist, fluxlit.toml wins. Supported keys include target, gateway_host, gateway_port, log_level, api_mount_path, and root_path.
Example fluxlit.toml:
target = "app:app"
gateway_host = "127.0.0.1"
gateway_port = 8000
log_level = "info"
Environment (FluxlitSettings)
See Configuration and the API reference (fluxlit.config). Variables use the FLUXLIT_ prefix and optional .env file.
| Variable | Role |
|---|---|
FLUXLIT_TITLE |
App title (default for FastAPI / UX). |
FLUXLIT_GATEWAY_HOST / FLUXLIT_GATEWAY_PORT |
Defaults for binding (also used in settings; CLI overrides bind for dev/run). |
FLUXLIT_ROOT_PATH |
ASGI root path behind a reverse proxy (passed through to FastAPI). |
FLUXLIT_INTERNAL_API_BASE |
Set by the runtime for Streamlit-side ApiClient (includes /api). |
FLUXLIT_ENABLE_REQUEST_LOGGING |
If true, log each API request (method, path, status) at INFO with request id context. |
Suggested project layout
my_app/
├── app.py # FluxLit instance, @app.api routes, @app.page handlers
├── pkg/ # Optional: Python package for discover_pages
│ ├── __init__.py
│ └── pages/ # call discover_pages("pages", package="pkg")
│ ├── __init__.py
│ └── reports.py # def register(app): @app.page(...) ...
├── services/
├── static/
├── fluxlit.toml # Optional defaults for CLI
└── .env # FLUXLIT_* and secrets (do not commit)
Optional page packages: each pkg/pages/*.py module may define register(app: FluxLit) -> None that attaches @app.page handlers; call app.discover_pages("pages", package="pkg") after constructing FluxLit.
Package layout (src/fluxlit)
Autogenerated detail: Python API reference. Summary:
| Module | Purpose |
|---|---|
app |
FluxLit application object |
cli |
Typer CLI |
client |
ApiClient (httpx) for server-side API calls |
config |
FluxlitSettings |
gateway |
ASGI router + HTTP/WebSocket proxy (request id context + debug logs) |
project_config |
fluxlit.toml / [tool.fluxlit] loading |
logging_context |
Request id ContextVar for gateway / API |
runtime |
Subprocess orchestration, Uvicorn entry |
streamlit_main |
Streamlit entry script (FLUXLIT_APP) |
testing |
FluxLitTestClient (FluxLit-native API + Streamlit test helpers) |
api |
Optional APIRouter helpers |
auth |
Forward-auth / trusted-proxy user helpers |
jwt_auth |
JWT bearer dependencies, JWKS, scopes/roles (fluxlit[auth]) |
oidc |
OIDC client + BFF routes (fluxlit[auth]) |
streamlit_auth |
Streamlit helpers, prepare_streamlit_api_client (fluxlit[auth]) |
security_middleware |
Optional security headers on FluxLit.api |
Testing
FluxLit uses the same “built-in” testing platforms you likely already know:
- FastAPI:
starlette.testclient.TestClient - Streamlit:
streamlit.testing.v1.AppTest(version-dependent)
FluxLit also ships a small wrapper: FluxLitTestClient.
from fluxlit import FluxLit, FluxLitTestClient
app = FluxLit(title="Test")
client = FluxLitTestClient(app)
assert client.api_get("/healthz").status_code == 200
For Streamlit, you can run FluxLit’s Streamlit entrypoint via AppTest:
at = client.streamlit(target="my_app:app", extra_sys_path=".")
Development (contributors)
See Contributing on Read the Docs for setup, docs builds, and PR expectations.
pip install -e ".[dev]"
ruff check src tests && ruff format src tests
python -m pytest
python -m mypy src/fluxlit
Features: today vs planned
Available in current alphas
- Single public port; managed Streamlit subprocess
- Gateway:
/api→ FastAPI; HTTP + WebSocket proxy to Streamlit @app.page+st.navigationintegration; optionaldiscover_pagesfor package layoutsfluxlit new,dev,run,doctor,buildfluxlit.toml/[tool.fluxlit]project defaults;ApiClient.get_model/post_model- Request id propagation (
X-Request-ID) and optional API access logging - Optional
fluxlit[auth]: JWT (HS256 + JWKS/RS256), OIDC + BFF login routes, Streamlit code exchange +ApiClienthelpers,FluxLit.make_jwt_bearer/attach_oidc_login, forward-auth helpers, opt-in security headers (see Auth recipes) - Typed package; Ruff + Mypy + Pytest in-tree; CI on GitHub Actions
Planned (see Roadmap on Read the Docs)
- Hardened reload (Streamlit lifecycle), first-class health/metrics
- Deeper session/cookie presets and deployment cookbooks
- Official Docker/Kubernetes examples beyond
fluxlit buildtemplates - OpenAPI-generated client (optional)
Philosophy
FluxLit should stay Pythonic (explicit, typed, easy to reason about), production-minded (proxy-safe, observable, deployable), and honest about Streamlit’s process/WebSocket model until a native ASGI path is proven.
License
MIT — see pyproject.toml metadata.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fluxlit-0.3.0.tar.gz.
File metadata
- Download URL: fluxlit-0.3.0.tar.gz
- Upload date:
- Size: 60.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c99740fc66fe81a02e0e1332ada97ce8396fa94ba1ebcd80b391c97224be2a0
|
|
| MD5 |
3efd2c964378bcb7c12eb1f8c1d094ae
|
|
| BLAKE2b-256 |
2aad189c2b01db000d158f1315c03c486464b92550255193b6c0f39d2fac114b
|
File details
Details for the file fluxlit-0.3.0-py3-none-any.whl.
File metadata
- Download URL: fluxlit-0.3.0-py3-none-any.whl
- Upload date:
- Size: 43.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
73d7bf8227ff8b79220b973e787dc70e271bba1190e319d4e1b14d85ca0eb9eb
|
|
| MD5 |
b8354035cd99e9e895f6dad5738b36d6
|
|
| BLAKE2b-256 |
d39a800c640b9b8398d137a0494f94d30fa02dcb9f7d523143e681f12dc91a8e
|