def test_with_index_legacy_add_rows(self): """Test plain old _legacy_add_rows.""" all_methods = self._get_unnamed_data_methods() for method in all_methods: # Create a new data-carrying element (e.g. st._legacy_dataframe) el = method(DATAFRAME_WITH_INDEX) # Make sure it has 2 rows in it. df_proto = _get_data_frame(self.get_delta_from_queue()) num_rows = len(df_proto.data.cols[0].int64s.data) self.assertEqual(2, num_rows) # This is what we're testing: el._legacy_add_rows(NEW_ROWS_WITH_INDEX) # Make sure the add_rows proto looks like we expect. df_proto = _get_data_frame(self.get_delta_from_queue()) rows = df_proto.data.cols[0].int64s.data self.assertEqual([30, 40, 50], rows) index = df_proto.index.int_64_index.data.data self.assertEqual([3, 4, 5], index) # Clear the queue so the next loop is like a brand new test. get_script_run_ctx().reset() self.forward_msg_queue.clear()
def test_simple_legacy_add_rows_with_clear_queue(self): """Test plain old _legacy_add_rows after clearing the queue.""" all_methods = self._get_unnamed_data_methods( ) + self._get_named_data_methods() for method in all_methods: # Create a new data-carrying element (e.g. st._legacy_dataframe) el = method(DATAFRAME) # Make sure it has 2 rows in it. df_proto = _get_data_frame(self.get_delta_from_queue()) num_rows = len(df_proto.data.cols[0].int64s.data) self.assertEqual(2, num_rows) # This is what we're testing: self.forward_msg_queue.clear() el._legacy_add_rows(NEW_ROWS) # Make sure there are 3 rows in the delta that got appended. ar = self.get_delta_from_queue().add_rows num_rows = len(ar.data.data.cols[0].int64s.data) self.assertEqual(3, num_rows) # Clear the queue so the next loop is like a brand new test. get_script_run_ctx().reset() self.forward_msg_queue.clear()
def test_legacy_add_rows_with_pyarrow_table_data(self): """Test that an error is raised when called with `pyarrow.Table` data.""" all_methods = self._get_unnamed_data_methods( ) + self._get_named_data_methods() for method in all_methods: with self.assertRaises(StreamlitAPIException): # Create a new data-carrying element (e.g. st._legacy_dataframe) el = method(DATAFRAME) # This is what we're testing: el._legacy_add_rows(pa.Table.from_pandas(NEW_ROWS)) # Clear the queue so the next loop is like a brand new test. get_script_run_ctx().reset() self.forward_msg_queue.clear()
def marshall_component(dg, element: Element) -> Union[Any, Type[NoValue]]: element.component_instance.component_name = self.name element.component_instance.form_id = current_form_id(dg) if self.url is not None: element.component_instance.url = self.url # Normally, a widget's element_hash (which determines # its identity across multiple runs of an app) is computed # by hashing the entirety of its protobuf. This means that, # if any of the arguments to the widget are changed, Streamlit # considers it a new widget instance and it loses its previous # state. # # However! If a *component* has a `key` argument, then the # component's hash identity is determined by entirely by # `component_name + url + key`. This means that, when `key` # exists, the component will maintain its identity even when its # other arguments change, and the component's iframe won't be # remounted on the frontend. # # So: if `key` is None, we marshall the element's arguments # *before* computing its widget_ui_value (which creates its hash). # If `key` is not None, we marshall the arguments *after*. def marshall_element_args(): element.component_instance.json_args = serialized_json_args element.component_instance.special_args.extend(special_args) if key is None: marshall_element_args() def deserialize_component(ui_value, widget_id=""): # ui_value is an object from json, an ArrowTable proto, or a bytearray return ui_value ctx = get_script_run_ctx() widget_value, _ = register_widget( element_type="component_instance", element_proto=element.component_instance, user_key=key, widget_func_name=self.name, deserializer=deserialize_component, serializer=lambda x: x, ctx=ctx, ) if key is not None: marshall_element_args() if widget_value is None: widget_value = default elif isinstance(widget_value, ArrowTableProto): widget_value = component_arrow.arrow_proto_to_dataframe( widget_value) # widget_value will be either None or whatever the component's most # recent setWidgetValue value is. We coerce None -> NoValue, # because that's what DeltaGenerator._enqueue expects. return widget_value if widget_value is not None else NoValue
def test_remove_orphaned_files( self, get_file_recs_patch, remove_orphaned_files_patch ): """When file_uploader is accessed, it should call UploadedFileManager.remove_orphaned_files. """ ctx = get_script_run_ctx() ctx.uploaded_file_mgr._file_id_counter = 101 file_recs = [ UploadedFileRec(1, "file1", "type", b"123"), UploadedFileRec(2, "file2", "type", b"456"), ] get_file_recs_patch.return_value = file_recs st.file_uploader("foo", accept_multiple_files=True) args, kwargs = remove_orphaned_files_patch.call_args self.assertEqual(len(args), 0) self.assertEqual(kwargs["session_id"], "test session id") self.assertEqual(kwargs["newest_file_id"], 100) self.assertEqual(kwargs["active_file_ids"], [1, 2]) # Patch _get_file_recs to return [] instead. remove_orphaned_files # should not be called when file_uploader is accessed. get_file_recs_patch.return_value = [] remove_orphaned_files_patch.reset_mock() st.file_uploader("foo") remove_orphaned_files_patch.assert_not_called()
def _current_form( this_dg: "streamlit.delta_generator.DeltaGenerator", ) -> Optional[FormData]: """Find the FormData for the given DeltaGenerator. Forms are blocks, and can have other blocks nested inside them. To find the current form, we walk up the dg_stack until we find a DeltaGenerator that has FormData. """ if not streamlit._is_running_with_streamlit: return None if this_dg._form_data is not None: return this_dg._form_data if this_dg == this_dg._main_dg: # We were created via an `st.foo` call. # Walk up the dg_stack to see if we're nested inside a `with st.form` statement. ctx = get_script_run_ctx() if ctx is None or len(ctx.dg_stack) == 0: return None for dg in reversed(ctx.dg_stack): if dg._form_data is not None: return dg._form_data else: # We were created via an `dg.foo` call. # Take a look at our parent's form data to see if we're nested inside a form. parent = this_dg._parent if parent is not None and parent._form_data is not None: return parent._form_data return None
def _enqueue_message(msg): """Enqueues a ForwardMsg proto to send to the app.""" ctx = get_script_run_ctx() if ctx is None: raise NoSessionContext() ctx.enqueue(msg)
def __exit__(self, type, value, traceback): # with block ended ctx = get_script_run_ctx() if ctx is not None: ctx.dg_stack.pop() # Re-raise any exceptions return False
def test_legacy_add_rows_suceeds_when_wrong_shape(self): """_legacy_add_rows doesn't raise an error even if its input has the wrong shape. Instead, it's up to the frontend to catch and raise this error. """ all_methods = self._get_unnamed_data_methods( ) + self._get_named_data_methods() for method in all_methods: # Create a new data-carrying element (e.g. st._legacy_dataframe) el = method(DATAFRAME) # This is what we're testing: el._legacy_add_rows(NEW_ROWS_WRONG_SHAPE) # Clear the queue so the next loop is like a brand new test. get_script_run_ctx().reset() self.forward_msg_queue.clear()
def test_legacy_add_rows_works_when_new_name(self): """Test _legacy_add_rows with new named datasets.""" for method in self._get_named_data_methods(): # Create a new data-carrying element (e.g. st._legacy_dataframe) el = method(DATAFRAME) self.forward_msg_queue.clear() # This is what we're testing: el._legacy_add_rows(new_name=NEW_ROWS) # Make sure there are 3 rows in the delta that got appended. ar = self.get_delta_from_queue().add_rows num_rows = len(ar.data.data.cols[0].int64s.data) self.assertEqual(3, num_rows) # Clear the queue so the next loop is like a brand new test. get_script_run_ctx().reset() self.forward_msg_queue.clear()
def test_no_index_no_data_legacy_add_rows(self): """Test plain old _legacy_add_rows.""" all_methods = self._get_unnamed_data_methods() for method in all_methods: # Create a new data-carrying element (e.g. st._legacy_dataframe) el = method(None) _get_data_frame(self.get_delta_from_queue()) # This is what we're testing: el._legacy_add_rows(DATAFRAME) # Make sure there are 2 rows in it now. df_proto = _get_data_frame(self.get_delta_from_queue()) num_rows = len(df_proto.data.cols[0].int64s.data) self.assertEqual(2, num_rows) # Clear the queue so the next loop is like a brand new test. get_script_run_ctx().reset() self.forward_msg_queue.clear()
def _get_session_id() -> str: """Semantic wrapper to retrieve current AppSession ID.""" from streamlit.scriptrunner import get_script_run_ctx ctx = get_script_run_ctx() if ctx is None: # This is only None when running "python myscript.py" rather than # "streamlit run myscript.py". In which case the session ID doesn't # matter and can just be a constant, as there's only ever "session". return "dontcare" else: return ctx.session_id
def test_widget_outputs_dont_alias(self): color = st.select_slider( "Select a color of the rainbow", options=[ ["red", "orange"], ["yellow", "green"], ["blue", "indigo"], ["violet"], ], key="color", ) ctx = get_script_run_ctx() assert ctx.session_state["color"] is not color
def test_named_legacy_add_rows(self): """Test _legacy_add_rows with a named dataset.""" for method in self._get_named_data_methods(): # Create a new data-carrying element (e.g. st._legacy_dataframe) el = method(DATAFRAME) # Make sure it has 2 rows in it. df_proto = _get_data_frame(self.get_delta_from_queue()) num_rows = len(df_proto.data.cols[0].int64s.data) self.assertEqual(2, num_rows) # This is what we're testing: el._legacy_add_rows(mydata1=NEW_ROWS) # Make sure the add_rows proto looks like we expect df_proto = _get_data_frame(self.get_delta_from_queue(), name="mydata1") rows = df_proto.data.cols[0].int64s.data self.assertEqual([3, 4, 5], rows) # Clear the queue so the next loop is like a brand new test. get_script_run_ctx().reset() self.forward_msg_queue.clear()
def test_enqueue_new_session_message(self): """The SCRIPT_STARTED event should enqueue a 'new_session' message.""" session = _create_test_session(self.io_loop) orig_ctx = get_script_run_ctx() ctx = ScriptRunContext( session_id="TestSessionID", enqueue=session._session_data.enqueue, query_string="", session_state=MagicMock(), uploaded_file_mgr=MagicMock(), ) add_script_run_ctx(ctx=ctx) mock_scriptrunner = MagicMock(spec=ScriptRunner) session._scriptrunner = mock_scriptrunner # Send a mock SCRIPT_STARTED event. session._on_scriptrunner_event( sender=mock_scriptrunner, event=ScriptRunnerEvent.SCRIPT_STARTED, ) # Yield to let the AppSession's callbacks run. yield sent_messages = session._session_data._browser_queue._queue self.assertEqual( 2, len(sent_messages)) # NewApp and SessionState messages # Note that we're purposefully not very thoroughly testing new_session # fields below to avoid getting to the point where we're just # duplicating code in tests. new_session_msg = sent_messages[0].new_session self.assertEqual("mock_scriptrun_id", new_session_msg.script_run_id) self.assertTrue(new_session_msg.HasField("config")) self.assertEqual( config.get_option("server.allowRunOnSave"), new_session_msg.config.allow_run_on_save, ) self.assertTrue(new_session_msg.HasField("custom_theme")) self.assertEqual("black", new_session_msg.custom_theme.text_color) init_msg = new_session_msg.initialize self.assertTrue(init_msg.HasField("user_info")) add_script_run_ctx(ctx=orig_ctx)
def form_submit_button( self, label: str = "Submit", help: Optional[str] = None, on_click=None, args=None, kwargs=None, ) -> bool: """Display a form submit button. When this button is clicked, all widget values inside the form will be sent to Streamlit in a batch. Every form must have a form_submit_button. A form_submit_button cannot exist outside a form. For more information about forms, check out our `blog post <https://blog.streamlit.io/introducing-submit-button-and-forms/>`_. Parameters ---------- label : str A short label explaining to the user what this button is for. Defaults to "Submit". help : str or None A tooltip that gets displayed when the button is hovered over. Defaults to None. on_click : callable An optional callback invoked when this button is clicked. args : tuple An optional tuple of args to pass to the callback. kwargs : dict An optional dict of kwargs to pass to the callback. Returns ------- bool True if the button was clicked. """ ctx = get_script_run_ctx() return self._form_submit_button( label=label, help=help, on_click=on_click, args=args, kwargs=kwargs, ctx=ctx, )
def _active_dg(self) -> "DeltaGenerator": """Return the DeltaGenerator that's currently 'active'. If we are the main DeltaGenerator, and are inside a `with` block that creates a container, our active_dg is that container. Otherwise, our active_dg is self. """ if self == self._main_dg: # We're being invoked via an `st.foo` pattern - use the current # `with` dg (aka the top of the stack). ctx = get_script_run_ctx() if ctx and len(ctx.dg_stack) > 0: return ctx.dg_stack[-1] # We're being invoked via an `st.sidebar.foo` pattern - ignore the # current `with` dg. return self
def setUp(self, override_root=True): self.forward_msg_queue = ForwardMsgQueue() self.override_root = override_root self.orig_report_ctx = None self.new_script_run_ctx = ScriptRunContext( session_id="test session id", enqueue=self.forward_msg_queue.enqueue, query_string="", session_state=SessionState(), uploaded_file_mgr=UploadedFileManager(), ) if self.override_root: self.orig_report_ctx = get_script_run_ctx() add_script_run_ctx(threading.current_thread(), self.new_script_run_ctx) self.app_session = FakeAppSession()
def get_container_cursor( root_container: Optional[int], ) -> Optional["RunningCursor"]: """Return the top-level RunningCursor for the given container. This is the cursor that is used when user code calls something like `st.foo` (which uses the main container) or `st.sidebar.foo` (which uses the sidebar container). """ if root_container is None: return None ctx = get_script_run_ctx() if ctx is None: return None if root_container in ctx.cursors: return ctx.cursors[root_container] cursor = RunningCursor(root_container=root_container) ctx.cursors[root_container] = cursor return cursor
def __setitem__(self, user_key: str, value: Any) -> None: """Set the value of the session_state entry with the given user_key. If the key corresponds to a widget or form that's been instantiated during the current script run, raise a StreamlitAPIException instead. """ from streamlit.scriptrunner import get_script_run_ctx ctx = get_script_run_ctx() if ctx is not None: widget_id = self._key_id_mapping.get(user_key, None) widget_ids = ctx.widget_ids_this_run form_ids = ctx.form_ids_this_run if widget_id in widget_ids or user_key in form_ids: raise StreamlitAPIException( f"`st.session_state.{user_key}` cannot be modified after the widget" f" with key `{user_key}` is instantiated.") self._new_session_state[user_key] = value
def _get_file_recs_for_camera_input_widget( widget_id: str, widget_value: Optional[FileUploaderStateProto] ) -> List[UploadedFileRec]: if widget_value is None: return [] ctx = get_script_run_ctx() if ctx is None: return [] uploaded_file_info = widget_value.uploaded_file_info if len(uploaded_file_info) == 0: return [] active_file_ids = [f.id for f in uploaded_file_info] # Grab the files that correspond to our active file IDs. return ctx.uploaded_file_mgr.get_files( session_id=ctx.session_id, widget_id=widget_id, file_ids=active_file_ids, )
def get_session_state() -> SafeSessionState: """Get the SessionState object for the current session. Note that in streamlit scripts, this function should not be called directly. Instead, SessionState objects should be accessed via st.session_state. """ global _state_use_warning_already_displayed from streamlit.scriptrunner import get_script_run_ctx ctx = get_script_run_ctx() # If there is no script run context because the script is run bare, have # session state act as an always empty dictionary, and print a warning. if ctx is None: if not _state_use_warning_already_displayed: _state_use_warning_already_displayed = True if not st._is_running_with_streamlit: logger.warning( "Session state does not function when running a script without `streamlit run`" ) return SafeSessionState(SessionState()) return ctx.session_state
def serialize_camera_image_input( snapshot: SomeUploadedSnapshotFile, ) -> FileUploaderStateProto: state_proto = FileUploaderStateProto() ctx = get_script_run_ctx() if ctx is None: return state_proto # ctx.uploaded_file_mgr._file_id_counter stores the id to use for # the *next* uploaded file, so the current highest file id is the # counter minus 1. state_proto.max_file_id = ctx.uploaded_file_mgr._file_id_counter - 1 if not snapshot: return state_proto file_info: UploadedFileInfoProto = state_proto.uploaded_file_info.add( ) file_info.id = snapshot.id file_info.name = snapshot.name file_info.size = snapshot.size return state_proto
def serialize_file_uploader(files: SomeUploadedFiles) -> FileUploaderStateProto: state_proto = FileUploaderStateProto() ctx = get_script_run_ctx() if ctx is None: return state_proto # ctx.uploaded_file_mgr._file_id_counter stores the id to use for # the *next* uploaded file, so the current highest file id is the # counter minus 1. state_proto.max_file_id = ctx.uploaded_file_mgr._file_id_counter - 1 if not files: return state_proto elif not isinstance(files, list): files = [files] for f in files: file_info: UploadedFileInfoProto = state_proto.uploaded_file_info.add() file_info.id = f.id file_info.name = f.name file_info.size = f.size return state_proto
def slider( self, label: str, min_value=None, max_value=None, value=None, step=None, format: Optional[str] = None, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, *, # keyword-only arguments: disabled: bool = False, ): """Display a slider widget. This supports int, float, date, time, and datetime types. This also allows you to render a range slider by passing a two-element tuple or list as the `value`. The difference between `st.slider` and `st.select_slider` is that `slider` only accepts numerical or date/time data and takes a range as input, while `select_slider` accepts any datatype and takes an iterable set of options. Parameters ---------- label : str A short label explaining to the user what this slider is for. min_value : a supported type or None The minimum permitted value. Defaults to 0 if the value is an int, 0.0 if a float, value - timedelta(days=14) if a date/datetime, time.min if a time max_value : a supported type or None The maximum permitted value. Defaults to 100 if the value is an int, 1.0 if a float, value + timedelta(days=14) if a date/datetime, time.max if a time value : a supported type or a tuple/list of supported types or None The value of the slider when it first renders. If a tuple/list of two values is passed here, then a range slider with those lower and upper bounds is rendered. For example, if set to `(1, 10)` the slider will have a selectable range between 1 and 10. Defaults to min_value. step : int/float/timedelta or None The stepping interval. Defaults to 1 if the value is an int, 0.01 if a float, timedelta(days=1) if a date/datetime, timedelta(minutes=15) if a time (or if max_value - min_value < 1 day) format : str or None A printf-style format string controlling how the interface should display numbers. This does not impact the return value. Formatter for int/float supports: %d %e %f %g %i Formatter for date/time/datetime uses Moment.js notation: https://momentjs.com/docs/#/displaying/format/ key : str or int An optional string or integer to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str An optional tooltip that gets displayed next to the slider. on_change : callable An optional callback invoked when this slider's value changes. args : tuple An optional tuple of args to pass to the callback. kwargs : dict An optional dict of kwargs to pass to the callback. disabled : bool An optional boolean, which disables the slider if set to True. The default is False. This argument can only be supplied by keyword. Returns ------- int/float/date/time/datetime or tuple of int/float/date/time/datetime The current value of the slider widget. The return type will match the data type of the value parameter. Examples -------- >>> age = st.slider('How old are you?', 0, 130, 25) >>> st.write("I'm ", age, 'years old') And here's an example of a range slider: >>> values = st.slider( ... 'Select a range of values', ... 0.0, 100.0, (25.0, 75.0)) >>> st.write('Values:', values) This is a range time slider: >>> from datetime import time >>> appointment = st.slider( ... "Schedule your appointment:", ... value=(time(11, 30), time(12, 45))) >>> st.write("You're scheduled for:", appointment) Finally, a datetime slider: >>> from datetime import datetime >>> start_time = st.slider( ... "When do you start?", ... value=datetime(2020, 1, 1, 9, 30), ... format="MM/DD/YY - hh:mm") >>> st.write("Start time:", start_time) .. output:: https://share.streamlit.io/streamlit/docs/main/python/api-examples-source/widget.slider.py height: 300px """ ctx = get_script_run_ctx() return self._slider( label=label, min_value=min_value, max_value=max_value, value=value, step=step, format=format, key=key, help=help, on_change=on_change, args=args, kwargs=kwargs, disabled=disabled, ctx=ctx, )
def radio( self, label: str, options: OptionSequence, index: int = 0, format_func: Callable[[Any], Any] = str, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, *, # keyword-only args: disabled: bool = False, ) -> Any: """Display a radio button widget. Parameters ---------- label : str A short label explaining to the user what this radio group is for. options : Sequence, numpy.ndarray, pandas.Series, pandas.DataFrame, or pandas.Index Labels for the radio options. This will be cast to str internally by default. For pandas.DataFrame, the first column is selected. index : int The index of the preselected option on first render. format_func : function Function to modify the display of radio options. It receives the raw option as an argument and should output the label to be shown for that option. This has no impact on the return value of the radio. key : str or int An optional string or integer to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str An optional tooltip that gets displayed next to the radio. on_change : callable An optional callback invoked when this radio's value changes. args : tuple An optional tuple of args to pass to the callback. kwargs : dict An optional dict of kwargs to pass to the callback. disabled : bool An optional boolean, which disables the radio button if set to True. The default is False. This argument can only be supplied by keyword. Returns ------- any The selected option. Example ------- >>> genre = st.radio( ... "What\'s your favorite movie genre", ... ('Comedy', 'Drama', 'Documentary')) >>> >>> if genre == 'Comedy': ... st.write('You selected comedy.') ... else: ... st.write("You didn\'t select comedy.") .. output:: https://share.streamlit.io/streamlit/docs/main/python/api-examples-source/widget.radio.py height: 260px """ ctx = get_script_run_ctx() return self._radio( label=label, options=options, index=index, format_func=format_func, key=key, help=help, on_change=on_change, args=args, kwargs=kwargs, disabled=disabled, ctx=ctx, )
def download_button( self, label: str, data: DownloadButtonDataType, file_name: Optional[str] = None, mime: Optional[str] = None, key: Optional[Key] = None, help: Optional[str] = None, on_click: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, *, # keyword-only arguments: disabled: bool = False, ) -> bool: """Display a download button widget. This is useful when you would like to provide a way for your users to download a file directly from your app. Note that the data to be downloaded is stored in-memory while the user is connected, so it's a good idea to keep file sizes under a couple hundred megabytes to conserve memory. Parameters ---------- label : str A short label explaining to the user what this button is for. data : str or bytes or file The contents of the file to be downloaded. See example below for caching techniques to avoid recomputing this data unnecessarily. file_name: str An optional string to use as the name of the file to be downloaded, such as 'my_file.csv'. If not specified, the name will be automatically generated. mime : str or None The MIME type of the data. If None, defaults to "text/plain" (if data is of type *str* or is a textual *file*) or "application/octet-stream" (if data is of type *bytes* or is a binary *file*). key : str or int An optional string or integer to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str An optional tooltip that gets displayed when the button is hovered over. on_click : callable An optional callback invoked when this button is clicked. args : tuple An optional tuple of args to pass to the callback. kwargs : dict An optional dict of kwargs to pass to the callback. disabled : bool An optional boolean, which disables the download button if set to True. The default is False. This argument can only be supplied by keyword. Returns ------- bool True if the button was clicked on the last run of the app, False otherwise. Examples -------- Download a large DataFrame as a CSV: >>> @st.cache ... def convert_df(df): ... # IMPORTANT: Cache the conversion to prevent computation on every rerun ... return df.to_csv().encode('utf-8') >>> >>> csv = convert_df(my_large_df) >>> >>> st.download_button( ... label="Download data as CSV", ... data=csv, ... file_name='large_df.csv', ... mime='text/csv', ... ) Download a string as a file: >>> text_contents = '''This is some text''' >>> st.download_button('Download some text', text_contents) Download a binary file: >>> binary_contents = b'example content' >>> # Defaults to 'application/octet-stream' >>> st.download_button('Download binary file', binary_contents) Download an image: >>> with open("flower.png", "rb") as file: ... btn = st.download_button( ... label="Download image", ... data=file, ... file_name="flower.png", ... mime="image/png" ... ) .. output:: https://share.streamlit.io/streamlit/docs/main/python/api-examples-source/widget.download_button.py height: 335px """ ctx = get_script_run_ctx() return self._download_button( label=label, data=data, file_name=file_name, mime=mime, key=key, help=help, on_click=on_click, args=args, kwargs=kwargs, disabled=disabled, ctx=ctx, )
def camera_input( self, label: str, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, *, # keyword-only arguments: disabled: bool = False, ) -> SomeUploadedSnapshotFile: """Display a widget that returns pictures from the user's webcam. Parameters ---------- label : str A short label explaining to the user what this widget is used for. key : str or int An optional string or integer to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str A tooltip that gets displayed next to the camera input. on_change : callable An optional callback invoked when this camera_input's value changes. args : tuple An optional tuple of args to pass to the callback. kwargs : dict An optional dict of kwargs to pass to the callback. disabled : bool An optional boolean, which disables the camera input if set to True. The default is False. This argument can only be supplied by keyword. Returns ------- None or UploadedFile The UploadedFile class is a subclass of BytesIO, and therefore it is "file-like". This means you can pass them anywhere where a file is expected. Examples -------- >>> import streamlit as st >>> >>> picture = st.camera_input("Take a picture") >>> >>> if picture: ... st.image(picture) """ ctx = get_script_run_ctx() return self._camera_input( label=label, key=key, help=help, on_change=on_change, args=args, kwargs=kwargs, disabled=disabled, ctx=ctx, )
def _camera_input( self, label: str, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, *, # keyword-only arguments: disabled: bool = False, ctx: Optional[ScriptRunContext] = None, ) -> SomeUploadedSnapshotFile: key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=None, key=key, writes_allowed=False) camera_input_proto = CameraInputProto() camera_input_proto.label = label camera_input_proto.form_id = current_form_id(self.dg) if help is not None: camera_input_proto.help = dedent(help) def serialize_camera_image_input( snapshot: SomeUploadedSnapshotFile, ) -> FileUploaderStateProto: state_proto = FileUploaderStateProto() ctx = get_script_run_ctx() if ctx is None: return state_proto # ctx.uploaded_file_mgr._file_id_counter stores the id to use for # the *next* uploaded file, so the current highest file id is the # counter minus 1. state_proto.max_file_id = ctx.uploaded_file_mgr._file_id_counter - 1 if not snapshot: return state_proto file_info: UploadedFileInfoProto = state_proto.uploaded_file_info.add( ) file_info.id = snapshot.id file_info.name = snapshot.name file_info.size = snapshot.size return state_proto def deserialize_camera_image_input( ui_value: Optional[FileUploaderStateProto], widget_id: str) -> SomeUploadedSnapshotFile: file_recs = self._get_file_recs_for_camera_input_widget( widget_id, ui_value) if len(file_recs) == 0: return_value = None else: return_value = UploadedFile(file_recs[0]) return return_value widget_value, _ = register_widget( "camera_input", camera_input_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_camera_image_input, serializer=serialize_camera_image_input, ctx=ctx, ) # This needs to be done after register_widget because we don't want # the following proto fields to affect a widget's ID. camera_input_proto.disabled = disabled ctx = get_script_run_ctx() camera_image_input_state = serialize_camera_image_input(widget_value) uploaded_shapshot_info = camera_image_input_state.uploaded_file_info if ctx is not None and len(uploaded_shapshot_info) != 0: newest_file_id = camera_image_input_state.max_file_id active_file_ids = [f.id for f in uploaded_shapshot_info] ctx.uploaded_file_mgr.remove_orphaned_files( session_id=ctx.session_id, widget_id=camera_input_proto.id, newest_file_id=newest_file_id, active_file_ids=active_file_ids, ) self.dg._enqueue("camera_input", camera_input_proto) return cast(SomeUploadedSnapshotFile, widget_value)
def button( self, label: str, key: Optional[Key] = None, help: Optional[str] = None, on_click: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, *, # keyword-only arguments: disabled: bool = False, ) -> bool: """Display a button widget. Parameters ---------- label : str A short label explaining to the user what this button is for. key : str or int An optional string or integer to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str An optional tooltip that gets displayed when the button is hovered over. on_click : callable An optional callback invoked when this button is clicked. args : tuple An optional tuple of args to pass to the callback. kwargs : dict An optional dict of kwargs to pass to the callback. disabled : bool An optional boolean, which disables the button if set to True. The default is False. This argument can only be supplied by keyword. Returns ------- bool True if the button was clicked on the last run of the app, False otherwise. Example ------- >>> if st.button('Say hello'): ... st.write('Why hello there') ... else: ... st.write('Goodbye') .. output:: https://share.streamlit.io/streamlit/docs/main/python/api-examples-source/widget.button.py height: 220px """ key = to_key(key) ctx = get_script_run_ctx() return self.dg._button( label, key, help, is_form_submitter=False, on_click=on_click, args=args, kwargs=kwargs, disabled=disabled, ctx=ctx, )