Source code for fluxlit.deep_links
"""Deep links to Streamlit pages: normalized query params and optional ``?page=`` routing."""
from __future__ import annotations
import logging
from collections.abc import Mapping, Sequence
from typing import Any, cast
from fluxlit.pages.records import PageRecord
from fluxlit.pages.slug import page_slug
from fluxlit.runtime.env_parse import truthy_env
_log = logging.getLogger(__name__)
def _scalar_query_value(raw: Any) -> str | None:
if raw is None:
return None
if isinstance(raw, list):
return str(raw[0]) if raw else None
return str(raw)
[docs]
def query_params(st: Any) -> dict[str, str]:
"""Return the current URL query as plain ``str`` values (first value if a list).
Streamlit (and ``AppTest``) may expose ``st.query_params`` values as strings or
lists when multiple values share a key. This helper normalizes to a single
``str`` per key so page logic and email-link prefill stay simple.
Missing or unreadable ``query_params`` yields an empty dict.
"""
qp = getattr(st, "query_params", None)
if qp is None:
return {}
keys: list[str]
try:
keys = [str(k) for k in qp.keys()]
except Exception as exc: # noqa: BLE001 — best-effort for mocks / older Streamlit
if truthy_env("FLUXLIT_DEBUG"):
_log.debug(
"query_params: could not list keys from st.query_params: %s", exc, exc_info=True
)
if isinstance(qp, dict):
keys = [str(k) for k in qp]
else:
return {}
out: dict[str, str] = {}
for k in keys:
try:
raw = qp.get(k) if hasattr(qp, "get") else qp[k]
except Exception as exc: # noqa: BLE001
if truthy_env("FLUXLIT_DEBUG"):
_log.debug(
"query_params: could not read key %r from st.query_params: %s",
k,
exc,
exc_info=True,
)
continue
v = _scalar_query_value(raw)
if v is not None:
out[k] = v
return out
[docs]
def match_nav_page(
params: Mapping[str, str],
pages: Sequence[tuple[str, str] | tuple[str, str, Any] | Any],
*,
page_key: str = "page",
) -> tuple[str, str] | None:
"""Resolve ``(path, title)`` from a ``page``-style query key and registered pages.
Compares the ``page_key`` value (default ``\"page\"``) to each page **title**,
**path**, and Streamlit-style **slug** (``\"/\"`` → ``\"home\"``; otherwise the
path without slashes). The first match in *pages* wins (order matches
:attr:`~fluxlit.app.FluxLit.pages`).
Returns ``None`` when the key is missing, empty, or unmatched.
"""
wanted = (params.get(page_key) or "").strip()
if not wanted:
return None
for row in pages:
if isinstance(row, PageRecord):
path, title = row.path, row.title
elif hasattr(row, "path") and hasattr(row, "title"):
duck = cast(Any, row)
path, title = str(duck.path), str(duck.title)
else:
path, title = row[0], row[1]
slug = page_slug(path)
if wanted == title or wanted == path or wanted == slug:
return (path, title)
w = wanted.strip("/")
p = path.strip("/")
if w and p and w == p:
return (path, title)
return None
__all__ = ["match_nav_page", "query_params"]