Source code for fluxlit.pages.query
"""Typed query string parsing for Streamlit pages (Pydantic)."""
from __future__ import annotations
import logging
from typing import Any, TypeVar
from pydantic import BaseModel, TypeAdapter, ValidationError
from fluxlit.runtime.env_parse import truthy_env
ModelT = TypeVar("ModelT", bound=BaseModel)
_log = logging.getLogger(__name__)
[docs]
class Query:
"""Optional metadata for a query-shaped parameter (manifest / docs)."""
def __init__(self, *, description: str = "") -> None:
self.description = description
def _query_dict_from_st(st: Any) -> dict[str, Any]:
qp = getattr(st, "query_params", None)
if qp is None:
return {}
out: dict[str, Any] = {}
try:
keys = list(qp.keys())
except Exception as exc: # noqa: BLE001
if truthy_env("FLUXLIT_DEBUG"):
_log.debug(
"_query_dict_from_st: could not list keys from st.query_params: %s",
exc,
exc_info=True,
)
return {}
for k in keys:
key = str(k)
try:
raw = qp.get(key) if hasattr(qp, "get") else qp[key]
except Exception as exc: # noqa: BLE001
if truthy_env("FLUXLIT_DEBUG"):
_log.debug(
"_query_dict_from_st: could not read key %r: %s",
key,
exc,
exc_info=True,
)
continue
if isinstance(raw, list):
out[key] = raw[0] if len(raw) == 1 else raw
else:
out[key] = raw
return out
[docs]
def parse_query_params(
st: Any,
model: type[ModelT],
*,
strict: bool = False,
) -> ModelT:
"""Build *model* from ``st.query_params``.
For multi-value keys, Streamlit may expose a list: a single-element list is
coerced to that element; multiple values are passed through as a list.
On :class:`pydantic.ValidationError`, calls ``st.error`` with a short message unless
*strict* is true, in which case the exception is re-raised.
"""
raw = _query_dict_from_st(st)
try:
return model.model_validate(raw)
except ValidationError as e:
if strict:
raise
err = getattr(st, "error", None)
if callable(err):
err(f"Invalid query parameters: {e}")
try:
return model.model_validate({})
except ValidationError:
raise
def parse_query_params_adapter(
st: Any,
adapter: TypeAdapter[Any],
*,
strict: bool = False,
) -> Any:
"""Validate query params with a :class:`pydantic.TypeAdapter` (e.g. ``TypedDict``)."""
raw = _query_dict_from_st(st)
try:
return adapter.validate_python(raw)
except ValidationError as e:
if strict:
raise
err = getattr(st, "error", None)
if callable(err):
err(f"Invalid query parameters: {e}")
raise
def query_dict_for_manifest(st: Any) -> dict[str, Any]:
"""Return a JSON-friendly snapshot of current query keys (for manifest tooling)."""
return dict(_query_dict_from_st(st))
__all__ = ["Query", "parse_query_params", "parse_query_params_adapter", "query_dict_for_manifest"]