Mise à jour de Monitor.py et autres scripts
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# 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.
|
||||
126
myenv/lib/python3.11/site-packages/streamlit/commands/echo.py
Normal file
126
myenv/lib/python3.11/site-packages/streamlit/commands/echo.py
Normal file
@@ -0,0 +1,126 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# 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 __future__ import annotations
|
||||
|
||||
import ast
|
||||
import contextlib
|
||||
import re
|
||||
import textwrap
|
||||
import traceback
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from streamlit.runtime.metrics_util import gather_metrics
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable
|
||||
|
||||
_SPACES_RE = re.compile("\\s*")
|
||||
_EMPTY_LINE_RE = re.compile("\\s*\n")
|
||||
|
||||
|
||||
@gather_metrics("echo")
|
||||
@contextlib.contextmanager
|
||||
def echo(code_location="above"):
|
||||
"""Use in a `with` block to draw some code on the app, then execute it.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
code_location : "above" or "below"
|
||||
Whether to show the echoed code before or after the results of the
|
||||
executed code block.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> import streamlit as st
|
||||
>>>
|
||||
>>> with st.echo():
|
||||
>>> st.write('This code will be printed')
|
||||
|
||||
"""
|
||||
from streamlit import code, empty, source_util, warning
|
||||
|
||||
if code_location == "below":
|
||||
show_code = code
|
||||
show_warning = warning
|
||||
else:
|
||||
placeholder = empty()
|
||||
show_code = placeholder.code
|
||||
show_warning = placeholder.warning
|
||||
|
||||
try:
|
||||
# Get stack frame *before* running the echoed code. The frame's
|
||||
# line number will point to the `st.echo` statement we're running.
|
||||
frame = traceback.extract_stack()[-3]
|
||||
filename, start_line = frame.filename, frame.lineno or 0
|
||||
|
||||
# Read the file containing the source code of the echoed statement.
|
||||
with source_util.open_python_file(filename) as source_file:
|
||||
source_lines = source_file.readlines()
|
||||
|
||||
# Use ast to parse the Python file and find the code block to display
|
||||
root_node = ast.parse("".join(source_lines))
|
||||
line_to_node_map: dict[int, Any] = {}
|
||||
|
||||
def collect_body_statements(node: ast.AST) -> None:
|
||||
if not hasattr(node, "body"):
|
||||
return
|
||||
for child in ast.iter_child_nodes(node):
|
||||
# If child doesn't have "lineno", it is not something we could display
|
||||
if hasattr(child, "lineno"):
|
||||
line_to_node_map[child.lineno] = child
|
||||
collect_body_statements(child)
|
||||
|
||||
collect_body_statements(root_node)
|
||||
|
||||
# In AST module the lineno (line numbers) are 1-indexed,
|
||||
# so we decrease it by 1 to lookup in source lines list
|
||||
echo_block_start_line = line_to_node_map[start_line].body[0].lineno - 1
|
||||
echo_block_end_line = line_to_node_map[start_line].end_lineno
|
||||
lines_to_display = source_lines[echo_block_start_line:echo_block_end_line]
|
||||
|
||||
code_string = textwrap.dedent("".join(lines_to_display))
|
||||
|
||||
# Run the echoed code...
|
||||
yield
|
||||
|
||||
# And draw the code string to the app!
|
||||
show_code(code_string, "python")
|
||||
|
||||
except FileNotFoundError as err:
|
||||
show_warning("Unable to display code. %s" % err)
|
||||
|
||||
|
||||
def _get_initial_indent(lines: Iterable[str]) -> int:
|
||||
"""Return the indent of the first non-empty line in the list.
|
||||
If all lines are empty, return 0.
|
||||
"""
|
||||
for line in lines:
|
||||
indent = _get_indent(line)
|
||||
if indent is not None:
|
||||
return indent
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _get_indent(line: str) -> int | None:
|
||||
"""Get the number of whitespaces at the beginning of the given line.
|
||||
If the line is empty, or if it contains just whitespace and a newline,
|
||||
return None.
|
||||
"""
|
||||
if _EMPTY_LINE_RE.match(line) is not None:
|
||||
return None
|
||||
|
||||
match = _SPACES_RE.match(line)
|
||||
return match.end() if match is not None else 0
|
||||
@@ -0,0 +1,238 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# 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 __future__ import annotations
|
||||
|
||||
import os
|
||||
from itertools import dropwhile
|
||||
from pathlib import Path
|
||||
from typing import Literal, NoReturn
|
||||
|
||||
import streamlit as st
|
||||
from streamlit.errors import NoSessionContext, StreamlitAPIException
|
||||
from streamlit.file_util import get_main_script_directory, normalize_path_join
|
||||
from streamlit.navigation.page import StreamlitPage
|
||||
from streamlit.runtime.metrics_util import gather_metrics
|
||||
from streamlit.runtime.scriptrunner import (
|
||||
RerunData,
|
||||
ScriptRunContext,
|
||||
get_script_run_ctx,
|
||||
)
|
||||
|
||||
|
||||
@gather_metrics("stop")
|
||||
def stop() -> NoReturn: # type: ignore[misc]
|
||||
"""Stops execution immediately.
|
||||
|
||||
Streamlit will not run any statements after `st.stop()`.
|
||||
We recommend rendering a message to explain why the script has stopped.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> import streamlit as st
|
||||
>>>
|
||||
>>> name = st.text_input("Name")
|
||||
>>> if not name:
|
||||
>>> st.warning('Please input a name.')
|
||||
>>> st.stop()
|
||||
>>> st.success("Thank you for inputting a name.")
|
||||
|
||||
"""
|
||||
ctx = get_script_run_ctx()
|
||||
|
||||
if ctx and ctx.script_requests:
|
||||
ctx.script_requests.request_stop()
|
||||
# Force a yield point so the runner can stop
|
||||
st.empty()
|
||||
|
||||
|
||||
def _new_fragment_id_queue(
|
||||
ctx: ScriptRunContext,
|
||||
scope: Literal["app", "fragment"],
|
||||
) -> list[str]:
|
||||
if scope == "app":
|
||||
return []
|
||||
|
||||
else: # scope == "fragment"
|
||||
curr_queue = ctx.fragment_ids_this_run
|
||||
|
||||
# If st.rerun(scope="fragment") is called during a full script run, we raise an
|
||||
# exception. This occurs, of course, if st.rerun(scope="fragment") is called
|
||||
# outside of a fragment, but it somewhat surprisingly occurs if it gets called
|
||||
# from within a fragment during a run of the full script. While this behvior may
|
||||
# be surprising, it seems somewhat reasonable given that the correct behavior of
|
||||
# calling st.rerun(scope="fragment") in this situation is unclear to me:
|
||||
# * Rerunning just the fragment immediately may cause weirdness down the line
|
||||
# as any part of the script that occurs after the fragment will not be
|
||||
# executed.
|
||||
# * Waiting until the full script run completes before rerunning the fragment
|
||||
# seems odd (even if we normally do this before running a fragment not
|
||||
# triggered by st.rerun()) because it defers the execution of st.rerun().
|
||||
# * Rerunning the full app feels incorrect as we're seemingly ignoring the
|
||||
# `scope` argument.
|
||||
# With these issues and given that it seems pretty unnatural to have a
|
||||
# fragment-scoped rerun happen during a full script run to begin with, it seems
|
||||
# reasonable to just disallow this completely for now.
|
||||
if not curr_queue:
|
||||
raise StreamlitAPIException(
|
||||
'scope="fragment" can only be specified from `@st.fragment`-decorated '
|
||||
"functions during fragment reruns."
|
||||
)
|
||||
|
||||
assert (
|
||||
new_queue := list(
|
||||
dropwhile(lambda x: x != ctx.current_fragment_id, curr_queue)
|
||||
)
|
||||
), (
|
||||
"Could not find current_fragment_id in fragment_id_queue. This should never happen."
|
||||
)
|
||||
|
||||
return new_queue
|
||||
|
||||
|
||||
@gather_metrics("rerun")
|
||||
def rerun( # type: ignore[misc]
|
||||
*, # The scope argument can only be passed via keyword.
|
||||
scope: Literal["app", "fragment"] = "app",
|
||||
) -> NoReturn:
|
||||
"""Rerun the script immediately.
|
||||
|
||||
When ``st.rerun()`` is called, Streamlit halts the current script run and
|
||||
executes no further statements. Streamlit immediately queues the script to
|
||||
rerun.
|
||||
|
||||
When using ``st.rerun`` in a fragment, you can scope the rerun to the
|
||||
fragment. However, if a fragment is running as part of a full-app rerun,
|
||||
a fragment-scoped rerun is not allowed.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
scope : "app" or "fragment"
|
||||
Specifies what part of the app should rerun. If ``scope`` is ``"app"``
|
||||
(default), the full app reruns. If ``scope`` is ``"fragment"``,
|
||||
Streamlit only reruns the fragment from which this command is called.
|
||||
|
||||
Setting ``scope="fragment"`` is only valid inside a fragment during a
|
||||
fragment rerun. If ``st.rerun(scope="fragment")`` is called during a
|
||||
full-app rerun or outside of a fragment, Streamlit will raise a
|
||||
``StreamlitAPIException``.
|
||||
|
||||
"""
|
||||
|
||||
if scope not in ["app", "fragment"]:
|
||||
raise StreamlitAPIException(
|
||||
f"'{scope}'is not a valid rerun scope. Valid scopes are 'app' and 'fragment'."
|
||||
)
|
||||
|
||||
ctx = get_script_run_ctx()
|
||||
|
||||
if ctx and ctx.script_requests:
|
||||
query_string = ctx.query_string
|
||||
page_script_hash = ctx.page_script_hash
|
||||
|
||||
ctx.script_requests.request_rerun(
|
||||
RerunData(
|
||||
query_string=query_string,
|
||||
page_script_hash=page_script_hash,
|
||||
fragment_id_queue=_new_fragment_id_queue(ctx, scope),
|
||||
is_fragment_scoped_rerun=scope == "fragment",
|
||||
)
|
||||
)
|
||||
# Force a yield point so the runner can do the rerun
|
||||
st.empty()
|
||||
|
||||
|
||||
@gather_metrics("switch_page")
|
||||
def switch_page(page: str | Path | StreamlitPage) -> NoReturn: # type: ignore[misc]
|
||||
"""Programmatically switch the current page in a multipage app.
|
||||
|
||||
When ``st.switch_page`` is called, the current page execution stops and
|
||||
the specified page runs as if the user clicked on it in the sidebar
|
||||
navigation. The specified page must be recognized by Streamlit's multipage
|
||||
architecture (your main Python file or a Python file in a ``pages/``
|
||||
folder). Arbitrary Python scripts cannot be passed to ``st.switch_page``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
page: str, Path, or st.Page
|
||||
The file path (relative to the main script) or an st.Page indicating
|
||||
the page to switch to.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
Consider the following example given this file structure:
|
||||
|
||||
>>> your-repository/
|
||||
>>> ├── pages/
|
||||
>>> │ ├── page_1.py
|
||||
>>> │ └── page_2.py
|
||||
>>> └── your_app.py
|
||||
|
||||
>>> import streamlit as st
|
||||
>>>
|
||||
>>> if st.button("Home"):
|
||||
>>> st.switch_page("your_app.py")
|
||||
>>> if st.button("Page 1"):
|
||||
>>> st.switch_page("pages/page_1.py")
|
||||
>>> if st.button("Page 2"):
|
||||
>>> st.switch_page("pages/page_2.py")
|
||||
|
||||
.. output ::
|
||||
https://doc-switch-page.streamlit.app/
|
||||
height: 350px
|
||||
|
||||
"""
|
||||
|
||||
ctx = get_script_run_ctx()
|
||||
|
||||
if not ctx or not ctx.script_requests:
|
||||
# This should never be the case
|
||||
raise NoSessionContext()
|
||||
|
||||
page_script_hash = ""
|
||||
if isinstance(page, StreamlitPage):
|
||||
page_script_hash = page._script_hash
|
||||
else:
|
||||
# Convert Path to string if necessary
|
||||
if isinstance(page, Path):
|
||||
page = str(page)
|
||||
|
||||
main_script_directory = get_main_script_directory(ctx.main_script_path)
|
||||
requested_page = os.path.realpath(
|
||||
normalize_path_join(main_script_directory, page)
|
||||
)
|
||||
all_app_pages = ctx.pages_manager.get_pages().values()
|
||||
|
||||
matched_pages = [p for p in all_app_pages if p["script_path"] == requested_page]
|
||||
|
||||
if len(matched_pages) == 0:
|
||||
raise StreamlitAPIException(
|
||||
f"Could not find page: `{page}`. Must be the file path relative to the main script, from the directory: `{os.path.basename(main_script_directory)}`. Only the main app file and files in the `pages/` directory are supported."
|
||||
)
|
||||
|
||||
page_script_hash = matched_pages[0]["page_script_hash"]
|
||||
|
||||
# We want to reset query params (with exception of embed) when switching pages
|
||||
with ctx.session_state.query_params() as qp:
|
||||
qp.clear()
|
||||
|
||||
ctx.script_requests.request_rerun(
|
||||
RerunData(
|
||||
query_string=ctx.query_string,
|
||||
page_script_hash=page_script_hash,
|
||||
)
|
||||
)
|
||||
# Force a yield point so the runner can do the rerun
|
||||
st.empty()
|
||||
@@ -0,0 +1,169 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# 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 __future__ import annotations
|
||||
|
||||
import urllib.parse as parse
|
||||
from typing import Any
|
||||
|
||||
from streamlit.errors import StreamlitAPIException
|
||||
from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
|
||||
from streamlit.runtime.metrics_util import gather_metrics
|
||||
from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
|
||||
from streamlit.runtime.state.query_params import (
|
||||
EMBED_OPTIONS_QUERY_PARAM,
|
||||
EMBED_QUERY_PARAM,
|
||||
EMBED_QUERY_PARAMS_KEYS,
|
||||
)
|
||||
|
||||
|
||||
@gather_metrics("experimental_get_query_params")
|
||||
def get_query_params() -> dict[str, list[str]]:
|
||||
"""Return the query parameters that is currently showing in the browser's URL bar.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
The current query parameters as a dict. "Query parameters" are the part of the URL that comes
|
||||
after the first "?".
|
||||
|
||||
Example
|
||||
-------
|
||||
Let's say the user's web browser is at
|
||||
`http://localhost:8501/?show_map=True&selected=asia&selected=america`.
|
||||
Then, you can get the query parameters using the following:
|
||||
|
||||
>>> import streamlit as st
|
||||
>>>
|
||||
>>> st.experimental_get_query_params()
|
||||
{"show_map": ["True"], "selected": ["asia", "america"]}
|
||||
|
||||
Note that the values in the returned dict are *always* lists. This is
|
||||
because we internally use Python's urllib.parse.parse_qs(), which behaves
|
||||
this way. And this behavior makes sense when you consider that every item
|
||||
in a query string is potentially a 1-element array.
|
||||
|
||||
"""
|
||||
ctx = get_script_run_ctx()
|
||||
if ctx is None:
|
||||
return {}
|
||||
ctx.mark_experimental_query_params_used()
|
||||
# Return new query params dict, but without embed, embed_options query params
|
||||
return _exclude_keys_in_dict(
|
||||
parse.parse_qs(ctx.query_string, keep_blank_values=True),
|
||||
keys_to_exclude=EMBED_QUERY_PARAMS_KEYS,
|
||||
)
|
||||
|
||||
|
||||
@gather_metrics("experimental_set_query_params")
|
||||
def set_query_params(**query_params: Any) -> None:
|
||||
"""Set the query parameters that are shown in the browser's URL bar.
|
||||
|
||||
.. warning::
|
||||
Query param `embed` cannot be set using this method.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
**query_params : dict
|
||||
The query parameters to set, as key-value pairs.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
To point the user's web browser to something like
|
||||
"http://localhost:8501/?show_map=True&selected=asia&selected=america",
|
||||
you would do the following:
|
||||
|
||||
>>> import streamlit as st
|
||||
>>>
|
||||
>>> st.experimental_set_query_params(
|
||||
... show_map=True,
|
||||
... selected=["asia", "america"],
|
||||
... )
|
||||
|
||||
"""
|
||||
ctx = get_script_run_ctx()
|
||||
if ctx is None:
|
||||
return
|
||||
ctx.mark_experimental_query_params_used()
|
||||
|
||||
msg = ForwardMsg()
|
||||
msg.page_info_changed.query_string = _ensure_no_embed_params(
|
||||
query_params, ctx.query_string
|
||||
)
|
||||
ctx.query_string = msg.page_info_changed.query_string
|
||||
ctx.enqueue(msg)
|
||||
|
||||
|
||||
def _exclude_keys_in_dict(
|
||||
d: dict[str, Any], keys_to_exclude: list[str]
|
||||
) -> dict[str, Any]:
|
||||
"""Returns new object but without keys defined in keys_to_exclude."""
|
||||
return {
|
||||
key: value for key, value in d.items() if key.lower() not in keys_to_exclude
|
||||
}
|
||||
|
||||
|
||||
def _extract_key_query_params(
|
||||
query_params: dict[str, list[str]], param_key: str
|
||||
) -> set[str]:
|
||||
"""Extracts key (case-insensitive) query params from Dict, and returns them as Set of str."""
|
||||
return {
|
||||
item.lower()
|
||||
for sublist in [
|
||||
[value.lower() for value in query_params[key]]
|
||||
for key in query_params.keys()
|
||||
if key.lower() == param_key and query_params.get(key)
|
||||
]
|
||||
for item in sublist
|
||||
}
|
||||
|
||||
|
||||
def _ensure_no_embed_params(
|
||||
query_params: dict[str, list[str] | str], query_string: str
|
||||
) -> str:
|
||||
"""Ensures there are no embed params set (raises StreamlitAPIException) if there is a try,
|
||||
also makes sure old param values in query_string are preserved. Returns query_string : str.
|
||||
"""
|
||||
# Get query params dict without embed, embed_options params
|
||||
query_params_without_embed = _exclude_keys_in_dict(
|
||||
query_params, keys_to_exclude=EMBED_QUERY_PARAMS_KEYS
|
||||
)
|
||||
if query_params != query_params_without_embed:
|
||||
raise StreamlitAPIException(
|
||||
"Query param embed and embed_options (case-insensitive) cannot be set using set_query_params method."
|
||||
)
|
||||
|
||||
all_current_params = parse.parse_qs(query_string, keep_blank_values=True)
|
||||
current_embed_params = parse.urlencode(
|
||||
{
|
||||
EMBED_QUERY_PARAM: list(
|
||||
_extract_key_query_params(
|
||||
all_current_params, param_key=EMBED_QUERY_PARAM
|
||||
)
|
||||
),
|
||||
EMBED_OPTIONS_QUERY_PARAM: list(
|
||||
_extract_key_query_params(
|
||||
all_current_params, param_key=EMBED_OPTIONS_QUERY_PARAM
|
||||
)
|
||||
),
|
||||
},
|
||||
doseq=True,
|
||||
)
|
||||
query_string = parse.urlencode(query_params, doseq=True)
|
||||
|
||||
if query_string:
|
||||
separator = "&" if current_embed_params else ""
|
||||
return separator.join([query_string, current_embed_params])
|
||||
return current_embed_params
|
||||
189
myenv/lib/python3.11/site-packages/streamlit/commands/logo.py
Normal file
189
myenv/lib/python3.11/site-packages/streamlit/commands/logo.py
Normal file
@@ -0,0 +1,189 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Handle App logos."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from streamlit import url_util
|
||||
from streamlit.elements.lib.image_utils import AtomicImage, WidthBehavior, image_to_url
|
||||
from streamlit.errors import StreamlitAPIException
|
||||
from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
|
||||
from streamlit.runtime.metrics_util import gather_metrics
|
||||
from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
|
||||
|
||||
|
||||
def _invalid_logo_text(field_name: str):
|
||||
return f"The {field_name} passed to st.logo is invalid - See [documentation](https://docs.streamlit.io/develop/api-reference/media/st.logo) for more information on valid types"
|
||||
|
||||
|
||||
@gather_metrics("logo")
|
||||
def logo(
|
||||
image: AtomicImage,
|
||||
*, # keyword-only args:
|
||||
size: Literal["small", "medium", "large"] = "medium",
|
||||
link: str | None = None,
|
||||
icon_image: AtomicImage | None = None,
|
||||
) -> None:
|
||||
r"""
|
||||
Renders a logo in the upper-left corner of your app and its sidebar.
|
||||
|
||||
If ``st.logo`` is called multiple times within a page, Streamlit will
|
||||
render the image passed in the last call. For the most consistent results,
|
||||
call ``st.logo`` early in your page script and choose an image that works
|
||||
well in both light and dark mode. Avoid empty margins around your image.
|
||||
|
||||
If your logo does not work well for both light and dark mode, consider
|
||||
setting the theme and hiding the settings menu from users with the
|
||||
`configuration option <https://docs.streamlit.io/develop/api-reference/configuration/config.toml>`_
|
||||
``client.toolbarMode="minimal"``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image: Anything supported by st.image (except list)
|
||||
The image to display in the upper-left corner of your app and its
|
||||
sidebar. This can be any of the types supported by |st.image|_ except
|
||||
a list. If ``icon_image`` is also provided, then Streamlit will only
|
||||
display ``image`` in the sidebar.
|
||||
|
||||
Streamlit scales the image to a max height set by ``size`` and a max
|
||||
width to fit within the sidebar.
|
||||
|
||||
.. |st.image| replace:: ``st.image``
|
||||
.. _st.image: https://docs.streamlit.io/develop/api-reference/media/st.image
|
||||
|
||||
size: "small", "medium", or "large"
|
||||
The size of the image displayed in the upper-left corner of the app and its
|
||||
sidebar. The possible values are as follows:
|
||||
|
||||
- ``"small"``: 20px max height
|
||||
- ``"medium"`` (default): 24px max height
|
||||
- ``"large"``: 32px max height
|
||||
|
||||
link : str or None
|
||||
The external URL to open when a user clicks on the logo. The URL must
|
||||
start with "\http://" or "\https://". If ``link`` is ``None`` (default),
|
||||
the logo will not include a hyperlink.
|
||||
icon_image: Anything supported by st.image (except list) or None
|
||||
An optional, typically smaller image to replace ``image`` in the
|
||||
upper-left corner when the sidebar is closed. This can be any of the
|
||||
types supported by ``st.image`` except a list. If ``icon_image`` is
|
||||
``None`` (default), Streamlit will always display ``image`` in the
|
||||
upper-left corner, regardless of whether the sidebar is open or closed.
|
||||
Otherwise, Streamlit will render ``icon_image`` in the upper-left
|
||||
corner of the app when the sidebar is closed.
|
||||
|
||||
Streamlit scales the image to a max height set by ``size`` and a max
|
||||
width to fit within the sidebar. If the sidebar is closed, the max
|
||||
width is retained from when it was last open.
|
||||
|
||||
For best results, pass a wide or horizontal image to ``image`` and a
|
||||
square image to ``icon_image``. Or, pass a square image to ``image``
|
||||
and leave ``icon_image=None``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
A common design practice is to use a wider logo in the sidebar, and a
|
||||
smaller, icon-styled logo in your app's main body.
|
||||
|
||||
>>> import streamlit as st
|
||||
>>>
|
||||
>>> st.logo(
|
||||
... LOGO_URL_LARGE,
|
||||
... link="https://streamlit.io/gallery",
|
||||
... icon_image=LOGO_URL_SMALL,
|
||||
... )
|
||||
|
||||
Try switching logos around in the following example:
|
||||
|
||||
>>> import streamlit as st
|
||||
>>>
|
||||
>>> HORIZONTAL_RED = "images/horizontal_red.png"
|
||||
>>> ICON_RED = "images/icon_red.png"
|
||||
>>> HORIZONTAL_BLUE = "images/horizontal_blue.png"
|
||||
>>> ICON_BLUE = "images/icon_blue.png"
|
||||
>>>
|
||||
>>> options = [HORIZONTAL_RED, ICON_RED, HORIZONTAL_BLUE, ICON_BLUE]
|
||||
>>> sidebar_logo = st.selectbox("Sidebar logo", options, 0)
|
||||
>>> main_body_logo = st.selectbox("Main body logo", options, 1)
|
||||
>>>
|
||||
>>> st.logo(sidebar_logo, icon_image=main_body_logo)
|
||||
>>> st.sidebar.markdown("Hi!")
|
||||
|
||||
.. output::
|
||||
https://doc-logo.streamlit.app/
|
||||
height: 300px
|
||||
|
||||
"""
|
||||
|
||||
ctx = get_script_run_ctx()
|
||||
if ctx is None:
|
||||
return
|
||||
|
||||
fwd_msg = ForwardMsg()
|
||||
|
||||
try:
|
||||
image_url = image_to_url(
|
||||
image,
|
||||
width=WidthBehavior.AUTO,
|
||||
clamp=False,
|
||||
channels="RGB",
|
||||
output_format="auto",
|
||||
image_id="logo",
|
||||
)
|
||||
fwd_msg.logo.image = image_url
|
||||
except Exception as ex:
|
||||
raise StreamlitAPIException(_invalid_logo_text("image")) from ex
|
||||
|
||||
if link:
|
||||
# Handle external links:
|
||||
if url_util.is_url(link, ("http", "https")):
|
||||
fwd_msg.logo.link = link
|
||||
else:
|
||||
raise StreamlitAPIException(
|
||||
f"Invalid link: {link} - the link param supports external links only and must start with either http:// or https://."
|
||||
)
|
||||
|
||||
if icon_image:
|
||||
try:
|
||||
icon_image_url = image_to_url(
|
||||
icon_image,
|
||||
width=WidthBehavior.AUTO,
|
||||
clamp=False,
|
||||
channels="RGB",
|
||||
output_format="auto",
|
||||
image_id="icon-image",
|
||||
)
|
||||
fwd_msg.logo.icon_image = icon_image_url
|
||||
except Exception as ex:
|
||||
raise StreamlitAPIException(_invalid_logo_text("icon_image")) from ex
|
||||
|
||||
def validate_size(size):
|
||||
if isinstance(size, str):
|
||||
image_size = size.lower()
|
||||
valid_sizes = ["small", "medium", "large"]
|
||||
|
||||
if image_size in valid_sizes:
|
||||
return image_size
|
||||
|
||||
raise StreamlitAPIException(
|
||||
f'The size argument to st.logo must be "small", "medium", or "large". \n'
|
||||
f"The argument passed was {size}."
|
||||
)
|
||||
|
||||
fwd_msg.logo.size = validate_size(size)
|
||||
|
||||
ctx.enqueue(fwd_msg)
|
||||
@@ -0,0 +1,385 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# 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 __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping, Sequence
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Callable, Literal, Union
|
||||
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
from streamlit import config
|
||||
from streamlit.errors import StreamlitAPIException
|
||||
from streamlit.navigation.page import StreamlitPage
|
||||
from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
|
||||
from streamlit.proto.Navigation_pb2 import Navigation as NavigationProto
|
||||
from streamlit.runtime.metrics_util import gather_metrics
|
||||
from streamlit.runtime.pages_manager import PagesManager
|
||||
from streamlit.runtime.scriptrunner_utils.script_run_context import (
|
||||
ScriptRunContext,
|
||||
get_script_run_ctx,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from streamlit.source_util import PageHash, PageInfo
|
||||
|
||||
SectionHeader: TypeAlias = str
|
||||
PageType: TypeAlias = Union[str, Path, Callable[[], None], StreamlitPage]
|
||||
|
||||
|
||||
def convert_to_streamlit_page(
|
||||
page_input: PageType,
|
||||
) -> StreamlitPage:
|
||||
"""Convert various input types to StreamlitPage objects."""
|
||||
if isinstance(page_input, StreamlitPage):
|
||||
return page_input
|
||||
|
||||
if isinstance(page_input, str):
|
||||
return StreamlitPage(page_input)
|
||||
|
||||
if isinstance(page_input, Path):
|
||||
return StreamlitPage(page_input)
|
||||
|
||||
if callable(page_input):
|
||||
# Convert function to StreamlitPage
|
||||
return StreamlitPage(page_input)
|
||||
|
||||
raise StreamlitAPIException(
|
||||
f"Invalid page type: {type(page_input)}. Must be either a string path, "
|
||||
"a pathlib.Path, a callable function, or a st.Page object."
|
||||
)
|
||||
|
||||
|
||||
def pages_from_nav_sections(
|
||||
nav_sections: dict[SectionHeader, list[StreamlitPage]],
|
||||
) -> list[StreamlitPage]:
|
||||
page_list = []
|
||||
for pages in nav_sections.values():
|
||||
for page in pages:
|
||||
page_list.append(page)
|
||||
|
||||
return page_list
|
||||
|
||||
|
||||
def send_page_not_found(ctx: ScriptRunContext):
|
||||
msg = ForwardMsg()
|
||||
msg.page_not_found.page_name = ""
|
||||
ctx.enqueue(msg)
|
||||
|
||||
|
||||
@gather_metrics("navigation")
|
||||
def navigation(
|
||||
pages: Sequence[PageType] | Mapping[SectionHeader, Sequence[PageType]],
|
||||
*,
|
||||
position: Literal["sidebar", "hidden"] = "sidebar",
|
||||
expanded: bool = False,
|
||||
) -> StreamlitPage:
|
||||
"""
|
||||
Configure the available pages in a multipage app.
|
||||
|
||||
Call ``st.navigation`` in your entrypoint file to define the available
|
||||
pages for your app. ``st.navigation`` returns the current page, which can
|
||||
be executed using ``.run()`` method.
|
||||
|
||||
When using ``st.navigation``, your entrypoint file (the file passed to
|
||||
``streamlit run``) acts like a router or frame of common elements around
|
||||
each of your pages. Streamlit executes the entrypoint file with every app
|
||||
rerun. To execute the current page, you must call the ``.run()`` method on
|
||||
the ``StreamlitPage`` object returned by ``st.navigation``.
|
||||
|
||||
The set of available pages can be updated with each rerun for dynamic
|
||||
navigation. By default, ``st.navigation`` displays the available pages in
|
||||
the sidebar if there is more than one page. This behavior can be changed
|
||||
using the ``position`` keyword argument.
|
||||
|
||||
As soon as any session of your app executes the ``st.navigation`` command,
|
||||
your app will ignore the ``pages/`` directory (across all sessions).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pages : Sequence[page-like], Mapping[str, Sequence[page-like]]
|
||||
The available pages for the app.
|
||||
|
||||
To create a navigation menu with no sections or page groupings,
|
||||
``pages`` must be a list of page-like objects. Page-like objects are
|
||||
anything that can be passed to ``st.Page`` or a ``StreamlitPage``
|
||||
object returned by ``st.Page``.
|
||||
|
||||
To create labeled sections or page groupings within the navigation
|
||||
menu, ``pages`` must be a dictionary. Each key is the label of a
|
||||
section and each value is the list of page-like objects for
|
||||
that section.
|
||||
|
||||
When you use a string or path as a page-like object, they are
|
||||
internally passed to ``st.Page`` and converted to ``StreamlitPage``
|
||||
objects. In this case, the page will have the default title, icon, and
|
||||
path inferred from its path or filename. To customize these attributes
|
||||
for your page, initialize your page with ``st.Page``.
|
||||
|
||||
position : "sidebar" or "hidden"
|
||||
The position of the navigation menu. If this is ``"sidebar"``
|
||||
(default), the navigation widget appears at the top of the sidebar. If
|
||||
this is ``"hidden"``, the navigation widget is not displayed.
|
||||
|
||||
If there is only one page in ``pages``, the navigation will be hidden
|
||||
for any value of ``position``.
|
||||
|
||||
expanded : bool
|
||||
Whether the navigation menu should be expanded. If this is ``False``
|
||||
(default), the navigation menu will be collapsed and will include a
|
||||
button to view more options when there are too many pages to display.
|
||||
If this is ``True``, the navigation menu will always be expanded; no
|
||||
button to collapse the menu will be displayed.
|
||||
|
||||
If ``st.navigation`` changes from ``expanded=True`` to
|
||||
``expanded=False`` on a rerun, the menu will stay expanded and a
|
||||
collapse button will be displayed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
StreamlitPage
|
||||
The current page selected by the user. To run the page, you must use
|
||||
the ``.run()`` method on it.
|
||||
|
||||
Examples
|
||||
--------
|
||||
The following examples show different possible entrypoint files, each named
|
||||
``streamlit_app.py``. An entrypoint file is passed to ``streamlit run``. It
|
||||
manages your app's navigation and serves as a router between pages.
|
||||
|
||||
**Example 1: Use a callable or Python file as a page**
|
||||
|
||||
You can declare pages from callables or file paths. If you pass callables
|
||||
or paths to ``st.navigation`` as a page-like objects, they are internally
|
||||
converted to ``StreamlitPage`` objects using ``st.Page``. In this case, the
|
||||
page titles, icons, and paths are inferred from the file or callable names.
|
||||
|
||||
``page_1.py`` (in the same directory as your entrypoint file):
|
||||
|
||||
>>> import streamlit as st
|
||||
>>>
|
||||
>>> st.title("Page 1")
|
||||
|
||||
``streamlit_app.py``:
|
||||
|
||||
>>> import streamlit as st
|
||||
>>>
|
||||
>>> def page_2():
|
||||
... st.title("Page 2")
|
||||
>>>
|
||||
>>> pg = st.navigation(["page_1.py", page_2])
|
||||
>>> pg.run()
|
||||
|
||||
.. output::
|
||||
https://doc-navigation-example-1.streamlit.app/
|
||||
height: 200px
|
||||
|
||||
**Example 2: Group pages into sections and customize them with ``st.Page``**
|
||||
|
||||
You can use a dictionary to create sections within your navigation menu. In
|
||||
the following example, each page is similar to Page 1 in Example 1, and all
|
||||
pages are in the same directory. However, you can use Python files from
|
||||
anywhere in your repository. ``st.Page`` is used to give each page a custom
|
||||
title. For more information, see |st.Page|_.
|
||||
|
||||
Directory structure:
|
||||
|
||||
>>> your_repository/
|
||||
>>> ├── create_account.py
|
||||
>>> ├── learn.py
|
||||
>>> ├── manage_account.py
|
||||
>>> ├── streamlit_app.py
|
||||
>>> └── trial.py
|
||||
|
||||
``streamlit_app.py``:
|
||||
|
||||
>>> import streamlit as st
|
||||
>>>
|
||||
>>> pages = {
|
||||
... "Your account": [
|
||||
... st.Page("create_account.py", title="Create your account"),
|
||||
... st.Page("manage_account.py", title="Manage your account"),
|
||||
... ],
|
||||
... "Resources": [
|
||||
... st.Page("learn.py", title="Learn about us"),
|
||||
... st.Page("trial.py", title="Try it out"),
|
||||
... ],
|
||||
... }
|
||||
>>>
|
||||
>>> pg = st.navigation(pages)
|
||||
>>> pg.run()
|
||||
|
||||
.. output::
|
||||
https://doc-navigation-example-2.streamlit.app/
|
||||
height: 300px
|
||||
|
||||
**Example 3: Stateful widgets across multiple pages**
|
||||
|
||||
Call widget functions in your entrypoint file when you want a widget to be
|
||||
stateful across pages. Assign keys to your common widgets and access their
|
||||
values through Session State within your pages.
|
||||
|
||||
``streamlit_app.py``:
|
||||
|
||||
>>> import streamlit as st
|
||||
>>>
|
||||
>>> def page1():
|
||||
>>> st.write(st.session_state.foo)
|
||||
>>>
|
||||
>>> def page2():
|
||||
>>> st.write(st.session_state.bar)
|
||||
>>>
|
||||
>>> # Widgets shared by all the pages
|
||||
>>> st.sidebar.selectbox("Foo", ["A", "B", "C"], key="foo")
|
||||
>>> st.sidebar.checkbox("Bar", key="bar")
|
||||
>>>
|
||||
>>> pg = st.navigation([page1, page2])
|
||||
>>> pg.run()
|
||||
|
||||
.. output::
|
||||
https://doc-navigation-multipage-widgets.streamlit.app/
|
||||
height: 350px
|
||||
|
||||
.. |st.Page| replace:: ``st.Page``
|
||||
.. _st.Page: https://docs.streamlit.io/develop/api-reference/navigation/st.page
|
||||
|
||||
"""
|
||||
# Disable the use of the pages feature (ie disregard v1 behavior of Multipage Apps)
|
||||
PagesManager.uses_pages_directory = False
|
||||
|
||||
return _navigation(pages, position=position, expanded=expanded)
|
||||
|
||||
|
||||
def _navigation(
|
||||
pages: Sequence[PageType] | Mapping[SectionHeader, Sequence[PageType]],
|
||||
*,
|
||||
position: Literal["sidebar", "hidden"],
|
||||
expanded: bool,
|
||||
) -> StreamlitPage:
|
||||
if isinstance(pages, Sequence):
|
||||
converted_pages = [convert_to_streamlit_page(p) for p in pages]
|
||||
nav_sections = {"": converted_pages}
|
||||
else:
|
||||
nav_sections = {
|
||||
section: [convert_to_streamlit_page(p) for p in section_pages]
|
||||
for section, section_pages in pages.items()
|
||||
}
|
||||
page_list = pages_from_nav_sections(nav_sections)
|
||||
|
||||
if not page_list:
|
||||
raise StreamlitAPIException(
|
||||
"`st.navigation` must be called with at least one `st.Page`."
|
||||
)
|
||||
|
||||
default_page = None
|
||||
pagehash_to_pageinfo: dict[PageHash, PageInfo] = {}
|
||||
|
||||
# Get the default page.
|
||||
for section_header in nav_sections:
|
||||
for page in nav_sections[section_header]:
|
||||
if page._default:
|
||||
if default_page is not None:
|
||||
raise StreamlitAPIException(
|
||||
"Multiple Pages specified with `default=True`. "
|
||||
"At most one Page can be set to default."
|
||||
)
|
||||
default_page = page
|
||||
|
||||
if default_page is None:
|
||||
default_page = page_list[0]
|
||||
default_page._default = True
|
||||
|
||||
ctx = get_script_run_ctx()
|
||||
if not ctx:
|
||||
# This should never run in Streamlit, but we want to make sure that
|
||||
# the function always returns a page
|
||||
default_page._can_be_called = True
|
||||
return default_page
|
||||
|
||||
# Build the pagehash-to-pageinfo mapping.
|
||||
for section_header in nav_sections:
|
||||
for page in nav_sections[section_header]:
|
||||
if isinstance(page._page, Path):
|
||||
script_path = str(page._page)
|
||||
else:
|
||||
script_path = ""
|
||||
|
||||
script_hash = page._script_hash
|
||||
if script_hash in pagehash_to_pageinfo:
|
||||
# The page script hash is soley based on the url path
|
||||
# So duplicate page script hashes are due to duplicate url paths
|
||||
raise StreamlitAPIException(
|
||||
f"Multiple Pages specified with URL pathname {page.url_path}. "
|
||||
"URL pathnames must be unique. The url pathname may be "
|
||||
"inferred from the filename, callable name, or title."
|
||||
)
|
||||
|
||||
pagehash_to_pageinfo[script_hash] = {
|
||||
"page_script_hash": script_hash,
|
||||
"page_name": page.title,
|
||||
"icon": page.icon,
|
||||
"script_path": script_path,
|
||||
"url_pathname": page.url_path,
|
||||
}
|
||||
|
||||
msg = ForwardMsg()
|
||||
if position == "hidden":
|
||||
msg.navigation.position = NavigationProto.Position.HIDDEN
|
||||
elif config.get_option("client.showSidebarNavigation") is False:
|
||||
msg.navigation.position = NavigationProto.Position.HIDDEN
|
||||
else:
|
||||
msg.navigation.position = NavigationProto.Position.SIDEBAR
|
||||
|
||||
msg.navigation.expanded = expanded
|
||||
msg.navigation.sections[:] = nav_sections.keys()
|
||||
for section_header in nav_sections:
|
||||
for page in nav_sections[section_header]:
|
||||
p = msg.navigation.app_pages.add()
|
||||
p.page_script_hash = page._script_hash
|
||||
p.page_name = page.title
|
||||
p.icon = page.icon
|
||||
p.is_default = page._default
|
||||
p.section_header = section_header
|
||||
p.url_pathname = page.url_path
|
||||
|
||||
# Inform our page manager about the set of pages we have
|
||||
ctx.pages_manager.set_pages(pagehash_to_pageinfo)
|
||||
found_page = ctx.pages_manager.get_page_script(
|
||||
fallback_page_hash=default_page._script_hash
|
||||
)
|
||||
|
||||
page_to_return = None
|
||||
if found_page:
|
||||
found_page_script_hash = found_page["page_script_hash"]
|
||||
matching_pages = [
|
||||
p for p in page_list if p._script_hash == found_page_script_hash
|
||||
]
|
||||
if len(matching_pages) > 0:
|
||||
page_to_return = matching_pages[0]
|
||||
|
||||
if not page_to_return:
|
||||
send_page_not_found(ctx)
|
||||
page_to_return = default_page
|
||||
|
||||
# Ordain the page that can be called
|
||||
page_to_return._can_be_called = True
|
||||
msg.navigation.page_script_hash = page_to_return._script_hash
|
||||
# Set the current page script hash to the page that is going to be executed
|
||||
ctx.set_mpa_v2_page(page_to_return._script_hash)
|
||||
|
||||
# This will either navigation or yield if the page is not found
|
||||
ctx.enqueue(msg)
|
||||
|
||||
return page_to_return
|
||||
@@ -0,0 +1,311 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# 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 __future__ import annotations
|
||||
|
||||
import random
|
||||
from collections.abc import Mapping
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
from typing import TYPE_CHECKING, Any, Final, Literal, Union, cast
|
||||
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
from streamlit.elements.lib.image_utils import AtomicImage, image_to_url
|
||||
from streamlit.errors import (
|
||||
StreamlitInvalidMenuItemKeyError,
|
||||
StreamlitInvalidPageLayoutError,
|
||||
StreamlitInvalidSidebarStateError,
|
||||
StreamlitInvalidURLError,
|
||||
)
|
||||
from streamlit.proto.ForwardMsg_pb2 import ForwardMsg as ForwardProto
|
||||
from streamlit.proto.PageConfig_pb2 import PageConfig as PageConfigProto
|
||||
from streamlit.runtime.metrics_util import gather_metrics
|
||||
from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
|
||||
from streamlit.string_util import is_emoji, validate_material_icon
|
||||
from streamlit.url_util import is_url
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import TypeGuard
|
||||
|
||||
GET_HELP_KEY: Final = "get help"
|
||||
REPORT_A_BUG_KEY: Final = "report a bug"
|
||||
ABOUT_KEY: Final = "about"
|
||||
|
||||
PageIcon: TypeAlias = Union[AtomicImage, str]
|
||||
Layout: TypeAlias = Literal["centered", "wide"]
|
||||
InitialSideBarState: TypeAlias = Literal["auto", "expanded", "collapsed"]
|
||||
_GetHelp: TypeAlias = Literal["Get help", "Get Help", "get help"]
|
||||
_ReportABug: TypeAlias = Literal["Report a bug", "report a bug"]
|
||||
_About: TypeAlias = Literal["About", "about"]
|
||||
MenuKey: TypeAlias = Literal[_GetHelp, _ReportABug, _About]
|
||||
MenuItems: TypeAlias = Mapping[MenuKey, Union[str, None]]
|
||||
|
||||
# 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: Final = list(
|
||||
"🔥™🎉🚀🌌💣✨🌙🎆🎇💥🤩🤙🌛🤘⬆💡🤪🥂⚡💨🌠🎊🍿😛🔮🤟🌃🍃🍾💫▪🌴🎈🎬🌀🎄😝☔⛽🍂💃😎🍸🎨🥳☀😍🅱🌞😻🌟😜💦💅🦄😋😉👻🍁🤤👯🌻‼🌈👌🎃💛😚🔫🙌👽🍬🌅☁🍷👭☕🌚💁👅🥰🍜😌🎥🕺❕🧡☄💕🍻✅🌸🚬🤓🍹®☺💪😙☘🤠✊🤗🍵🤞😂💯😏📻🎂💗💜🌊❣🌝😘💆🤑🌿🦋😈⛄🚿😊🌹🥴😽💋😭🖤🙆👐⚪💟☃🙈🍭💻🥀🚗🤧🍝💎💓🤝💄💖🔞⁉⏰🕊🎧☠♥🌳🏾🙉⭐💊🍳🌎🙊💸❤🔪😆🌾✈📚💀🏠✌🏃🌵🚨💂🤫🤭😗😄🍒👏🙃🖖💞😅🎅🍄🆓👉💩🔊🤷⌚👸😇🚮💏👳🏽💘💿💉👠🎼🎶🎤👗❄🔐🎵🤒🍰👓🏄🌲🎮🙂📈🚙📍😵🗣❗🌺🙄👄🚘🥺🌍🏡♦💍🌱👑👙☑👾🍩🥶📣🏼🤣☯👵🍫➡🎀😃✋🍞🙇😹🙏👼🐝⚫🎁🍪🔨🌼👆👀😳🌏📖👃🎸👧💇🔒💙😞⛅🏻🍴😼🗿🍗♠🦁✔🤖☮🐢🐎💤😀🍺😁😴📺☹😲👍🎭💚🍆🍋🔵🏁🔴🔔🧐👰☎🏆🤡🐠📲🙋📌🐬✍🔑📱💰🐱💧🎓🍕👟🐣👫🍑😸🍦👁🆗🎯📢🚶🦅🐧💢🏀🚫💑🐟🌽🏊🍟💝💲🐍🍥🐸☝♣👊⚓❌🐯🏈📰🌧👿🐳💷🐺📞🆒🍀🤐🚲🍔👹🙍🌷🙎🐥💵🔝📸⚠❓🎩✂🍼😑⬇⚾🍎💔🐔⚽💭🏌🐷🍍✖🍇📝🍊🐙👋🤔🥊🗽🐑🐘🐰💐🐴♀🐦🍓✏👂🏴👇🆘😡🏉👩💌😺✝🐼🐒🐶👺🖕👬🍉🐻🐾⬅⏬▶👮🍌♂🔸👶🐮👪⛳🐐🎾🐕👴🐨🐊🔹©🎣👦👣👨👈💬⭕📹📷"
|
||||
)
|
||||
|
||||
# Also pick out some vanity emojis.
|
||||
ENG_EMOJIS: Final = [
|
||||
"🎈", # 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
|
||||
]
|
||||
|
||||
|
||||
def _lower_clean_dict_keys(dict: MenuItems) -> dict[str, Any]:
|
||||
return {str(k).lower().strip(): v for k, v in dict.items()}
|
||||
|
||||
|
||||
def _get_favicon_string(page_icon: PageIcon) -> str:
|
||||
"""Return the string to pass to the frontend to have it show
|
||||
the given PageIcon.
|
||||
|
||||
If page_icon is a string that looks like an emoji (or an emoji shortcode),
|
||||
we return it as-is. Otherwise we use `image_to_url` to return a URL.
|
||||
|
||||
(If `image_to_url` raises an error and page_icon is a string, return
|
||||
the unmodified page_icon string instead of re-raising the error.)
|
||||
"""
|
||||
|
||||
# Choose a random emoji.
|
||||
if page_icon == "random":
|
||||
return get_random_emoji()
|
||||
|
||||
# If page_icon is an emoji, return it as is.
|
||||
if isinstance(page_icon, str) and is_emoji(page_icon):
|
||||
return f"emoji:{page_icon}"
|
||||
|
||||
if isinstance(page_icon, str) and page_icon.startswith(":material"):
|
||||
return validate_material_icon(page_icon)
|
||||
|
||||
# Convert Path to string if necessary
|
||||
if isinstance(page_icon, Path):
|
||||
page_icon = str(page_icon)
|
||||
|
||||
# Fall back to image_to_url.
|
||||
try:
|
||||
return image_to_url(
|
||||
page_icon,
|
||||
width=-1, # Always use full width for favicons
|
||||
clamp=False,
|
||||
channels="RGB",
|
||||
output_format="auto",
|
||||
image_id="favicon",
|
||||
)
|
||||
except Exception:
|
||||
if isinstance(page_icon, str):
|
||||
# This fall-thru handles emoji shortcode strings (e.g. ":shark:"),
|
||||
# which aren't valid filenames and so will cause an Exception from
|
||||
# `image_to_url`.
|
||||
return page_icon
|
||||
raise
|
||||
|
||||
|
||||
@gather_metrics("set_page_config")
|
||||
def set_page_config(
|
||||
page_title: str | None = None,
|
||||
page_icon: PageIcon | None = None,
|
||||
layout: Layout = "centered",
|
||||
initial_sidebar_state: InitialSideBarState = "auto",
|
||||
menu_items: MenuItems | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Configures the default settings of the page.
|
||||
|
||||
.. note::
|
||||
This must be the first Streamlit command used on an app page, and must only
|
||||
be set once per page.
|
||||
|
||||
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 (except list), str, or None
|
||||
The page favicon. If ``page_icon`` is ``None`` (default), the favicon
|
||||
will be a monochrome Streamlit logo.
|
||||
|
||||
In addition to the types supported by |st.image|_ (except list), the
|
||||
following strings are valid:
|
||||
|
||||
- A single-character emoji. For example, you can set ``page_icon="🦈"``.
|
||||
|
||||
- An emoji short code. For example, you can set ``page_icon=":shark:"``.
|
||||
For a list of all supported codes, see
|
||||
https://share.streamlit.io/streamlit/emoji-shortcodes.
|
||||
|
||||
- The string literal, ``"random"``. You can set ``page_icon="random"``
|
||||
to set a random emoji from the supported list above.
|
||||
|
||||
- An icon from the Material Symbols library (rounded style) in the
|
||||
format ``":material/icon_name:"`` where "icon_name" is the name
|
||||
of the icon in snake case.
|
||||
|
||||
For example, ``page_icon=":material/thumb_up:"`` will display the
|
||||
Thumb Up icon. Find additional icons in the `Material Symbols \
|
||||
<https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded>`_
|
||||
font library.
|
||||
|
||||
.. note::
|
||||
Colors are not supported for Material icons. When you use a
|
||||
Material icon for favicon, it will be black, regardless of browser
|
||||
theme.
|
||||
|
||||
.. |st.image| replace:: ``st.image``
|
||||
.. _st.image: https://docs.streamlit.io/develop/api-reference/media/st.image
|
||||
|
||||
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", "expanded", or "collapsed"
|
||||
How the sidebar should start out. Defaults to "auto",
|
||||
which hides the sidebar on small devices and shows it otherwise.
|
||||
"expanded" shows the sidebar initially; "collapsed" hides it.
|
||||
In most cases, you should just use "auto", otherwise the app will
|
||||
look bad when embedded and viewed on mobile.
|
||||
|
||||
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.
|
||||
|
||||
The URL may also refer to an email address e.g. ``mailto:john@example.com``.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> import streamlit as st
|
||||
>>>
|
||||
>>> 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 = ForwardProto()
|
||||
|
||||
if page_title is not None:
|
||||
msg.page_config_changed.title = page_title
|
||||
|
||||
if page_icon is not None:
|
||||
msg.page_config_changed.favicon = _get_favicon_string(page_icon)
|
||||
|
||||
pb_layout: PageConfigProto.Layout.ValueType
|
||||
if layout == "centered":
|
||||
pb_layout = PageConfigProto.CENTERED
|
||||
elif layout == "wide":
|
||||
pb_layout = PageConfigProto.WIDE
|
||||
else:
|
||||
raise StreamlitInvalidPageLayoutError(layout=layout)
|
||||
|
||||
msg.page_config_changed.layout = pb_layout
|
||||
|
||||
pb_sidebar_state: PageConfigProto.SidebarState.ValueType
|
||||
if initial_sidebar_state == "auto":
|
||||
pb_sidebar_state = PageConfigProto.AUTO
|
||||
elif initial_sidebar_state == "expanded":
|
||||
pb_sidebar_state = PageConfigProto.EXPANDED
|
||||
elif initial_sidebar_state == "collapsed":
|
||||
pb_sidebar_state = PageConfigProto.COLLAPSED
|
||||
else:
|
||||
raise StreamlitInvalidSidebarStateError(
|
||||
initial_sidebar_state=initial_sidebar_state
|
||||
)
|
||||
|
||||
msg.page_config_changed.initial_sidebar_state = pb_sidebar_state
|
||||
|
||||
if menu_items is not None:
|
||||
lowercase_menu_items = cast("MenuItems", _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() -> str:
|
||||
# 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) -> None:
|
||||
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(menu_items: MenuItems) -> None:
|
||||
for k, v in menu_items.items():
|
||||
if not valid_menu_item_key(k):
|
||||
raise StreamlitInvalidMenuItemKeyError(key=k)
|
||||
if v is not None and (
|
||||
not is_url(v, ("http", "https", "mailto")) and k != ABOUT_KEY
|
||||
):
|
||||
raise StreamlitInvalidURLError(url=v)
|
||||
|
||||
|
||||
def valid_menu_item_key(key: str) -> TypeGuard[MenuKey]:
|
||||
return key in {GET_HELP_KEY, REPORT_A_BUG_KEY, ABOUT_KEY}
|
||||
Reference in New Issue
Block a user