Assigning Random Key to Streamlit Checkbox

Hi, I have a problem using the unique key in streamlit function, for instance, st.selectbox(*args, key='key', **kwargs).

For my own purpose, I find it annoying to specify the key every time in the code. So, I decided to write a decorator function which automatically assign a random key to each of the selectbox. Here is my code:

class StreamlitWrapper:
    @staticmethod
    def _rdnkey_decorator(func):
        def wrapper(*args, **kwargs):
            # generate a key with 10 characters
            # rdn_key = rdnkey_generator.generate()
            rdn_key = ''.join([random.choice(string.ascii_letters) for _ in range(10)])
            kw = dict(
                key = rdn_key,
            )
            kw.update(kwargs)
            return func(*args, **kw)
        return wrapper

Then, I defined an inherited class

class MyClass(StreamlitWrapper):
    @StreamlitWrapper._rdnkey_decorator
    def selection(self, label='selection', **kwargs):
        return st.selectbox(label, **kwargs)

and used it in my application code. For example,

myclass = MyClass()
choice1 = myclass.selection()
choice2 = myclass.selection()

The above code gives the the “unique key error”, i.e.
"When a widget is created, it’s assigned an internal key based on
its structure. Multiple widgets with an identical structure will
result in the same internal key, which causes this error.

To fix this error, please pass a unique key argument to
st.selectbox." However, if I pass the key in the application code,

myclass = MyClass()
choice1 = myclass.selection()
choice2 = myclass.selection(key='no-problem') # Note here the key is updated by the random key.

It works perfectly fine. Do anyone have some idea what went wrong here?
Thanks a lot.

rdnkey_generator is undefined in your code. After fixing that, calling myclass.selection() raises TypeError because it is calling st.selectbox with only the label argument (kwargs is an empty dictionary).

Hi Goyo,
Thanks for your input. After checking, I have a typo in the decorator where I forgot to change kwargs to kw after updating the key. I also replace the line for generating random key.

However, when I

choice1 = myclass.selection()

The selectbox goes back to its default selection whenever I choose other option.

Your edited code still raises TypeError because it is calling st.selectbox with only the key argument.

Hi Goyo,
I forgot to add the options in the question. Please use this script. Thanks a lot!

import random
import string
import streamlit as st

class StreamlitWrapper:
    @staticmethod
    def _rdnkey_decorator(func):
        def wrapper(*args, **kwargs):
            rdn_key = ''.join([random.choice(string.ascii_letters)
                              for _ in range(10)])
            kw = dict(
                key=rdn_key,
            )
            kw.update(kwargs)
            return func(*args, **kw)
        return wrapper

class MyClass(StreamlitWrapper):
    def __init__(self):
        pass

    @StreamlitWrapper._rdnkey_decorator
    def selection(self, label='selection', **kwargs):
        st.write(kwargs['key'])
        return st.selectbox(label, ['11', '22', '33'], **kwargs)

myclass = MyClass()
choice1 = myclass.selection()

You should see an app like this but the selection goes back to 11 every time.

Each time the app reruns, a new instance of MyClass is created and its .selection() method called, which actually calls the wrapper. That calls generates a ney random key and instantiates a widget with that key. Since it is a new widget with a new key, it doesn’t know or care about the old widget, which no longer exists. You can avoid that by creating the instance only in the first run and storing it in session_state.

A second issue is that each time you call `myclass.

You can fix that by:

  • Making sure you have the same instance of MyClass on each run, for example by storing it in session_state only in the first run.
  • Moving the generation of the random key outside the wrapper.

Which yields this:

import random
import string
import streamlit as st

class StreamlitWrapper:
    @staticmethod
    def _rdnkey_decorator(func):
        def wrapper(*args, **kwargs):
            kw = dict(key=rdn_key)
            kw.update(kwargs)
            return func(*args, **kw)

        rdn_key = ''.join(
            [random.choice(string.ascii_letters) for _ in range(10)]
        )
        return wrapper

class MyClass(StreamlitWrapper):
    def __init__(self):
        pass

    @StreamlitWrapper._rdnkey_decorator
    def selection(self, label='selection', **kwargs):
        st.write(kwargs['key'])
        return st.selectbox(label, ['11', '22', '33'], **kwargs)

if "myclass" not in st.session_state:
    st.session_state.myclass = MyClass()
choice1 = st.session_state.myclass.selection()

But looking at the OP I don’t think this will give you the behavior you want. Let me think of a better way for some time.