Multi-page label presentation

Summary

Multipage Apps are great, but it would hopefully be a big improvement, for a small change to make the labelling more flexible. Either a dictionary that could map filenames to a name on screen or simply allowing “x1_page_1.py” or "pg02_page_2.py.

Problem

The problem is acute when one has the normal structure and want to keep each app self contained, but be able to reference functions from the pages in the homepage.

SportsApp
├── homepage.py
├── pages
│   ├── 1_football.py
│   ├── 2_rugby.py

Our rugby app has lots of analytics but includes a function which pulls data from Snowflake and has relevant SQL queries built in and, say, returns the league scores.

Now on our homepage we want a summary of league scores for all the sports, but we can’t import a function because Python obviously restricts imports from files that have names that begin with numbers.

Suggested Solution

Allow an “x”, “pg”, or “page” before the number which would now make the app file importable by doing:

from pages.x2_rugby import league_scores

Or allow a dictionary which means we could change the labels in the sidebar afterwards.

Work Around

There are two work arounds:

  1. Just use the name but then the structure becomes alphanumeric which is fine in the sports app but clearly is unprofessional looking in a corporate settings
  2. Using the syntax below which isn’t recommended
imported_function = __import__("pages/2_rugby.py")

Hopefully this is a simple change that would have a lot of practical benefit.

Hey @pontificating_panda, welcome to our forum :balloon:

On the naming of pages for native multipage apps: you probably want to give a look at st-pages, a package developed by @blackary which exactly enables you to define pages <> scripts using a config file.

On importing util functions from pages: this is certainly doable as is (edit: not really if they are prefixed with digits, you’re right). But if the page triggers st commands, the imports may be janky and also trigger those UI commands… which is certainly not ideal. What we’ve been doing internally is to separate the “functional” utils to “UI” utils.

SportsApp
├── homepage.py
├── pages
│   ├── 1_football.py
│   ├── 2_rugby.py
├── data_sources
│   ├── big_query.py
│   ├── sql_queries.py

So that whenever you’re in a page, say, in 2_rugby.py you can still do:

from data_sources.big_query import big_query_connector
from data_sources.sql_queries import GET_RUGBY_DATA_SQL

conn = big_query_connector()
data = conn.get_dataframe_from_sql(GET_RUGBY_DATA_SQL)
...

What do you think?

2 Likes

Link to st-pages GitHub - blackary/st_pages: An experimental version of Streamlit Multi-Page Apps if you’re interested in reading more

Thanks @arnaud, I realised the issue you highlighted with triggering other streamlit commands a while later :slightly_frowning_face: in particular is was the page config bit of the UI.

I settled on a similar solution to what you say you’ve been doing internally, where there is a separate directory with utility functions - but to keep things clean each app has it’s own paired utilities file - that seems to work nicely enough.

Not sure if it could be possible to develop a way for those UI functions to not be triggered on import? This is probably an edge case, but I work in finance on a team with a mix of quants and non-quant people. As we develop more multi-page apps, the individual pages are being treated a bit like modules, and non-quant people want to mix and match. It’s why we find it cleaner to try and keep apps self contained. Like I say, an edge case in data science, but from a Snowflake user point of view could potentially be very helpful in corporate setting.

Thanks… I’ve been converting from Dash and am loving Streamlit.

1 Like

Thanks @blackary quick look I’ve had seems really helpful, will look a bit deeper.

Best,
David

Not sure if it could be possible to develop a way for those UI functions to not be triggered on import?

I certainly hear you on this, and more than once had to refactor apps which were nicely self-sufficient just in order to benefit from their logics. I did not love doing so! My initial thought is that this might end up confusing to not have those imports actually write anything, since the code running when importing a module is a standard and expected Python behaviour.

We’re always open for suggestions though, so feel free to file a feature request in our repository!

Thanks for your kind words,

1 Like

One quick option for “importing but not running” is to change each of your separate apps so that the actual app code is wrapped in a function, like this:

def main():
    ... # app code codes here

def other_functions():
   ....

if __name__ == "__main__":
    main()

This means that if you run the app as a script (which is what happens when you click on the page) it will run the main function, but if you just import it, it won’t run the code in your functions.

3 Likes

thanks @blackary I’d tried that with no success and from your example have worked out what I was doing wrong :man_facepalming: