Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Prototype] Add key parameter to st.container #8569

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 9 additions & 1 deletion frontend/lib/src/components/core/Block/Block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,15 @@ const VerticalBlock = (props: BlockPropsWithoutWidth): ReactElement => {
data-test-scroll-behavior="normal"
>
<StyledVerticalBlockWrapper ref={wrapperElement}>
<StyledVerticalBlock width={width} data-testid="stVerticalBlock">
<StyledVerticalBlock
width={width}
data-testid="stVerticalBlock"
className={
props.node.deltaBlock.key
? `st-${props.node.deltaBlock.key}`
: undefined
}
>
<ChildRenderer {...propsWithNewWidth} />
</StyledVerticalBlock>
</StyledVerticalBlockWrapper>
Expand Down
4 changes: 3 additions & 1 deletion lib/streamlit/components/v1/custom_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from streamlit import _main, type_util
from streamlit.components.types.base_custom_component import BaseCustomComponent
from streamlit.elements.form import current_form_id
from streamlit.elements.utils import check_cache_replay_rules
from streamlit.elements.utils import check_cache_replay_rules, current_container_key
from streamlit.errors import StreamlitAPIException
from streamlit.proto.Components_pb2 import ArrowTable as ArrowTableProto
from streamlit.proto.Components_pb2 import SpecialArg
Expand Down Expand Up @@ -169,6 +169,7 @@ def marshall_element_args():
key=key,
json_args=serialized_json_args,
special_args=special_args,
container_key=current_container_key(dg),
page=ctx.page_script_hash if ctx else None,
)
else:
Expand All @@ -179,6 +180,7 @@ def marshall_element_args():
form_id=current_form_id(dg),
url=self.url,
key=key,
container_key=current_container_key(dg),
page=ctx.page_script_hash if ctx else None,
)
element.component_instance.id = computed_id
Expand Down
1 change: 1 addition & 0 deletions lib/streamlit/delta_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ def __init__(

# If this an `st.form` block, this will get filled in.
self._form_data: FormData | None = None
self._container_key: str | None = None

# Change the module of all mixin'ed functions to be st.delta_generator,
# instead of the original module (e.g. st.elements.markdown)
Expand Down
22 changes: 20 additions & 2 deletions lib/streamlit/elements/layouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@

from __future__ import annotations

from typing import TYPE_CHECKING, Literal, Sequence, Union, cast
import re
from typing import TYPE_CHECKING, Final, Literal, Sequence, Union, cast

from typing_extensions import TypeAlias

from streamlit.errors import StreamlitAPIException
from streamlit.proto.Block_pb2 import Block as BlockProto
from streamlit.runtime.metrics_util import gather_metrics
from streamlit.type_util import Key, to_key

if TYPE_CHECKING:
from streamlit.delta_generator import DeltaGenerator
Expand All @@ -29,11 +31,18 @@

SpecType: TypeAlias = Union[int, Sequence[Union[int, float]]]

# Pattern to validate a container key (to be compatible with css class names):
_KEY_PATTERN: Final = re.compile(r"^[a-zA-Z_][a-zA-Z0-9\-_]*$")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check here that it starts with a letter is not needed since we prefix the key anyways with some letters (e.g. st-)



class LayoutsMixin:
@gather_metrics("container")
def container(
self, *, height: int | None = None, border: bool | None = None
self,
*,
height: int | None = None,
border: bool | None = None,
key: Key | None = None,
) -> DeltaGenerator:
"""Insert a multi-element container.

Expand Down Expand Up @@ -130,6 +139,15 @@ def container(
block_proto = BlockProto()
block_proto.allow_empty = False
block_proto.vertical.border = border or False
if key := to_key(key):
if not _KEY_PATTERN.match(key):
raise StreamlitAPIException(
f"Invalid key '{key}'. Key must match the pattern '{_KEY_PATTERN.pattern}'."
)
block_proto.key = key
# Set the container key in the DeltaGenerator
self.dg._container_key = key

if height:
# Activate scrolling container behavior:
block_proto.allow_empty = True
Expand Down
29 changes: 29 additions & 0 deletions lib/streamlit/elements/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,35 @@
_shown_default_value_warning = True


def current_container_key(this_dg: DeltaGenerator) -> str | None:
"""Find the container key for the given DeltaGenerator."""
# Avoid circular imports.
from streamlit.delta_generator import dg_stack

Check notice

Code scanning / CodeQL

Cyclic import Note

Import of module
streamlit.delta_generator
begins an import cycle.

if not runtime.exists():
return None

if this_dg._container_key is not None:
return this_dg._container_key

if this_dg == this_dg._main_dg:
# We were created via an `st.foo` call.
# Walk up the dg_stack to see if there is a container key set.
for dg in reversed(dg_stack.get()):
if dg._container_key is not None:
return dg._container_key
else:
# We were created via an `dg.foo` call.
# Take a look at our parent's container key.
parent = this_dg._parent
if parent is not None and parent._container_key is not None:
return parent._container_key
else:
return current_container_key(parent)

return None


class CachedWidgetWarning(StreamlitAPIWarning):
def __init__(self):
super().__init__(
Expand Down
4 changes: 4 additions & 0 deletions lib/streamlit/elements/widgets/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,7 @@ def _download_button(
check_cache_replay_rules,
check_callback_rules,
check_session_state_rules,
current_container_key,
)

check_cache_replay_rules()
Expand All @@ -576,6 +577,7 @@ def _download_button(
help=help,
type=type,
use_container_width=use_container_width,
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down Expand Up @@ -724,6 +726,7 @@ def _button(
check_cache_replay_rules,
check_callback_rules,
check_session_state_rules,
current_container_key,
)

if not is_form_submitter:
Expand All @@ -740,6 +743,7 @@ def _button(
is_form_submitter=is_form_submitter,
type=type,
use_container_width=use_container_width,
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down
2 changes: 2 additions & 0 deletions lib/streamlit/elements/widgets/camera_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
check_cache_replay_rules,
check_callback_rules,
check_session_state_rules,
current_container_key,
get_label_visibility_proto_value,
)
from streamlit.elements.widgets.file_uploader import _get_upload_files
Expand Down Expand Up @@ -210,6 +211,7 @@ def _camera_input(
key=key,
help=help,
form_id=current_form_id(self.dg),
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down
2 changes: 2 additions & 0 deletions lib/streamlit/elements/widgets/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ def chat_input(
check_cache_replay_rules,
check_callback_rules,
check_session_state_rules,
current_container_key,
)

check_cache_replay_rules()
Expand All @@ -311,6 +312,7 @@ def chat_input(
key=key,
placeholder=placeholder,
max_chars=max_chars,
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down
3 changes: 2 additions & 1 deletion lib/streamlit/elements/widgets/checkbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
WidgetKwargs,
register_widget,
)
from streamlit.runtime.state.common import compute_widget_id
from streamlit.runtime.state.common import compute_widget_id, current_container_key
from streamlit.type_util import Key, LabelVisibility, maybe_raise_label_warnings, to_key

if TYPE_CHECKING:
Expand Down Expand Up @@ -293,6 +293,7 @@ def _checkbox(
key=key,
help=help,
form_id=current_form_id(self.dg),
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down
3 changes: 2 additions & 1 deletion lib/streamlit/elements/widgets/color_picker.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from typing import cast

import streamlit
from streamlit.elements.form import current_form_id
from streamlit.elements.form import current_container_key, current_form_id
Fixed Show fixed Hide fixed
from streamlit.elements.utils import (
check_cache_replay_rules,
check_callback_rules,
Expand Down Expand Up @@ -185,6 +185,7 @@
key=key,
help=help,
form_id=current_form_id(self.dg),
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down
3 changes: 2 additions & 1 deletion lib/streamlit/elements/widgets/data_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from streamlit import logger as _logger
from streamlit import type_util
from streamlit.deprecation_util import deprecate_func_name
from streamlit.elements.form import current_form_id
from streamlit.elements.form import current_container_key, current_form_id
Fixed Show fixed Hide fixed
from streamlit.elements.lib.column_config_utils import (
INDEX_IDENTIFIER,
ColumnConfigMapping,
Expand Down Expand Up @@ -874,6 +874,7 @@
num_rows=num_rows,
key=key,
form_id=current_form_id(self.dg),
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down
2 changes: 2 additions & 0 deletions lib/streamlit/elements/widgets/file_uploader.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
check_cache_replay_rules,
check_callback_rules,
check_session_state_rules,
current_container_key,
get_label_visibility_proto_value,
)
from streamlit.proto.Common_pb2 import FileUploaderState as FileUploaderStateProto
Expand Down Expand Up @@ -415,6 +416,7 @@ def _file_uploader(
key=key,
help=help,
form_id=current_form_id(self.dg),
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down
2 changes: 2 additions & 0 deletions lib/streamlit/elements/widgets/multiselect.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
check_cache_replay_rules,
check_callback_rules,
check_session_state_rules,
current_container_key,
get_label_visibility_proto_value,
maybe_coerce_enum_sequence,
)
Expand Down Expand Up @@ -310,6 +311,7 @@ def _multiselect(
max_selections=max_selections,
placeholder=placeholder,
form_id=current_form_id(self.dg),
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down
2 changes: 2 additions & 0 deletions lib/streamlit/elements/widgets/number_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
check_cache_replay_rules,
check_callback_rules,
check_session_state_rules,
current_container_key,
get_label_visibility_proto_value,
)
from streamlit.errors import StreamlitAPIException
Expand Down Expand Up @@ -301,6 +302,7 @@ def _number_input(
help=help,
placeholder=None if placeholder is None else str(placeholder),
form_id=current_form_id(self.dg),
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down
2 changes: 2 additions & 0 deletions lib/streamlit/elements/widgets/radio.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
check_cache_replay_rules,
check_callback_rules,
check_session_state_rules,
current_container_key,
get_label_visibility_proto_value,
maybe_coerce_enum,
)
Expand Down Expand Up @@ -272,6 +273,7 @@ def _radio(
horizontal=horizontal,
captions=captions,
form_id=current_form_id(self.dg),
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down
2 changes: 2 additions & 0 deletions lib/streamlit/elements/widgets/select_slider.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
check_cache_replay_rules,
check_callback_rules,
check_session_state_rules,
current_container_key,
get_label_visibility_proto_value,
maybe_coerce_enum,
maybe_coerce_enum_sequence,
Expand Down Expand Up @@ -299,6 +300,7 @@ def as_index_list(v: object) -> list[int]:
key=key,
help=help,
form_id=current_form_id(self.dg),
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down
2 changes: 2 additions & 0 deletions lib/streamlit/elements/widgets/selectbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
check_cache_replay_rules,
check_callback_rules,
check_session_state_rules,
current_container_key,
get_label_visibility_proto_value,
maybe_coerce_enum,
)
Expand Down Expand Up @@ -251,6 +252,7 @@ def _selectbox(
help=help,
placeholder=placeholder,
form_id=current_form_id(self.dg),
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down
2 changes: 2 additions & 0 deletions lib/streamlit/elements/widgets/slider.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
check_cache_replay_rules,
check_callback_rules,
check_session_state_rules,
current_container_key,
get_label_visibility_proto_value,
)
from streamlit.errors import StreamlitAPIException
Expand Down Expand Up @@ -384,6 +385,7 @@ def _slider(
key=key,
help=help,
form_id=current_form_id(self.dg),
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down
3 changes: 3 additions & 0 deletions lib/streamlit/elements/widgets/text_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
check_cache_replay_rules,
check_callback_rules,
check_session_state_rules,
current_container_key,
get_label_visibility_proto_value,
)
from streamlit.errors import StreamlitAPIException
Expand Down Expand Up @@ -277,6 +278,7 @@ def _text_input(
autocomplete=autocomplete,
placeholder=str(placeholder),
form_id=current_form_id(self.dg),
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down Expand Up @@ -542,6 +544,7 @@ def _text_area(
help=help,
placeholder=str(placeholder),
form_id=current_form_id(self.dg),
container_key=current_container_key(self.dg),
page=ctx.page_script_hash if ctx else None,
)

Expand Down