# Copyright 2018-2022 Streamlit Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from urllib.parse import urlparse from textwrap import dedent from streamlit.scriptrunner import get_script_run_ctx from streamlit.proto import ForwardMsg_pb2 from streamlit.proto import PageConfig_pb2 from streamlit.elements import image from streamlit.errors import StreamlitAPIException from streamlit.util import lower_clean_dict_keys GET_HELP_KEY = "get help" REPORT_A_BUG_KEY = "report a bug" ABOUT_KEY = "about" def set_page_config( page_title=None, page_icon=None, layout="centered", initial_sidebar_state="auto", menu_items=None, ): """ Configures the default settings of the page. .. note:: This must be the first Streamlit command used in your app, and must only be set once. Parameters ---------- page_title: str or None The page title, shown in the browser tab. If None, defaults to the filename of the script ("app.py" would show "app โ€ข Streamlit"). page_icon : Anything supported by st.image or str or None The page favicon. Besides the types supported by `st.image` (like URLs or numpy arrays), you can pass in an emoji as a string ("๐Ÿฆˆ") or a shortcode (":shark:"). If you're feeling lucky, try "random" for a random emoji! Emoji icons are courtesy of Twemoji and loaded from MaxCDN. layout: "centered" or "wide" How the page content should be laid out. Defaults to "centered", which constrains the elements into a centered column of fixed width; "wide" uses the entire screen. initial_sidebar_state: "auto" or "expanded" or "collapsed" How the sidebar should start out. Defaults to "auto", which hides the sidebar on mobile-sized devices, and shows it otherwise. "expanded" shows the sidebar initially; "collapsed" hides it. menu_items: dict Configure the menu that appears on the top-right side of this app. The keys in this dict denote the menu item you'd like to configure: - "Get help": str or None The URL this menu item should point to. If None, hides this menu item. - "Report a Bug": str or None The URL this menu item should point to. If None, hides this menu item. - "About": str or None A markdown string to show in the About dialog. If None, only shows Streamlit's default About text. Example ------- >>> st.set_page_config( ... page_title="Ex-stream-ly Cool App", ... page_icon="๐ŸงŠ", ... layout="wide", ... initial_sidebar_state="expanded", ... menu_items={ ... 'Get Help': 'https://www.extremelycoolapp.com/help', ... 'Report a bug': "https://www.extremelycoolapp.com/bug", ... 'About': "# This is a header. This is an *extremely* cool app!" ... } ... ) """ msg = ForwardMsg_pb2.ForwardMsg() if page_title: msg.page_config_changed.title = page_title if page_icon: if page_icon == "random": page_icon = get_random_emoji() msg.page_config_changed.favicon = image.image_to_url( page_icon, width=-1, # Always use full width for favicons clamp=False, channels="RGB", output_format="auto", image_id="favicon", allow_emoji=True, ) if layout == "centered": layout = PageConfig_pb2.PageConfig.CENTERED elif layout == "wide": layout = PageConfig_pb2.PageConfig.WIDE else: raise StreamlitAPIException( f'`layout` must be "centered" or "wide" (got "{layout}")' ) msg.page_config_changed.layout = layout if initial_sidebar_state == "auto": initial_sidebar_state = PageConfig_pb2.PageConfig.AUTO elif initial_sidebar_state == "expanded": initial_sidebar_state = PageConfig_pb2.PageConfig.EXPANDED elif initial_sidebar_state == "collapsed": initial_sidebar_state = PageConfig_pb2.PageConfig.COLLAPSED else: raise StreamlitAPIException( '`initial_sidebar_state` must be "auto" or "expanded" or "collapsed" ' + f'(got "{initial_sidebar_state}")' ) msg.page_config_changed.initial_sidebar_state = initial_sidebar_state if menu_items is not None: lowercase_menu_items = lower_clean_dict_keys(menu_items) validate_menu_items(lowercase_menu_items) menu_items_proto = msg.page_config_changed.menu_items set_menu_items_proto(lowercase_menu_items, menu_items_proto) ctx = get_script_run_ctx() if ctx is None: return ctx.enqueue(msg) def get_random_emoji(): import random # Emojis recommended by https://share.streamlit.io/rensdimmendaal/emoji-recommender/main/app/streamlit.py # for the term "streamlit". Watch out for zero-width joiners, # as they won't parse correctly in the list() call! RANDOM_EMOJIS = list( "๐Ÿ”ฅโ„ข๐ŸŽ‰๐Ÿš€๐ŸŒŒ๐Ÿ’ฃโœจ๐ŸŒ™๐ŸŽ†๐ŸŽ‡๐Ÿ’ฅ๐Ÿคฉ๐Ÿค™๐ŸŒ›๐Ÿค˜โฌ†๐Ÿ’ก๐Ÿคช๐Ÿฅ‚โšก๐Ÿ’จ๐ŸŒ ๐ŸŽŠ๐Ÿฟ๐Ÿ˜›๐Ÿ”ฎ๐ŸคŸ๐ŸŒƒ๐Ÿƒ๐Ÿพ๐Ÿ’ซโ–ช๐ŸŒด๐ŸŽˆ๐ŸŽฌ๐ŸŒ€๐ŸŽ„๐Ÿ˜โ˜”โ›ฝ๐Ÿ‚๐Ÿ’ƒ๐Ÿ˜Ž๐Ÿธ๐ŸŽจ๐Ÿฅณโ˜€๐Ÿ˜๐Ÿ…ฑ๐ŸŒž๐Ÿ˜ป๐ŸŒŸ๐Ÿ˜œ๐Ÿ’ฆ๐Ÿ’…๐Ÿฆ„๐Ÿ˜‹๐Ÿ˜‰๐Ÿ‘ป๐Ÿ๐Ÿคค๐Ÿ‘ฏ๐ŸŒปโ€ผ๐ŸŒˆ๐Ÿ‘Œ๐ŸŽƒ๐Ÿ’›๐Ÿ˜š๐Ÿ”ซ๐Ÿ™Œ๐Ÿ‘ฝ๐Ÿฌ๐ŸŒ…โ˜๐Ÿท๐Ÿ‘ญโ˜•๐ŸŒš๐Ÿ’๐Ÿ‘…๐Ÿฅฐ๐Ÿœ๐Ÿ˜Œ๐ŸŽฅ๐Ÿ•บโ•๐Ÿงกโ˜„๐Ÿ’•๐Ÿปโœ…๐ŸŒธ๐Ÿšฌ๐Ÿค“๐Ÿนยฎโ˜บ๐Ÿ’ช๐Ÿ˜™โ˜˜๐Ÿค โœŠ๐Ÿค—๐Ÿต๐Ÿคž๐Ÿ˜‚๐Ÿ’ฏ๐Ÿ˜๐Ÿ“ป๐ŸŽ‚๐Ÿ’—๐Ÿ’œ๐ŸŒŠโฃ๐ŸŒ๐Ÿ˜˜๐Ÿ’†๐Ÿค‘๐ŸŒฟ๐Ÿฆ‹๐Ÿ˜ˆโ›„๐Ÿšฟ๐Ÿ˜Š๐ŸŒน๐Ÿฅด๐Ÿ˜ฝ๐Ÿ’‹๐Ÿ˜ญ๐Ÿ–ค๐Ÿ™†๐Ÿ‘โšช๐Ÿ’Ÿโ˜ƒ๐Ÿ™ˆ๐Ÿญ๐Ÿ’ป๐Ÿฅ€๐Ÿš—๐Ÿคง๐Ÿ๐Ÿ’Ž๐Ÿ’“๐Ÿค๐Ÿ’„๐Ÿ’–๐Ÿ”žโ‰โฐ๐Ÿ•Š๐ŸŽงโ˜ โ™ฅ๐ŸŒณ๐Ÿพ๐Ÿ™‰โญ๐Ÿ’Š๐Ÿณ๐ŸŒŽ๐Ÿ™Š๐Ÿ’ธโค๐Ÿ”ช๐Ÿ˜†๐ŸŒพโœˆ๐Ÿ“š๐Ÿ’€๐Ÿ โœŒ๐Ÿƒ๐ŸŒต๐Ÿšจ๐Ÿ’‚๐Ÿคซ๐Ÿคญ๐Ÿ˜—๐Ÿ˜„๐Ÿ’๐Ÿ‘๐Ÿ™ƒ๐Ÿ––๐Ÿ’ž๐Ÿ˜…๐ŸŽ…๐Ÿ„๐Ÿ†“๐Ÿ‘‰๐Ÿ’ฉ๐Ÿ”Š๐ŸคทโŒš๐Ÿ‘ธ๐Ÿ˜‡๐Ÿšฎ๐Ÿ’๐Ÿ‘ณ๐Ÿฝ๐Ÿ’˜๐Ÿ’ฟ๐Ÿ’‰๐Ÿ‘ ๐ŸŽผ๐ŸŽถ๐ŸŽค๐Ÿ‘—โ„๐Ÿ”๐ŸŽต๐Ÿค’๐Ÿฐ๐Ÿ‘“๐Ÿ„๐ŸŒฒ๐ŸŽฎ๐Ÿ™‚๐Ÿ“ˆ๐Ÿš™๐Ÿ“๐Ÿ˜ต๐Ÿ—ฃโ—๐ŸŒบ๐Ÿ™„๐Ÿ‘„๐Ÿš˜๐Ÿฅบ๐ŸŒ๐Ÿกโ™ฆ๐Ÿ’๐ŸŒฑ๐Ÿ‘‘๐Ÿ‘™โ˜‘๐Ÿ‘พ๐Ÿฉ๐Ÿฅถ๐Ÿ“ฃ๐Ÿผ๐Ÿคฃโ˜ฏ๐Ÿ‘ต๐Ÿซโžก๐ŸŽ€๐Ÿ˜ƒโœ‹๐Ÿž๐Ÿ™‡๐Ÿ˜น๐Ÿ™๐Ÿ‘ผ๐Ÿโšซ๐ŸŽ๐Ÿช๐Ÿ”จ๐ŸŒผ๐Ÿ‘†๐Ÿ‘€๐Ÿ˜ณ๐ŸŒ๐Ÿ“–๐Ÿ‘ƒ๐ŸŽธ๐Ÿ‘ง๐Ÿ’‡๐Ÿ”’๐Ÿ’™๐Ÿ˜žโ›…๐Ÿป๐Ÿด๐Ÿ˜ผ๐Ÿ—ฟ๐Ÿ—โ™ ๐Ÿฆโœ”๐Ÿค–โ˜ฎ๐Ÿข๐ŸŽ๐Ÿ’ค๐Ÿ˜€๐Ÿบ๐Ÿ˜๐Ÿ˜ด๐Ÿ“บโ˜น๐Ÿ˜ฒ๐Ÿ‘๐ŸŽญ๐Ÿ’š๐Ÿ†๐Ÿ‹๐Ÿ”ต๐Ÿ๐Ÿ”ด๐Ÿ””๐Ÿง๐Ÿ‘ฐโ˜Ž๐Ÿ†๐Ÿคก๐Ÿ ๐Ÿ“ฒ๐Ÿ™‹๐Ÿ“Œ๐Ÿฌโœ๐Ÿ”‘๐Ÿ“ฑ๐Ÿ’ฐ๐Ÿฑ๐Ÿ’ง๐ŸŽ“๐Ÿ•๐Ÿ‘Ÿ๐Ÿฃ๐Ÿ‘ซ๐Ÿ‘๐Ÿ˜ธ๐Ÿฆ๐Ÿ‘๐Ÿ†—๐ŸŽฏ๐Ÿ“ข๐Ÿšถ๐Ÿฆ…๐Ÿง๐Ÿ’ข๐Ÿ€๐Ÿšซ๐Ÿ’‘๐ŸŸ๐ŸŒฝ๐ŸŠ๐ŸŸ๐Ÿ’๐Ÿ’ฒ๐Ÿ๐Ÿฅ๐Ÿธโ˜โ™ฃ๐Ÿ‘Šโš“โŒ๐Ÿฏ๐Ÿˆ๐Ÿ“ฐ๐ŸŒง๐Ÿ‘ฟ๐Ÿณ๐Ÿ’ท๐Ÿบ๐Ÿ“ž๐Ÿ†’๐Ÿ€๐Ÿค๐Ÿšฒ๐Ÿ”๐Ÿ‘น๐Ÿ™๐ŸŒท๐Ÿ™Ž๐Ÿฅ๐Ÿ’ต๐Ÿ”๐Ÿ“ธโš โ“๐ŸŽฉโœ‚๐Ÿผ๐Ÿ˜‘โฌ‡โšพ๐ŸŽ๐Ÿ’”๐Ÿ”โšฝ๐Ÿ’ญ๐ŸŒ๐Ÿท๐Ÿโœ–๐Ÿ‡๐Ÿ“๐ŸŠ๐Ÿ™๐Ÿ‘‹๐Ÿค”๐ŸฅŠ๐Ÿ—ฝ๐Ÿ‘๐Ÿ˜๐Ÿฐ๐Ÿ’๐Ÿดโ™€๐Ÿฆ๐Ÿ“โœ๐Ÿ‘‚๐Ÿด๐Ÿ‘‡๐Ÿ†˜๐Ÿ˜ก๐Ÿ‰๐Ÿ‘ฉ๐Ÿ’Œ๐Ÿ˜บโœ๐Ÿผ๐Ÿ’๐Ÿถ๐Ÿ‘บ๐Ÿ–•๐Ÿ‘ฌ๐Ÿ‰๐Ÿป๐Ÿพโฌ…โฌโ–ถ๐Ÿ‘ฎ๐ŸŒโ™‚๐Ÿ”ธ๐Ÿ‘ถ๐Ÿฎ๐Ÿ‘ชโ›ณ๐Ÿ๐ŸŽพ๐Ÿ•๐Ÿ‘ด๐Ÿจ๐ŸŠ๐Ÿ”นยฉ๐ŸŽฃ๐Ÿ‘ฆ๐Ÿ‘ฃ๐Ÿ‘จ๐Ÿ‘ˆ๐Ÿ’ฌโญ•๐Ÿ“น๐Ÿ“ท" ) # Also pick out some vanity emojis. ENG_EMOJIS = [ "๐ŸŽˆ", # st.balloons ๐ŸŽˆ๐ŸŽˆ "๐Ÿค“", # Abhi "๐Ÿˆ", # Amey "๐Ÿšฒ", # Thiago "๐Ÿง", # Matteo "๐Ÿฆ’", # Ken "๐Ÿณ", # Karrie "๐Ÿ•น๏ธ", # Jonathan "๐Ÿ‡ฆ๐Ÿ‡ฒ", # Henrikh "๐ŸŽธ", # Guido "๐Ÿฆˆ", # Austin "๐Ÿ’Ž", # Emiliano "๐Ÿ‘ฉโ€๐ŸŽค", # Naomi "๐Ÿง™โ€โ™‚๏ธ", # Jon "๐Ÿป", # Brandon "๐ŸŽŽ", # James # TODO: Solicit emojis from the rest of Streamlit ] # Weigh our emojis 10x, cuz we're awesome! # TODO: fix the random seed with a hash of the user's app code, for stability? return random.choice(RANDOM_EMOJIS + 10 * ENG_EMOJIS) def set_menu_items_proto(lowercase_menu_items, menu_items_proto): if GET_HELP_KEY in lowercase_menu_items: if lowercase_menu_items[GET_HELP_KEY] is not None: menu_items_proto.get_help_url = lowercase_menu_items[GET_HELP_KEY] else: menu_items_proto.hide_get_help = True if REPORT_A_BUG_KEY in lowercase_menu_items: if lowercase_menu_items[REPORT_A_BUG_KEY] is not None: menu_items_proto.report_a_bug_url = lowercase_menu_items[REPORT_A_BUG_KEY] else: menu_items_proto.hide_report_a_bug = True if ABOUT_KEY in lowercase_menu_items: if lowercase_menu_items[ABOUT_KEY] is not None: menu_items_proto.about_section_md = dedent(lowercase_menu_items[ABOUT_KEY]) def validate_menu_items(dict): for k, v in dict.items(): if not valid_menu_item_key(k): raise StreamlitAPIException( "We only accept the keys: " f'"Get help", "Report a bug", and "About" ("{k}" is not a valid key.)' ) if v is not None: if not valid_url(v) and k != ABOUT_KEY: raise StreamlitAPIException(f'"{v}" is a not a valid URL!') def valid_menu_item_key(key): return key in [GET_HELP_KEY, REPORT_A_BUG_KEY, ABOUT_KEY] def valid_url(url): """ This code is copied and pasted from: https://stackoverflow.com/questions/7160737/how-to-validate-a-url-in-python-malformed-or-not """ try: result = urlparse(url) return all([result.scheme, result.netloc]) except: return False