Get url from streamlit.app

Hey there, I am using st.experimental_get_query_params() and would like to create a shareable link to the outputs of my app. If I have a fixed url, I can do that by attaching the url params to the base url, but I can’t figure out how the hosted public streamlit.app comes up with the base url. It didn’t seem to be based off the commit hash, so I’m not sure what else it could be.

st.markdown("[share](/?param=value)") should do.

If you actually need to get the full url as a string, you use streamlit-javascript like this:

import streamlit as st
from streamlit_javascript import st_javascript

url = st_javascript("await fetch('').then(r => window.parent.location.href)")

st.write(url)

Alternatively, this package GitHub - aghasemi/streamlit_js_eval: A custom Streamlit component to evaluate arbitrary Javascript expressions has a built-in method:

from streamlit_js_eval import get_page_location

st.write(get_page_location())
6 Likes

I didn’t realize this til now after building an app with more latency, but using the above js seems to be causing the app to run twice if I have the code at the very end.

That’s not too surprising to me, since the js might not return anything right away, and so the return value will change, and just like if you clicked a button on the page it will cause the app to rerun. In general, the solution to this is to use st.experimental_memo to cache slow data-gathering code, so then a rerun of the whole app should happen fast. This won’t solve all latency issues, but often helps.

So do you mean add a function to get the base url and decorate it with @st.experimental_memo? That way it would only rerun once? I guess I could use @st.cache for that, so I don’t really understand the use of the st.experimental_memo here.

Maybe the way I was trying to this function to get the domain url isn’t right. At the very end of my app, I get all the variables that are important to my app and combine that with the domain url to get a shareable link. I was running that st_javascript you suggested at the end which ended up making the whole script rerun, which isn’t ideal. Maybe I just need to have that js run at the very beginning and cache it so it doesn’t affect subsequent changes to widgets.

Oh well I just tried the answer by Goyo without getting the domain url and it seems to handle it correctly. I didn’t realize the domain would automatically get prepended? I’m not sure how that works, but seems to solve my use case.

st.markdown(f"[Share this app's output](?{url_params_str})")
1 Like

So, I couldn’t get any of the javascript components working.
But this built in works:

        import streamlit as st
        import urlib.parse
        sessions = st.runtime.get_instance()._session_mgr.list_active_sessions()
        req = st.runtime.get_instance()._session_mgr.get_active_session_info(sessions[0]).request
        joinme = (req.protocol, req.host, "", "", "", "")
        my_url = urllib.parse.urlunparse(joinme)

YMMV. I haven’t yet tried it on cloud, or any deployed env, but I don’t see why it shouldn’t work.

Looks nice! But accessing “_session_mgr” may not be future proof.
For e.g. if they change the variable name in future, we will end up with a fault.
Thanks for this though!

get_page_location and st_javascript("await fetch('').then(r => window.parent.location.href)") create a blank space, how should I remove it?

@Tiran It’s ultimately because any custom js like this requires an iframe to be inserted in the page. I can think of three ways to get around this, both of which just move the space to the bottom of the page.

Option 1, don’t call get_page_location() until after your other stuff is added to the page (might not be an option)

st.write("Before")
st.write("After")
loc = get_page_location()
st.write(loc)

Option 2, add the empty space to the sidebar:

st.write("Before")
st.write("After")
loc = get_page_location()
st.write(loc)

Option 3: Create two containers, one for the main content, and another just for the empty space

main = st.container()
footer = st.container()

with main:
    st.write("Before")
with footer:
    loc = get_page_location()
with main:
    st.write("After")
    st.write(loc)

I ended up writing a function to make it really easy to construct a sharable URL that I have used in a couple of my apps now.

# app_utils.py
from urllib.parse import quote_plus

def get_query_params_url(params_list, params_dict, **kwargs):
    """
    Create url params from alist of parameters and a dictionary with values.

    Args:
        params_list (str) :
            A list of parameters to get the value of from `params_dict`
        parmas_dict (dict) :
            A dict with values for the `parmas_list .
        **kwargs :
            Extra keyword args to add to the url
    """
    params_dict.update(kwargs)
    filtered_params_dict = {k: v for k, v in params_dict.items() if k in params_list}
    return "?" + "&".join(
        [
            f"{key}={quote_plus(str(value))}"
            for key, values in filtered_params_dict.items()
            for value in listify(values)
        ]
    )

def listify(o=None):
    if o is None:
        res = []
    elif isinstance(o, list):
        res = o
    elif isinstance(o, str):
        res = [o]
    else:
        res = [o]
    return res

Then you can construct the URL in your app like this:

# these are the variable names used in your app
vars_list = ["var1", ...]
url = get_query_params_url(vars_list, {**kwargs, **locals()})
st.markdown(f"[Share this app's output]({url})")

This will construct the URL for you, but then you need to be able to read those values when your app is run with that URL. Here is an example:

import streamlit as st
from app_utils import get_query_params_url

def app(**kwargs):
    # Streamlit returns a list of vales assigned in the url, so you want to wrap
    # default values in a list and index into them for when no url param is passed.
    # Example number input, 42 gets used as the default if no url param used
    default_var1 = int(kwargs.get("var1", [42])[0])
    var1 = st.number_input("Var 1", value=default_var1)
    # Example checkbox, defaults to False (checkbox unchecked)
    default_var2 = kwargs.get("var2", ["False"])[0] == "True"
    var2 = st.checkbox("Var 2", default_var2)
    # Example reading in multiple values from a list
    default_var3 = kwargs.get("var3", ["val1", "val2"])
    var3 = st.multiselect("Var 3", ["val1", "val2", "val3"], default_var3)

    # app logic
    st.markdown(f"var1: {var1}")
    st.markdown(f"var2: {var2}")
    st.markdown(f"var3: {var3}")

    vars_list = ["var1", "var2", "var3"]
    url = get_query_params_url(vars_list, {**kwargs, **locals()})
    st.markdown(f"[Share this app's output]({url})")

url_params = st.experimental_get_query_params()
app(**url_params)