Hello everyone
So far with Streamlit, we can assign custom user keys to widgets. It can be used to avoid duplicate errors when two widgets generate the same hash. In my experience, I haven’t encountered any other use for this parameter. However, I think it could be very powerful by its nature: bind a name to a widget.
TL;DR: The core idea is to bind Streamlit widgets to session state variables by using the key
parameter, and allow direct access to those state variables from HTTP queries or from an auto-generated REST or GraphQL API.
Session state
The key
parameter would be used to bind a widget to a state variable.
For this part, I’ll take as an example my implementation of a settings page with persisting state values.
What I don’t like in my code is this part:
state.input = st.text_input("Set input value.", state.input or "")
state.slider = st.slider("Set slider value.", 1, 10, state.slider)
state.radio = st.radio("Set radio value.", options, options.index(state.radio) if state.radio else 0)
state.checkbox = st.checkbox("Set checkbox value.", state.checkbox)
state.selectbox = st.selectbox("Select value.", options, options.index(state.selectbox) if state.selectbox else 0)
state.multiselect = st.multiselect("Select value(s).", options, state.multiselect)
It’s way to complex for what I want to do. However, using the key
parameter, we could instead write something like this:
import streamlit as st
import streamlit.state as state
options = ["Hello", "World", "Goodbye"]
state(my_var=1, another_var=2) # Create new state variables with default values
state(yet_another_var=5) # Can be called anytime in the code to create new state variables
state.my_var = 3 # Also assign a state value
st.text_input("Set input value.", key="my_input")
st.slider("Set slider value.", 1, 10, key="my_slider")
local_var = st.radio("Set radio value.", options, key="my_radio")
st.write(state.my_var, state.my_input, local_var)
In this example:
- You can assign values directly to your state, like
my_var
. - The user would not need to set state variables manually, it’d be handled internally thanks to the key argument.
- In case of undefined state variables, safe defaults like
options.index(state.radio) if state.radio else 0
wouldn’t be needed anymore. - The user would still be able to define local variables from widget return values, of course.
This session state would store serializable objects only.
- Storing complex objects should be managed by the caching system IMO.
- This would be a requirement to implement a REST API and HTTP query strings directly on top.
- Session state could be stored client-side as cookies, or server-side in a database or a file (PostgreSQL with credentials stored in streamlit config, sqlite, JSON, …).
- We could even imagine to include a import & export mechanism (JSON format).
HTTP query strings
Streamlit could retrieve parameters from query strings, and automatically assign values to your state. Widgets bound to a state variable thanks to their key
would be automatically updated.
The initial idea of query strings comes from this pull request.
import streamlit as st
import streamlit.state as state
options = ["Hello", "World", "Goodbye"]
state(my_var=3, another_var=2) # Set default state values, can be overridden by an HTTP query
state(yet_another_var=5) # Can be called anytime in the code to create new state variables
#state.my_var = 3 # Assign a state value that overrides HTTP query
st.text_input("Set input value.", key="my_input")
st.slider("Set slider value.", 1, 10, key="my_slider")
local_var = st.radio("Set radio value.", options, key="my_radio")
st.write(state.my_var, state.my_input, local_var)
An example of query string:
http://localhost:8501/?my_var=5&my_slider=4&my_radio=World
This way, you wouldn’t need to use a st.get_url()
to manually retrieve HTTP parameters, as those would be automatically assigned to your state. However, if one updates the values in the front-end, you would need a way to update automatically the query string, and more importantly, choose the state variables you want to update. We could do something like this to register state variables for update:
st.update_url("my_var", "my_radio")
Here I indicate which state variables will be updated in the query string. Unlike the aforementioned pull request, I don’t need to use any st.set_url()
function. Which means, if I update a state value, or a widget associated with a key, the query string would automatically change like so:
http://localhost:8501/?my_var=3&my_radio=Goodbye
If my_slider
is not manually set in the query string by the user, it won’t appear at all if not specified in update_url()
. IMO such function is needed when dealing with complex state values like DataFrames that can be ignored from the query string.
REST API
Like HTTP query strings, the REST API would directly set and retrieve values from state variables. Likewise, widgets bound to a state variable with their key
would be automatically updated.
I saw this great idea from this discussion: Streamlit restful app. From there, @thiago opened a GitHub issue and mentioned the idea of auto-generating a REST API. It would be so great to avoid that need of implementing boilerplate code to define routes, get and set values, and so on. Maybe for a finer control over your API, some function could be implemented later on, but I think providing a simple REST API remains possible with no additional function.
import streamlit as st
import streamlit.state as state
options = ["Hello", "World", "Goodbye"]
state(my_var=3, another_var=2) # Set default state values, can be overridden by an HTTP query or a REST value
state(yet_another_var=5) # Can be called anytime in the code to create new state variables
#state.my_var = 3 # Assign a state value that overrides HTTP query or REST value
st.text_input("Set input value.", key="my_input")
st.slider("Set slider value.", 1, 10, key="my_slider")
local_var = st.radio("Set radio value.", options, key="my_radio")
st.write(state.my_var, state.my_input, local_var)
And REST queries:
GET /rest/my_var
{ "my_var": 3 }
GET /rest/my_var/5
{ "my_var": 5 }
GET /rest/my_var/5?my_slider=4&my_radio=World
GET /rest/my_var?my_var=5&my_slider=4&my_radio=World
{ "my_var": 5 }
GET /rest/my_radio?my_var=5&my_slider=4&my_radio=World
{ "my_radio": "World" }
Here, the REST API would directly interact with state variables. And maybe with a persisting session we could do something like successive POSTs
/PUTs
instead of passing parameters when doing a GET
. Just a quick supposition.
Another (better?) approach would be to return the whole session state by default, or only registered state variables like suggested with HTTP queries.
With the python snipped above, we’d have this result:
GET /rest/?my_var=5&my_slider=4&my_radio=World
{
"state": {
"another_var": 2,
"my_radio": "World",
"my_slider": 4,
"my_var": 5,
"yet_another_var": 5
}
}
And with registered state variables:
st.register_rest("my_radio", "my_var")
We’d have:
GET /rest/?my_var=5&my_slider=4&my_radio=World
{
"state": {
"my_radio": "World",
"my_var": 5
}
}
GraphQL API
If implementing a REST API like above is possible, we could imagine to support GraphQL queries out of the box as well!
This query…
{
state {
my_radio,
my_var
}
}
… would return:
{
"state": {
"my_radio": "World",
"my_var": 5
}
}
That’s all folks!
They are definitely some corner cases I haven’t thought of, and maybe Streamlit wasn’t designed to be used this way. But if we could allow people to store state the way they want, and interact with it through auto generated APIs, just by naming widgets and nothing more, it could be a real killer feature!
I’ll update this post in case it needs any clarifications.