Page switching on login

Hello Team,
I have few doubts about page switching in streamlit and it is not working per my understanding.
My dir structure:
$HOME / login.py
$HOME / pages / app1.py app2.py app3.py

I am launching the main app (login.py) as streamlit run login.py (on default port 8501)
It presents the user with login form.
After validation of user and password, I am switching to app1.py
if login == ‘success’:
st.switch_page(‘pages/app1.py’)

app1.py gets loaded but the all pages(including login.py) are shown in sidebar navigation pane.
To hide the login page I have used the st-pages package(show_pages) to show only required pages in app1.py
app1.py:
from st_pages import show_pages, Page

show_pages([Page(‘pages/app1.py’), Page(‘pages/app2.py’), Page(‘pages/app3.py’)])

However, now If I launch another tab (localhost:8501) in the browser, I am taken directly to app1.py page instead of taking me to login page.
I suppose this is because of page switching.

So basically, If I don’t hide pages using show_pages in app1.py, on launching another tab (localhost:8501), I get to see login page everytime, but after successful login, I still see this login.py page in sidebar.
If I hide pages in app1.py to just show what I want, on launching a new tab I am taken directly to app1.py without the login form.

What I would like is on successful login only app1, app2 and app3 should be shown for navigation, but everytime I launch the a new tab on 8501 port, it should present me with login form.
This is important from users’ perspective because the users of my app should be presented with login page everytime and not for 1st time only, because right now only 1st user gets the login page and subsequent users are directly taken to app1 page.
Versions:
streamlit=1.30
python=3.11

Please let me know if more information is required.
Thanks.

1 Like

What user authentication method do you use? Google, streamlit-authenticator or something else.

2 Likes

Hello,
Thanks for your reply.
Presently, I am just checking in database whether the said user is present.
To illustrate further, I am attaching screenshots.

Case 1:
a. User login form:

b. User logs in:

c. User tries to browse the app in a new tab and gets a new login form:

d. User logs in again successfully.


As can be seen the user gets to see all pages in sidebar.

Case 2 (pages are hidden via show_pages() )
a. User login form (Same as Case 1 → a)
b. User logs in:
Note that the login page is now hidden in sidebar.

c. User tries to browse the app in a new tab but directly gets the app1 page loaded.

The only code change (app1.py) is the show_pages() method .
app1.py
st_pages.show_pages([Page(“pages/app1.py”), Page(“pages/app2.py”), Page(“pages/app3.py”)])

Please let me know if you need more info.
Is the show_pages() method causing this behavior or is it something else.
I checked the doc for show_pages() but didn’t get any info on this.
Also, if you have any docs on how to deploy such login based apps of switching/selective loading of pages, kindly let me know.
Thanks.

1 Like

I think this accomplishes what you’re looking for

(I added in the sleep()'s just to make it more obvious what’s happening)

from st_pages import hide_pages
from time import sleep
import streamlit as st


def log_in():
    st.session_state["logged_in"] = True
    hide_pages([])
    st.success("Logged in!")
    sleep(0.5)
    st.switch_page("pages/page1.py")


def log_out():
    st.session_state["logged_in"] = False
    st.success("Logged out!")
    sleep(0.5)


if not st.session_state.get("logged_in", False):
    hide_pages(["page1", "page2", "page3"])
    username = st.text_input("Username", key="username")
    password = st.text_input("Password", key="password", type="password")

    if username == "test" and password == "test":
        st.session_state["logged_in"] = True
        hide_pages([])
        st.success("Logged in!")
        sleep(0.5)
        st.switch_page("pages/page1.py")

else:
    st.write("Logged in!")
    st.button("log out", on_click=log_out)

Note that hide_pages([]) makes it so all the pages are visible again

3 Likes

Hello,
Thanks for your reply.
Yes, I think hide_pages() with empty list does the trick. Thank you for your solution.
However, just for my understanding I put show_pages([Page(“pages/app1.py”), Page(“pages/app2.py”), Page(“pages/app3.py”)]) in pages/app1.py but that didn’t work. In fact it showed all the pages. However, hide_pages([“login”]) works and hides the login page. I think I didn’t quite understand what show_pages() does ? Also, noticed that show_pages() takes a list of Pages while hide_pages() takes a list of strings(filenames) and that too without .py extension.

Thanks.

2 Likes

show_pages manipulates the dictionary that defines what makes the page list in the sidebar. This should be run once, and then it stays.

hide_pages adds some css to the page to hide certain pages from that list. This needs to be run on every page.

The differences in the API (filenames vs Pages) is just me being inconsistent.

2 Likes

Hi,

I am getting error.


pages

Version:
python 3.10.5
streamlit 1.30.0
st-pages 0.4.0

2 Likes

I am using st-pages 0.4.5.
Not sure if that’s the issue but please try updating version and check again.

2 Likes

@Dinesh_N Can you share a link to your repo?

It does appear, based on your screenshot, that you have \ instead of / – are you on a windows machine?

2 Likes

Hi,

Yes its Windows machine, after changing \ and /, both the cases same error (Must be the file path relative to the main script, from the directory)


1 Like

Can you share a link to your repo, show the full folder structure? It appears they you’re running app.py, but I don’t know where pages/page1.py is. Did you create a folder called pages and a file called page1.py in that folder? And is the folder pages in the same folder as app.py?

2 Likes

Here’s a new improved version which I recommend trying, which uses some new features from streamlit 1.31 GitHub - blackary/streamlit-login

2 Likes

Hello,
Thank you for the update. I will take a look at the new streamlit version 1.31.
Thanks.

2 Likes

@blackary,

Finally it worked, the issue is with the path as you said.

from st_pages import hide_pages
from time import sleep
import streamlit as st


def log_in():
    st.session_state["logged_in"] = True
    hide_pages([])
    st.success("Logged in!")
    sleep(0.5)
    st.switch_page(page1_path)


def log_out():
    st.session_state["logged_in"] = False
    st.success("Logged out!")
    sleep(0.5)


page1_path = r'E:\multi_page\pages\page1.py'
page2_path = r'E:\multi_page\pages\page2.py'


if not st.session_state.get("logged_in", False):
    hide_pages(["page1", "page2" ])
    username = st.text_input("Username", key="username")
    password = st.text_input("Password", key="password", type="password")

    if username == "test" and password == "test":
        st.session_state["logged_in"] = True
        hide_pages([])
        st.success("Logged in!")
        sleep(0.5)
        st.switch_page(page1_path)
else:
    st.write("Logged in!")
    st.button("log out", on_click=log_out)

But, can you suggest is this correct approach of passing the page as raw string.

3 Likes

It looks like there is a bug with Windows paths, which is being worked on – feel free to add feedback here StreamlitAPIException: Could not find page: 'pages/name.py'. Must be the file path relative to the main script, from the directory: .. · Issue #8070 · streamlit/streamlit · GitHub

4 Likes

I just popped over to a Windows computer to play around. Interestingly, I’m observing that st.switch_page breaks when it’s in a try-except block but not when it’s outside.

import streamlit as st

try:
    if st.button("pages/app2.py"):
        st.switch_page("pages/app2.py")
    st.page_link("pages/app2.py")
except:
    st.subheader("`dir/file.py` didn't work")

if st.button("pages/app2.py outside try"):
    st.switch_page("pages/app2.py")
st.page_link("pages/app2.py")

st.page_link works in both blocks, even if the order is reversed. st.switch_page works in the block without try-except, but not the other inside try. This holds true if the blocks are reversed in order.

(I’m building a larger test script, but this just seemed like a weird observation while I was fiddling…)

1 Like

Hello,
I have a similar use case, but st.switch_page() works if it is in else block of try.
E.g. code:
import streamlit as st
query_results = Get Data from Database
try:
next(query_results)
except StopIteration:
st.error(“No data”)
else:
st.switch_page(“pages/page1.py”)

1 Like

Hey @Sameer and @Dinesh_N !

We believe we have a fix to the switch_page. We have noticed in Windows that the auto complete of streamlit run app.py is actually appending a ./ to it. So streamlit run .\app.py. This has been causing the issues we can confirm, and we plan to release a patch with this fix.

Can you double verify how you are running the streamlit app and confirm this is the problem?

If it’s still an issue, I would love to get some more logs. I wrote a streamlit script to give us more information (using our internal APIs). If you can run this where the page variable is set and the exception you see occurs, I would appreciate it.

import os
import streamlit as st
from streamlit import source_util
from streamlit.runtime.scriptrunner import get_script_run_ctx


page = "pages/page_two.py" # MODIFY TO YOUR PAGE

ctx = get_script_run_ctx()
ctx_main_script = ""
if ctx:
  ctx_main_script = ctx.main_script_path

st.write("**Main Script File**")
st.text(f"\"{ctx_main_script}\"")

st.write("**Current Working Directory**")
st.text(f"\"{os.getcwd()}\"")

st.write("**Normalized Current Working Directory**")
st.text(f"\"{os.path.normpath(os.getcwd())}\"")

st.write("**Main Script Path**")
main_script_path = os.path.join(os.getcwd(), ctx_main_script)
st.text(f"\"{main_script_path}\"")

st.write("**Main Script Directory**")
main_script_directory = os.path.dirname(main_script_path)
st.text(f"\"{main_script_directory}\"")

st.write("**Normalized Path**")
page = os.path.normpath(page)
st.text(f"\"{page}\"")

st.write("**Requested Page**")
requested_page = os.path.join(main_script_directory, page)
st.text(f"\"{requested_page}\"")

st.write("**All Pages Page**")
all_app_pages = list(source_util.get_pages(ctx_main_script).values())
st.json(all_app_pages, expanded=True)

st.page_link(page, label="Page Two")
1 Like

@kmcgrady, I tried executing the code shared by you.

error

[
  {
    "page_script_hash": "c2e4464973d0f0b1af7b4bd7526c3e22",
    "page_name": "app",
    "icon": "",
    "script_path": "E:\\multi_page\\app.py"
  },
  {
    "page_script_hash": "a6e3c8fbeb2128e1daf91c1d6ac8f960",
    "page_name": "page1",
    "icon": "",
    "script_path": "E:\\multi_page\\pages\\page1.py"
  },
  {
    "page_script_hash": "235f0c94b9da5e7bff7d5ba062670150",
    "page_name": "page2",
    "icon": "",
    "script_path": "E:\\multi_page\\pages\\page2.py"
  }
]


1 Like

Hi @Dinesh_N ! Thank you this is very helpful.

I can tell from the output that streamlit is being run via streamlit run .\app.py

This is supplying the .\ to the path, and you can see for the Requested Page, we have E:\multi_page\.\pages\page2.py with the erroneous .\ in the middle. This is creating confusion.

So I have two pieces of good news.

  1. You can fix this now by ensuring the .\ is not included. So you would need to ensure streamlit run app.py without the .\. Hopefully, the code should work.
  2. We are releasing a patch to avoid this issue in the first place. We have found especially on windows, the .\ is added via autocomplete, and we should be more flexible.

Let me know if the (1) solves your issue, but if not, we should have 1.31.1 shortly to verify.

3 Likes