Streamlit pages: typing, Depends, and manifests¶
FluxLit 0.9 adds optional FastAPI-like patterns for Streamlit page handlers while
keeping the default (st, client) -> None contract unchanged.
Registration¶
Use page() as today, with optional metadata:
icon— forwarded tostreamlit.navigation/st.Pagewhere supported.tags— labels forbuild_page_manifest().page_meta— staticPageMeta(for examplepage_icon) merged intost.Pagewhere Streamlit accepts it. Globalstreamlit.set_page_configstill runs once at entry; per-run layout overrides are limited by Streamlit’s “first command” rule—seePageMeta.
Full records are available as page_records; pages
remains a list of (path, title, handler) tuples for backward compatibility.
PageMeta vs set_page_config vs st.Page¶
Streamlit only guarantees streamlit.set_page_config before any other Streamlit command
on a run. FluxLit calls global set_page_config once from the Streamlit entrypoint using
:class:~fluxlit.config.FluxlitSettings (title and streamlit_page_config). Per-page
:class:~fluxlit.pages.meta.PageMeta is applied in two different ways:
Field |
|
Returned |
|
|---|---|---|---|
|
Merged into |
Same keys are best-effort via |
|
|
Ignored until the handler runs. |
Sidebar caption via |
— |
|
Used for manifest / nav ordering ( |
Same as static if returned and validated. |
|
Official Streamlit multipage and set_page_config behaviour is described in the
Streamlit multipage apps <https://docs.streamlit.io/develop/concepts/multipage-apps>_ and
st.set_page_config <https://docs.streamlit.io/develop/api-reference/configuration/st.set_page_config>_
documentation.
Dependency injection¶
Import Depends, Header, and
Cookie from fluxlit (or fluxlit.pages).
Built-ins:
st,client,FluxLit,FluxlitSettings,FluxLitPublicUrls,ApiClient.Session store: pass
session_store=...toFluxLitand annotate a parameter withSessionStoreto receive the same instance.Depends:
def page(st, client, user: Annotated[User, Depends(load_user)]): ...whereload_user()returns the dependency value. Async callables and awaitable returns are supported whenFLUXLIT_ASYNC_PAGE_DEPENDS=1/async_page_dependsis on: if Streamlit (or another caller) already has an asyncio loop on the current thread, FluxLit runs the coroutine on a short-lived side thread with its own loop (avoiding nested-loop errors). Keep async deps fast and thread-safe; do not assume you are on Streamlit’s main thread.Depends
use_cache(0.13.3+): defaultTrueresolves each dependency callable once per page run (keyed by callable identity).Depends(fn, use_cache=False)invokesfnon every injection site in the same run:counter = {"n": 0} def bump() -> int: counter["n"] += 1 return counter["n"] @app.page("/") def page(st, client, a: int = Depends(bump), b: int = Depends(bump, use_cache=False)): ...
Header: resolved in order: (1)
FLUXLIT_TEST_PAGE_OVERRIDES/ test kwargs, (2) the map fromset_page_header_context(), (3) when present,st.context.headersfrom Streamlit (HTTP requests only). The gateway does not put browser headers into the Streamlit process by default.Cookie: resolved in order: (1) overrides /
FLUXLIT_TEST_PAGE_OVERRIDES, (2)set_page_cookie_context(), (3)st.context.cookies(Streamlit 1.30+). Cookie names are matched case-insensitively. The gateway does not merge arbitrary browser cookies onto the internal hop unless your deployment forwards them and Streamlit exposes them onst.context.
Optional HTTP forwarding: set FLUXLIT_GATEWAY_FORWARD_CLIENT_HEADERS_TO_STREAMLIT
to a comma-separated or JSON list of lowercase header names (for example traceparent).
FluxLit copies only those names from the browser onto the gateway → Streamlit HTTP
hop so st.context.headers and Header() can read them. authorization, cookie,
and hop-by-hop / X-Forwarded-* names are rejected. WebSocket requests already forward
most browser headers on a curated path—see :mod:fluxlit.gateway.websocket_proxy.
Explicit bridge: for sensitive forward-auth patterns, prefer mapping trusted headers into
set_page_header_context() from code that runs in the Streamlit process,
with strict allowlists and redaction—never log raw Authorization or session cookies.
Claims-style models: use Depends with a callable that returns a Pydantic model;
pair with your FastAPI JWT stack on the API side—FluxLit does not parse JWTs in Streamlit
unless you supply the callable.
Async Depends and header resolution (0.10)¶
Situation |
Behaviour |
|---|---|
Sync |
Invoked normally; return value injected. |
Async / awaitable deps, flag off |
|
Async / awaitable deps, flag on, no running asyncio loop on thread |
Resolved via |
Async / awaitable deps, flag on, running loop on thread |
Resolved on a short-lived daemon thread with its own |
Keep async deps idempotent and fast; they must not rely on thread-local state tied to
Streamlit’s main script thread. If resolution exceeds the join limit, FluxLit raises
:class:TimeoutError; the daemon thread may still run until the coroutine completes, so
avoid unbounded work in async dependencies.
Query and session state¶
parse_query_params()builds a Pydantic model fromst.query_params(withstrict=Truein tests to surfacepydantic.ValidationError).SessionModelmaps Pydantic models tost.session_state.hydrate_url_session_typed()validates URL-session blobs as a model.
Manifest and CLI¶
build_page_manifest()returns JSON-serializable metadata (manifest_version1,manifest_stabilitystable).fluxlit pages manifest [--target module:attr]prints JSON using project config when--targetis omitted.fluxlit pages validate [--target ...] [--strict]exits 0 when the manifest is JSON-serializable, there are no duplicate page paths or clashing url_path slugs (e.g./avs/a/), and (ifstrict_page_signaturesis on, or--strictis passed) every page handler passes strict signature checks; exits 1 with errors printed to stdout for CI. Registration-time :meth:~fluxlit.app.FluxLit.pagerejects duplicate paths and slug collisions early.
Strict registration¶
Set FLUXLIT_STRICT_PAGE_SIGNATURES=1 or strict_page_signatures
so unknown page parameters raise at decorator time.
Experimental generator pages¶
When FLUXLIT_EXPERIMENTAL_YIELD_PAGES=1, a generator handler runs next() twice in
one script execution (setup yield, then body). This is experimental; prefer plain
functions unless you understand rerun semantics.
Reruns re-execute the whole script: both next() calls happen again, so generator
state does not survive reruns like a session. Do not rely on the generator for
teardown-critical resources unless you understand that every rerun repeats setup.
The first yielded value and the final return/second yield may each carry PageMeta
(breadcrumb, etc.); invalid dicts are surfaced with st.error when validation fails.
Optional st typing (Protocol)¶
:class:~fluxlit.streamlit.page.PageFn keeps st typed as typing.Any so apps are not
forced to satisfy the full Streamlit surface. For stricter local typing, define a
typing.Protocol with only the members your page uses (e.g. title, write,
sidebar) and annotate def home(st: MySt, client): .... You can also import
streamlit only under typing.TYPE_CHECKING in shared modules and use string
annotations. FluxLit does not change the default PageFn generic to avoid widespread
false positives from partial stubs.
Static typing¶
FluxLit is a typing.Generic over your settings type for static
checkers, for example FluxLit[MySettings] with a custom FluxlitSettings
subclass.
See also¶
Testing —
FluxLitTestClient.streamlit(..., page_overrides=...).Deep links and query parameters —
match_nav_pageand?page=.URL session continuity (no cookies) —
SessionStoreand URL-bound sessions.