# Streamlit in Snowflake

Build Python data apps that run inside Snowflake — no separate hosting, no connection strings. The app executes in Snowpark using its own role and warehouse, so it only ever sees what that role can SELECT.

## What it is

Streamlit in Snowflake (SiS) runs a Streamlit app entirely inside Snowflake. The Python code, the compute, and the data access all live in the account: there is no external web server to provision, no credentials to store, and no egress of RWD data to a third-party host. You write a normal Streamlit script, deploy it as a Snowflake object, and Snowsight serves it.

This is different from running Streamlit *externally* (on your laptop, a container, or a VM) and connecting *back* to Snowflake over the network. That external pattern needs a connector, stored credentials, and a network path, and it pulls data out of the platform. SiS avoids all of that — the app already runs where the data lives and inherits the calling role's grants.

## Access

> **Required role**
>
> Deploying and editing SiS apps requires the add-on role `RWD_STREAMLIT_DEVELOPER_ROLE`, granted on top of your base read role (it does not replace it). App compute runs on `RWD_PRODUCTION_AI_WH` (the `RWD_{ENV}_AI_WH` warehouse for a given environment).

The add-on role is requested via Slack in #ndp-rwd-connect-core. See the **How to Grant Access** page for the request flow and the **Snowflake Role Reference** page for where this role sits in the hierarchy and what your base role can read — the app inherits exactly those grants.

## Create and deploy an app

In Snowsight (`app.snowflake.com`), set your role to `RWD_STREAMLIT_DEVELOPER_ROLE` and warehouse to `RWD_PRODUCTION_AI_WH`, then:

1. Go to **Projects → Streamlit** and choose **+ Streamlit App**.
2. Pick the database and schema where the app object will live (use a schema your role can create objects in), and the query warehouse (`RWD_PRODUCTION_AI_WH`).
3. Edit the app in the built-in editor — left pane is code, right pane is the live preview. Saving redeploys automatically.
4. Share by granting USAGE on the Streamlit object to other roles. Viewers run the app under *their* role context, so they still only see what they're entitled to.

Exact menu labels move occasionally; if the layout differs, look for **Streamlit** under the **Projects** section of the left nav.

## Accessing RWD data

Inside a SiS app, grab the running Snowpark session with `get_active_session()` — no login, no connection config. Use `session.table(...)` or `session.sql(...)` to get a Snowpark DataFrame, then materialize it for display.

```python
import streamlit as st
from snowflake.snowpark.context import get_active_session

# Active session in a warehouse-runtime SiS app — uses the app's role + warehouse.
session = get_active_session()

# A Snowpark DataFrame (lazy): pick a table your role can SELECT.
df = session.table("CLINICOGENOMICS.LIMS_PUB.BASE_SIGNATERA")

# Or raw SQL when you need it:
# df = session.sql("SELECT * FROM CLINICOGENOMICS.LIMS_PUB.BASE_SIGNATERA LIMIT 100")

# Materialize to pandas and render.
st.dataframe(df.limit(100).to_pandas(), use_container_width=True)
```

> **The app reads as its role**
>
> Queries run under the role the app executes with. If a table or column isn't granted to that role, the query errors — it does not silently return empty. Provision the right base read role before pointing an app at a new schema.

## Example app

A small aggregate over a real RWD table, rendered as a metric and a chart:

```python
import streamlit as st
from snowflake.snowpark.context import get_active_session
from snowflake.snowpark.functions import col, count

st.title("Signatera — sample summary")

session = get_active_session()

# Lazy aggregate: count records per result code, pushed down to Snowflake.
summary = (
    session.table("CLINICOGENOMICS.LIMS_PUB.BASE_SIGNATERA")
    .group_by(col("RESULT_CODE"))
    .agg(count("*").alias("N"))
    .sort(col("N").desc())
)

pdf = summary.to_pandas()

total = int(pdf["N"].sum())
st.metric("Total results", f"{total:,}")

st.bar_chart(pdf, x="RESULT_CODE", y="N", use_container_width=True)
st.dataframe(pdf, use_container_width=True)
```

Aggregations stay server-side until `.to_pandas()`, so only the small result set crosses into the app — keep heavy work in Snowpark/SQL rather than pulling raw rows into pandas.

## Help

Questions on app setup, roles, or data access — ask in #rwd-snowflake-help.
