Skip to content

page

pinky_streamlit.page

Page initialization helper.

Provides setup_page() — the standard entry point for every page in a Streamlit in Snowflake app. Call it once at the top of each page, before any other Streamlit calls.

Typical usage::

from functools import partial
from pinky_streamlit import setup_page, load_analytic, PALETTES

messages_container, main_container, df = setup_page(
    session,
    loaders=[partial(load_analytic, _query_fn=query_data, key="data")],
    palette=PALETTES.PYTHIA,
)

try:
    with main_container:
        st.title("My page")
        st.dataframe(df)
except Exception as e:
    messages_container.error(str(e), icon="‼️")

AppFont dataclass

Font to embed as a base64 data URI — eliminates local/SiS rendering drift.

woff2_path is relative to core/ (same convention as load_static_asset) or an absolute path for app-level overrides.

Source code in src/pinky_streamlit/core/page.py
70
71
72
73
74
75
76
77
78
79
80
81
@dataclass
class AppFont:
    """Font to embed as a base64 data URI — eliminates local/SiS rendering drift.

    ``woff2_path`` is relative to ``core/`` (same convention as
    ``load_static_asset``) or an absolute path for app-level overrides.
    """

    family: str
    woff2_path: str
    weight: str = "100 900"
    style: str = "normal"

load_static_asset(relative_filepath)

Load a CSS asset once per process lifetime.

Path is relative to this module's directory (core/assets/).

Source code in src/pinky_streamlit/core/page.py
47
48
49
50
51
52
53
54
55
56
57
58
59
@st.cache_resource  # type: ignore[untyped-decorator]
def load_static_asset(relative_filepath: str) -> str:
    """Load a CSS asset once per process lifetime.

    Path is relative to this module's directory (core/assets/).
    """
    try:
        base = os.path.dirname(__file__)
        with open(os.path.join(base, relative_filepath)) as f:
            return f.read()
    except Exception as e:
        logger.error("[load_static_asset] Failed: %s", e, exc_info=True)
        raise

setup_page(session, loaders=None, show_progress=True, logo=None, palette=None, font=DEFAULT_FONT)

Initialize a Streamlit page: inject CSS, load data with progress bar.

Call once at the top of each page, before any other Streamlit calls. Each loader receives the session as its sole positional argument — use functools.partial to pre-fill additional parameters (query function, cache key, transform, etc.).

Container Runtime (recommended): Use palette.to_config_toml() to generate .streamlit/config.toml for your app project. This sets primaryColor natively (fixes slider track and date picker — see KNOWN LIMITATIONS in style.css) and handles custom fonts via fontFiles. Then pass font=None here::

# .streamlit/config.toml generated once:
#   PALETTES.PYTHIA.to_config_toml(font_files=["static/DMSans.woff2"])

messages, main, df = setup_page(
    session,
    loaders=[partial(load_analytic, _query_fn=query_data, key="data")],
    palette=PALETTES.PYTHIA,
    font=None,          # config.toml handles font in Container Runtime
)

Legacy SiS / local dev: font=DEFAULT_FONT injects DM Sans as a base64 @font-face — works without config.toml support but cannot fix the KNOWN LIMITATIONS.

Parameters:

Name Type Description Default
session Session

Active Snowpark session.

required
loaders list[Callable[[Session], Any]] | None

Callables of signature (session) -> Any, executed in order with a progress bar. Results are appended to the return list in declaration order.

None
palette Palette | None

Optional Palette to inject as CSS variables (overrides the default design-system colors for this page). Use palette.to_config_toml() to also set primaryColor natively in config.toml.

None
font AppFont | None

Font to embed as a base64 data URI (legacy SiS / local dev). Pass None when using Container Runtime with config.toml fontFiles — eliminates the base64 hack and local/SiS rendering drift. Defaults to DEFAULT_FONT (DM Sans variable) for backward compat.

DEFAULT_FONT

Returns:

Type Description
list[Any]

(messages_container, main_container, *loader_results)

list[Any]
  • messages_container: bare st.container for flash messages (errors, warnings). Write to it from the except block.
list[Any]
  • main_container: bare st.container for the page body. Wrap all rendering inside with main_container:.
list[Any]
  • loader_results: one element per loader, in declaration order.
Source code in src/pinky_streamlit/core/page.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def setup_page(
    session: snowpark.Session,
    loaders: list[Callable[[snowpark.Session], Any]] | None = None,
    show_progress: bool = True,
    logo: Logo | None = None,
    palette: Palette | None = None,
    font: AppFont | None = DEFAULT_FONT,
) -> list[Any]:
    """Initialize a Streamlit page: inject CSS, load data with progress bar.

    Call once at the top of each page, before any other Streamlit calls.
    Each loader receives the session as its sole positional argument — use
    functools.partial to pre-fill additional parameters (query function, cache
    key, transform, etc.).

    **Container Runtime (recommended):**
    Use ``palette.to_config_toml()`` to generate ``.streamlit/config.toml``
    for your app project. This sets ``primaryColor`` natively (fixes slider
    track and date picker — see KNOWN LIMITATIONS in ``style.css``) and
    handles custom fonts via ``fontFiles``. Then pass ``font=None`` here::

        # .streamlit/config.toml generated once:
        #   PALETTES.PYTHIA.to_config_toml(font_files=["static/DMSans.woff2"])

        messages, main, df = setup_page(
            session,
            loaders=[partial(load_analytic, _query_fn=query_data, key="data")],
            palette=PALETTES.PYTHIA,
            font=None,          # config.toml handles font in Container Runtime
        )

    **Legacy SiS / local dev:**
    ``font=DEFAULT_FONT`` injects DM Sans as a base64 ``@font-face`` — works
    without ``config.toml`` support but cannot fix the KNOWN LIMITATIONS.

    Args:
        session:      Active Snowpark session.
        loaders:      Callables of signature ``(session) -> Any``, executed in
                      order with a progress bar. Results are appended to the
                      return list in declaration order.
        palette:      Optional Palette to inject as CSS variables (overrides
                      the default design-system colors for this page).
                      Use ``palette.to_config_toml()`` to also set
                      ``primaryColor`` natively in ``config.toml``.
        font:         Font to embed as a base64 data URI (legacy SiS / local
                      dev). Pass ``None`` when using Container Runtime with
                      ``config.toml`` ``fontFiles`` — eliminates the base64
                      hack and local/SiS rendering drift. Defaults to
                      ``DEFAULT_FONT`` (DM Sans variable) for backward compat.

    Returns:
        ``(messages_container, main_container, *loader_results)``

        - ``messages_container``: bare ``st.container`` for flash messages
            (errors, warnings). Write to it from the ``except`` block.
        - ``main_container``: bare ``st.container`` for the page body.
            Wrap all rendering inside ``with main_container:``.
        - ``loader_results``: one element per loader, in declaration order.
    """
    if logo:
        logo_image_filepath = os.path.join(logo.images_filepath, logo.image)
        logo_icon_filepath = (
            os.path.join(logo.images_filepath, logo.icon_image)
            if logo.icon_image
            else None
        )
        st.logo(
            image=logo_image_filepath, size=logo.size, icon_image=logo_icon_filepath
        )

    css = load_static_asset(relative_filepath="./assets/style.css")
    st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)
    if font:
        st.markdown(
            f"<style>{_load_font_css(font.woff2_path, font.family, font.weight, font.style)}</style>",
            unsafe_allow_html=True,
        )
    if palette:
        st.markdown(
            f"<style>{_build_palette_css(palette)}</style>", unsafe_allow_html=True
        )

    messages_container = st.container(border=False)
    main_container = st.container(border=False, key="page-content")

    if not loaders:
        return [messages_container, main_container]

    results: list[Any] = []
    durations_ms: list[int] = []
    if show_progress:
        progress = main_container.progress(0)
    n = len(loaders)
    t_total = time.monotonic()
    for i, loader in enumerate(loaders):
        t0 = time.monotonic()
        results.append(loader(session))
        durations_ms.append(int((time.monotonic() - t0) * 1000))
        if show_progress:
            progress.progress(int((i + 1) / n * 100))
    if show_progress:
        progress.progress(100)
        progress.empty()

    _emit_page_telemetry(
        loaders, durations_ms, int((time.monotonic() - t_total) * 1000)
    )

    return [messages_container, main_container, *results]