Snowflake: Display Query Results in Streamlit

So far in this series I’ve discussed how to create a simple native Streamlit app in Snowflake and connecting an app to Snowflake. In this article I’ll show some additional Streamlit navigation features as well as how to populate form elements from Snowflake data.

st.set_page_config

In order for the application to use all possible screen real estate, I’ll first add the Streamlit set_page_config() function to the app. In a standalone Streamlit app, this function can be used to set the page title, favicon and add menu items such as “Help” or “Report a Bug”. For this demo, I’ll use set_page_config to set the container layout to “wide” so that the app can use as much space as possible on the screen.

#IMPORT STREAMLIT LIBRARY
import streamlit as st
#IMPORT SNOWPARK SESSION
from snowflake.snowpark.context import get_active_session

#CREATE A SESSION VARIABLE
session = get_active_session()

#UPDATE THE PAGE CONFIG SETTINGS
st.set_page_config(layout="wide")

Adding a Sidebar

For the next iteration of this app, I would like to return the results of entries in RAW_DATA.TBL_EVT_LOG and use a selection box to choose the event type to display. Since the EVT_TYPE column can be populated with multiple values dynamically loading the values from Snowflake makes the app data-driven rather than hard coded. To help with application flow, I’ll add the new selection box to a sidebar that can be collapsed if desired.

st.sidebar

Adding a sidebar menu in Streamlit is done with a single line of code and leveraging the power of Python’s “with” command and proper indentation, I can nest several other objects inside of the sidebar easily.

st.selectbox

Streamlit also provides a slick way to add a selection box to our menu sidebar with a single line of code with st.selectbox(). Additionally by setting a variable equal to the st.selectbox function, the chosen value is automagically stored in that variable. For this example, the EVT_TYPE value will be stored in a variable and then passed to a SQL Command in later step.

The values in st.selectbox can be either hardcoded or dynamically loaded from a dataframe. For this example, I’ll load a dataframe with distinct EVT_TYPE values and populate the selectbox.

Building the Sidebar

First I’ll need to query Snowflake and load the distinct EVT_TYPE values into a dataframe to use in the select box. The app runs in a different database from where TBL_EVT_LOG is stored, so I’ll need to use the fully-qualified table names in my queries for the app. As an alternative, I could create a view in the STREAMLIT_DB; however, to keep this demo focused on the app, I’ll hold off on that for now.

Once the data is loaded into a dataframe, I can use that dataframe to populate the selectbox options. The selected value from the selectbox will be stored in var_evt_type to use later for filtering the query results.

Putting all the sidebar code together looks like the following:

#ADD THE SIDEBAR AND SELECT BOX
with st.sidebar:
    #SET THE SQL COMMAND TO LOAD THE DATAFRAME
    selectbox_sql = """
        SELECT EVT_TYPE
        FROM ST_DEMO_DB.RAW_DATA.TBL_EVT_LOG
        GROUP BY EVT_TYPE;
        """
    #COLLECT THE DATA VALUES INTO A DF
    selectbox_df = session.sql(selectbox_sql).collect()

    #CREATE THE SELECT BOX; STORE SELECTED VALUE IN VAR_EVT_TYPE
    var_evt_type=st.selectbox(label="Select Event Type:",options=selectbox_df)

Querying Snowflake With Variables

Now that I have a method to populate a selectbox with the EVT_TYPE values I want to filter on, I’ll need to query the data in TBL_EVT_LOG using the var_evt_type variable in my WHERE clause. I would like to display the EVT_TYPE, EVT_DESC, EVT_TIMESTAMP and EVT_USERNAME in my app, but alias them to remove “EVT_” from the column names. To make the sql command dynamic, I’ll use Python string formatting to substitute the chosen value for EVT_TYPE from the select box into the SQL command.

Like the prior article, I’ll collect the data into a datafram and use st.dataframe to write the output to the screen. One change is the addition of use_container_width parameter which allows the dataframe to fill the entire app container. Doing so will make wider columns easier to read on screen.

#BUILD SQL COMMAND
sql = """
SELECT
    EVT_TYPE as TYPE,
    EVT_DESC as DESCRIPTION,
    EVT_TIMESTAMP as TIMESTAMP,
    EVT_USERNAME as USER    
FROM
    ST_DEMO_DB.RAW_DATA.TBL_EVT_LOG
WHERE
    EVT_TYPE = '{evt_typ}'
"""
#FORMAT SQL STRING WITH VARIABLE
sql = sql.format(evt_typ=var_evt_type)

#QUERY SNOWFLAKE
df = session.sql(sql).collect()

#WRITE TO SCREEN
st.dataframe(data=df,use_container_width=True)

With all the code updates complete, I’ll update the st.header value to “TBL_EVT_LOG Viewer” and run the app. The entire app is less than 50 lines – which includes comments and spacing for ease of reading.

#IMPORT STREAMLIT LIBRARY
import streamlit as st
#IMPORT SNOWPARK SESSION
from snowflake.snowpark.context import get_active_session

#CREATE A SESSION VARIABLE
session = get_active_session()

#UPDATE THE PAGE CONFIG SETTINGS
st.set_page_config(layout="wide")

#WRITE THE HEADER
st.header("TBL_EVT_LOG Viewer")

#ADD THE SIDEBAR AND SELECT BOX
with st.sidebar:
    #SET THE SQL COMMAND TO LOAD THE DATAFRAME
    selectbox_sql = """
        SELECT EVT_TYPE
        FROM ST_DEMO_DB.RAW_DATA.TBL_EVT_LOG
        GROUP BY EVT_TYPE;
        """
    #COLLECT THE DATA VALUES INTO A DF
    selectbox_df = session.sql(selectbox_sql).collect()

    #CREATE THE SELECT BOX; STORE SELECTED VALUE IN VAR_EVT_TYPE
    var_evt_type=st.selectbox(label="Select Event Type:",options=selectbox_df)

#BUILD SQL COMMAND
sql = """
SELECT
    EVT_TYPE as TYPE,
    EVT_DESC as DESCRIPTION,
    EVT_TIMESTAMP as TIMESTAMP,
    EVT_USERNAME as USER    
FROM
    ST_DEMO_DB.RAW_DATA.TBL_EVT_LOG
WHERE
    EVT_TYPE = '{evt_typ}'
"""
#FORMAT SQL STRING WITH VARIABLE
sql = sql.format(evt_typ=var_evt_type)

#QUERY SNOWFLAKE
df = session.sql(sql).collect()

#WRITE TO SCREEN
st.dataframe(data=df,use_container_width=True)
Completed App

Streamlit also handles re-running the app automatically when a selection is changed which simplifies the code. Notice in the clip below that as the selection is changed, the query is re-executed against Snowflake and the dataframe is updated.

Conclusion

Snowflake and Streamlit both continue to impress with tools that make doing app and data development streamlined and intuitive! In this demo I covered adding some navigation features, connecting to Snowflake to run parameterized queries and displaying those results to the app.

Streamlit has a number of additional tools such as charts and graphs, maps and the list goes on. Not all features are supported natively in Snowflake yet, but I can only imagine they’re coming soon!

Snowflake: Connecting Native Streamlit Apps

In the first article in this series, I covered how to add a simple “Hello, World!” sample Streamlit application to Snowflake. The power of this newer feature comes into play when using data from Snowflake in the Streamlit. With the app running natively on Snowflake the data never leaves the Snowflake ecosystem; therefore, saving on data transfer costs.

Connecting to Snowflake

Connecting a native Streamlit app to Snowflake can be done in two lines of code – one to import the proper Snowpark library and one to call the get_active_session function.

I’ll also use a shortcut for my code editing this time by editing the code directly in Snowflake rather than in my IDE or text editor. Once logged into Snowsight, I’ll switch to the app owner role, ST_DEMO_ROLE. Now when I run the MY_FIRST_STREAMLIT app, I have an option in the upper right corner of Snowsight to EDIT the app. By clicking that button, a text editor opens on the left side of the screen allowing me to edit my_first_streamlit.py within Snowsight.

Edit button in Snowsight

With the editor open, I’ll update the code as shown to connect to Snowflake and create a session variable.

#IMPORT STREAMLIT LIBRARY
import streamlit as st
#IMPORT SNOWPARK SESSION
from snowflake.snowpark.context import get_active_session

#CREATE A SESSION VARIABLE
session = get_active_session()

#WRITE THE HEADER
st.header("My First Streamlit App in Snowflake")

#WRITE HELLO STR_NAME
st.write("Hello, world!")

Clicking RUN in the upper right corner will save the code and apply the changes. If done correctly, the application shouldn’t look any different on the right side of the screen; however, we have now established a connection with our current Snowflake session.

Updated app in Snowflake

Displaying Session Information

Now that I’ve connected to my current session, I’ll add a few lines of code to write some session information such as current warehouse and current database to the screen using st.write() as done before with “Hello, world!”.

#IMPORT STREAMLIT LIBRARY
import streamlit as st
#IMPORT SNOWPARK SESSION
from snowflake.snowpark.context import get_active_session

#CREATE A SESSION VARIABLE
session = get_active_session()

#WRITE THE HEADER
st.header("My First Streamlit App in Snowflake")

#COLLECT SESSION INFORMATION
current_db = session.get_current_database()
current_schema = session.get_current_schema()
current_warehouse = session.get_current_database()
current_role = session.get_current_role()

#WRITE TO SCREEN
st.write(f"Current Database: {current_db}")
st.write(f"Current Schema: {current_schema}")
st.write(f"Current Warehouse: {current_warehouse}")
st.write(f"Current Role: {current_role}")
Updated app showing session information

Executing SQL Against Snowflake

Running SQL against Snowflake directly is one of the great benefits of native Streamlit! Streamlit has built-in features to easily display query results in a grid in the app. With the small changes below, I’ll show the same session information as before, but display it in a grid.

The first step is to build the SQL command and store it to a variable. Notice here I’m using the triple-quote Python formatting to make the SQL query a bit easier to read. Why did I not include the CURRENT_USER function? Just as the get_current_user Streamlit function is not support yet, CURRENT_USER would return only a NULL value.

#BUILD SQL COMMAND
sql = """
SELECT
    CURRENT_DATABASE() as current_database
    ,CURRENT_SCHEMA() as current_schema
    ,CURRENT_WAREHOUSE() as current_warehouse
    ,CURRENT_ROLE() as current_role;
"""

Next, I’ll execute the SQL in the session, collect it into a data frame variable, and write it to the screen using the Streamlit Dataframe Object. Executing SQL against Snowflake using the session variable created previously done by using the .sql() function of the session object. Notice the .collect() added to the end of the .sql() function. Adding collect() executes the command and stores the data output into the target variable.

#QUERY SNOWFLAKE
df = session.sql(sql).collect()

#WRITE TO SCREEN
st.dataframe(data=df)

Clicking RUN again will update the app and show the session information in the data grid.

#IMPORT STREAMLIT LIBRARY
import streamlit as st
#IMPORT SNOWPARK SESSION
from snowflake.snowpark.context import get_active_session

#CREATE A SESSION VARIABLE
session = get_active_session()

#WRITE THE HEADER
st.header("My First Streamlit App in Snowflake")

#BUILD SQL COMMAND
sql = """
SELECT
    CURRENT_DATABASE() as current_database
    ,CURRENT_SCHEMA() as current_schema
    ,CURRENT_WAREHOUSE() as current_warehouse
    ,CURRENT_ROLE() as current_role;
"""

#QUERY SNOWFLAKE
df = session.sql(sql).collect()

#WRITE TO SCREEN
st.dataframe(data=df)

Updated session information in a dataframe

Wrapping Up

Connecting a native Streamlit to Snowflake is quite simple, requiring only a few lines of code. In this example, I connected to my active Snowflake session and showed two methods for retrieving and displaying the session information.

In the next installment, I’ll show how to populate a drop-down box from Snowflake and then use the selected option to query my TBL_EVT_LOG table.

Snowflake: Native Streamlit App

Snowflake continues to impress with additional features being added at a rapid pace. The recent Large Language Model (LLM) announcement at Snowday with Snowflake Cortex is one of the many new features headed our way from Snowflake!

One recently added feature is the ability to add a Streamlit app directly to your Snowflake instance. Note that as of the time of this article, native Streamlit apps are only Snowflake accounts hosted on AWS. Additional details about Streamlit in Snowflake can be found in the Snowflake Documentation.

This article will show my preferred method for adding a simple Streamlit app to Snowflake and how to share the app with other users.

What is Streamlit?

Streamlit is a set of Python libraries that facilitate the rapid development of web applications. With built-in forms, file uploaders, charts, and many other tools, Streamlit takes a lot of the headache out of building an application and makes it simple to get a functional app set up quickly. The Streamlit site has a get set of documentation walking through installation and code references for developing apps.

Why Streamlit in Snowflake?

Snowflake announced its purchase of Streamlit in March 2022. So the next natural progression was for Snowflake to integrate Streamlit into its infrastructure – I would equate that to some of the other “marriages” in the tech world such as Microsoft’s SQL Server and .NET frameworks coupling together so well. Developers had already been using Streamlit to build apps that connected to Snowflake as a data source. With the two integrated, the data never has to leave Snowflake to run a Streamlit app saving on data costs.

Streamlit Setup in Snowflake

With the addition of Streamlit in Snowflake, the Snowsight UI received an upgrade to allow users to add a Streamlit app directly from Snowsight. Doing so requires a few things to be setup first such as a stage to host the Python files and a virtual warehouse for the app to use when running. Using the UI to set up the app also creates a very oddly named Streamlit app – resembling a bunch of random letters and numbers. While this is a quick way to add an app, I prefer to set one up manually and add what’s needed in an organized manner.

Snowflake Object Setup

Before creating the Streamlit app, the pre-requisite data objects must be in place. While it’s not necessary to set up everything I’ll show here, creating Streamlit-specific objects is my preference because it helps keep things orderly. The setup scripts are an extension of the demo setup I used in a previous demo.

Database and Warehouse Setup

For a native Streamlit app to run within Snowflake, the application needs a database to store the Python code and a warehouse for compute resources. I’ll add these using the ACCOUNTADMIN role, then transfer the ownership to my ST_DEMO_ROLE.

USE ROLE ACCOUNTADMIN;
/*CREATE NEW STREAMLIT DB*/
CREATE DATABASE STREAMLIT_DB;
/*CREATE NEW XSMALL VWH FOR STREAMLIT*/
CREATE WAREHOUSE STREAMLIT_XS_WH
    WAREHOUSE_SIZE=XSMALL;
/*XFER OWNERSHIP TO DEMO ROLE*/
GRANT OWNERSHIP ON DATABASE STREAMLIT_DB
    TO ROLE ST_DEMO_ROLE;
GRANT OWNERSHIP ON WAREHOUSE STREAMLIT_XS_WH
    TO ROLE ST_DEMO_ROLE;

Schema and Stage Setup

Next, I’ll create two new schemas in the STREAMLIT_DB database, one will include an internal stage where the Python files will be stored for the Streamlit app, and the other will be used for the app itself. The new stage will also have the DIRECTORY feature enabled to allow browsing of the files from Snowsight UI. Notice that since I’ve transferred ownership to ST_DEMO_ROLE, I’ll switch to that role for the next batch of code.

/*SWITCH ROLE*/
USE ROLE ST_DEMO_ROLE;
/*SWTICH DB AND WAREHOUSE*/
USE DATABASE STREAMLIT_DB;
USE WAREHOUSE STREAMLIT_XS_WH;
/*CREATE NEW SCHEMA FOR STAGES*/
CREATE SCHEMA STREAMLIT_STAGES;
/*SWITCH TO SCHEMA*/
USE SCHEMA STREAMLIT_STAGES;
/*CREATE STAGE WITH DIRECTORY*/
CREATE STAGE MY_FIRST_STREAMLIT_STG
    DIRECTORY = (ENABLE=TRUE);
/*CREATE NEW APPS SCHEMA*/
CREATE SCHEMA STREAMLIT_APPS;

Streamlit App Setup

With the database-specific objects in place, the next step is to create the Streamlit object. I’ll discuss adding the Python scripts in the next section; however, the STREAMLIT object can be created before uploading or adding the Python file. Creating a STREAMLIT object is no different from creating any other object in Snowflake as it can be done with a simple “CREATE STREAMLIT” statement.

In the code block below, I’m first switching to the new schema created in the section above and then creating my STREAMLIT object in that schema. Notice the different settings in the create statement:

ROOT_LOCATION

The ROOT_LOCATION setting defines the stage used to store the .py files for the Streamlit app. The value is prefixed with “@” and uses the fully qualified path to the stage created above.

MAIN_FILE

MAIN_FILE specifies the name of the root Python file for the application. The value is only the file name including the extension (.py) that is stored in the stage specified in the ROOT_LOCATION setting. The referenced MAIN_FILE does not have to be in the stage yet for the command to work to build the Streamlit; however, without the main file being present in the stage the application will not load.

QUERY_WAREHOUSE

The query warehouse is the virtual warehouse for the app to use when running the app, including any queries against the Snowflake database. The sample below references the warehouse setup in the previous steps.

/*CHANGE SCHEMA/*
USE SCHEMA STREAMLIT_APPS;

/*CREATE STREAMLIT APP*/
CREATE STREAMLIT MY_FIRST_STREAMLIT
    ROOT_LOCATION = '@STREAMLIT_DB.STREAMLIT_STAGES.MY_FIRST_STREAMLIT_STG'
    MAIN_FILE = 'my_first_streamlit.py'
    QUERY_WAREHOUSE = STREAMLIT_XS_WH
    COMMENT = 'Streamlit testing app';

Executing the code block above will create the Streamlit application in Snowflake, which can be seen under Streamlit in Snowsight. Even though the app is created the code to run it is not ready, so trying to execute will result in an error.

MY_FIRST_STREAMLIT App in Snowsight
MY_FIRST_STREAMLIT error

MY_FIRST_STREAMLIT.PY

To correct the error and allow the application to run, the my_first_streamlit.py file needs to be added to the stage created above. But what goes into the file? Thankfully, because all of the libraries are already loaded in Snowflake for Streamlit, there’s not much needed to create the app. For this initial demo, I will not have the app run any Snowflake queries, only write a header and “Hello, world!” to the screen.

Since an extensive IDE isn’t needed to create Python files, simply open an empty text file in any text editor and save the file as “my_first_streamlit.py”. Next, add the following lines of code to the file and save the file.

#IMPORT STREAMLIT LIBRARY
import streamlit as st

#WRITE THE HEADER
st.header("My First Streamlit App")

#WRITE HELLO WORLD
st.write("Hello, world!")

With the file saved, upload it to the stage location specified in the CREATE STREAMLIT command above. Once the file is uploaded, navigate back to the Streamlit option in Snowsight and click on the MY_FIRST_STREAMLIT app in the list which will load the app!

Uploading the Streamlit App code
Running the App in Snowflake
Running MY_FIRST_STREAMLIT in Snowflake Snowsight

Sharing the App With Other Account Users

Now the app is running natively in Snowflake and it can be shared with other users in the same account by granting at least the following:

  1. Usage on the STREAMLIT_DB
  2. Usage on the STREAMLIT_APPS Schema
  3. Usage on the STREAMLIT app itself

ST_APP_USERS Role

To show how this works, first I’ll set up an ST_APP_USERS role and add my user account to it. The role will not have any privileges granted when first created, so when I navigate to the STREAMLIT apps, I won’t see any in Snowsight.

/*CREATE APP USER ROLE*/
USE ROLE SECURITYADMIN;
CREATE ROLE ST_APP_USERS;

/*ADD USER TO ROLE*/
GRANT ROLE ST_APP_USERS TO USER EHEILMAN;

Minimum Privileges for Apps

At minimum, a role will need usage on the Database, Schema and Streamlit objects in order to run the app within Snowflake.

/*SWITCH BACK TO DEMO ROLE*/
USE ROLE ST_DEMO_ROLE;

/*GRANT USAGE ON THE APP*/
USE SCHEMA STREAMLIT_DB.STREAMLIT_APPS;
GRANT USAGE ON STREAMLIT MY_FIRST_STREAMLIT TO ROLE ST_APP_USERS;
--NOT ENOUGH PRIVILEGE FOR THE APP YET--

/*GRANT USAGE ON THE DATABASE*/
USE DATABASE STREAMLIT_DB;
GRANT USAGE ON DATABASE STREAMLIT_DB TO ROLE ST_APP_USERS;
--NOT ENOUGH PRIVILEGE FOR THE APP YET--

/*GRANT USAGE ON THE SCHEMA*/
GRANT USAGE ON SCHEMA STREAMLIT_APPS TO ROLE ST_APP_USERS;
--APP WORKS!--
MY_FIRST_STREAMLIT app for ST_APP_USERS role

Streamlit is a powerful powerful Python library that can be used to rapidly generate an app both inside and outside of Snowflake. I have used Streamlit locally with VisualStudio code to create a UI for one of my Python utility scripts. In less than 100 lines of code, I had a working UI that reduced the manual commands needed to run my utility.

In this demo, I discussed my preferred method to add a simple native Streamlit app to Snowflake. I created a separate database, schema, stage, and warehouse specific to hosting my Streamlit apps. Additionally, I discussed how to share the app with other users through a role.

In the next installment in this Streamlit series, I’ll show simple ways to add user input items, such as text boxes to the app as well as retrieve session information from the Snowflake instance where the app runs!