def _text_area( self, label: str, value: str = "", height: Optional[int] = None, max_chars: Optional[int] = 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: placeholder: Optional[str] = None, disabled: bool = False, ctx: Optional[ScriptRunContext] = None, ) -> str: key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=None if value == "" else value, key=key) text_area_proto = TextAreaProto() text_area_proto.label = label text_area_proto.default = str(value) text_area_proto.form_id = current_form_id(self.dg) text_area_proto.disabled = disabled if help is not None: text_area_proto.help = dedent(help) if height is not None: text_area_proto.height = height if max_chars is not None: text_area_proto.max_chars = max_chars if placeholder is not None: text_area_proto.placeholder = str(placeholder) def deserialize_text_area(ui_value, widget_id="") -> str: return str(ui_value if ui_value is not None else value) current_value, set_frontend_value = register_widget( "text_area", text_area_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_text_area, serializer=lambda x: x, ctx=ctx, ) if set_frontend_value: text_area_proto.value = current_value text_area_proto.set_value = True self.dg._enqueue("text_area", text_area_proto) return cast(str, current_value)
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, ctx: Optional[ScriptRunContext] = None, ) -> bool: key = to_key(key) check_session_state_rules(default_value=None, key=key, writes_allowed=False) if is_in_form(self.dg): raise StreamlitAPIException( f"`st.download_button()` can't be used in an `st.form()`.{FORM_DOCS_INFO}" ) download_button_proto = DownloadButtonProto() download_button_proto.label = label download_button_proto.default = False marshall_file(self.dg._get_delta_path_str(), data, download_button_proto, mime, file_name) if help is not None: download_button_proto.help = dedent(help) def deserialize_button(ui_value, widget_id=""): return ui_value or False current_value, _ = register_widget( "download_button", download_button_proto, user_key=key, on_change_handler=on_click, args=args, kwargs=kwargs, deserializer=deserialize_button, serializer=bool, 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. download_button_proto.disabled = disabled self.dg._enqueue("download_button", download_button_proto) return cast(bool, current_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, ) -> 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. Returns ------- bool If the button was clicked on the last run of the app. Example ------- >>> if st.button('Say hello'): ... st.write('Why hello there') ... else: ... st.write('Goodbye') """ key = to_key(key) return self.dg._button( label, key, help, is_form_submitter=False, on_click=on_click, args=args, kwargs=kwargs, )
def _checkbox( self, label: str, value: bool = False, 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, ) -> bool: key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules( default_value=None if value is False else value, key=key) checkbox_proto = CheckboxProto() checkbox_proto.label = label checkbox_proto.default = bool(value) checkbox_proto.form_id = current_form_id(self.dg) if help is not None: checkbox_proto.help = dedent(help) def deserialize_checkbox(ui_value: Optional[bool], widget_id: str = "") -> bool: return bool(ui_value if ui_value is not None else value) current_value, set_frontend_value = register_widget( "checkbox", checkbox_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_checkbox, serializer=bool, 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. checkbox_proto.disabled = disabled if set_frontend_value: checkbox_proto.value = current_value checkbox_proto.set_value = True self.dg._enqueue("checkbox", checkbox_proto) return cast(bool, current_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, )
def _text_input( self, label: str, value: str = "", max_chars: Optional[int] = None, key: Optional[Key] = None, type: str = "default", help: Optional[str] = None, autocomplete: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, *, # keyword-only arguments: placeholder: Optional[str] = None, disabled: bool = False, ctx: Optional[ScriptRunContext] = None, ) -> str: key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=None if value == "" else value, key=key) text_input_proto = TextInputProto() text_input_proto.label = label text_input_proto.default = str(value) text_input_proto.form_id = current_form_id(self.dg) text_input_proto.disabled = disabled if help is not None: text_input_proto.help = dedent(help) if max_chars is not None: text_input_proto.max_chars = max_chars if placeholder is not None: text_input_proto.placeholder = str(placeholder) if type == "default": text_input_proto.type = TextInputProto.DEFAULT elif type == "password": text_input_proto.type = TextInputProto.PASSWORD else: raise StreamlitAPIException( "'%s' is not a valid text_input type. Valid types are 'default' and 'password'." % type ) # Marshall the autocomplete param. If unspecified, this will be # set to "new-password" for password inputs. if autocomplete is None: autocomplete = "new-password" if type == "password" else "" text_input_proto.autocomplete = autocomplete def deserialize_text_input(ui_value, widget_id="") -> str: return str(ui_value if ui_value is not None else value) current_value, set_frontend_value = register_widget( "text_input", text_input_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_text_input, serializer=lambda x: x, ctx=ctx, ) if set_frontend_value: text_input_proto.value = current_value text_input_proto.set_value = True self.dg._enqueue("text_input", text_input_proto) return cast(str, current_value)
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 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, ) -> 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. 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" ... ) """ key = to_key(key) check_session_state_rules(default_value=None, key=key, writes_allowed=False) if is_in_form(self.dg): raise StreamlitAPIException( f"`st.download_button()` can't be used in an `st.form()`.{FORM_DOCS_INFO}" ) download_button_proto = DownloadButtonProto() download_button_proto.label = label download_button_proto.default = False marshall_file( self.dg._get_delta_path_str(), data, download_button_proto, mime, file_name ) if help is not None: download_button_proto.help = dedent(help) def deserialize_button(ui_value, widget_id=""): return ui_value or False current_value, _ = register_widget( "download_button", download_button_proto, user_key=key, on_change_handler=on_click, args=args, kwargs=kwargs, deserializer=deserialize_button, serializer=bool, ) self.dg._enqueue("download_button", download_button_proto) return cast(bool, current_value)
def slider( self, label: str, min_value=None, max_value=None, value=None, step=None, format=None, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, ): """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. 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) """ key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=value, key=key) # Set value default. if value is None: value = min_value if min_value is not None else 0 SUPPORTED_TYPES = { int: SliderProto.INT, float: SliderProto.FLOAT, datetime: SliderProto.DATETIME, date: SliderProto.DATE, time: SliderProto.TIME, } TIMELIKE_TYPES = (SliderProto.DATETIME, SliderProto.TIME, SliderProto.DATE) # Ensure that the value is either a single value or a range of values. single_value = isinstance(value, tuple(SUPPORTED_TYPES.keys())) range_value = isinstance(value, (list, tuple)) and len(value) in (0, 1, 2) if not single_value and not range_value: raise StreamlitAPIException( "Slider value should either be an int/float/datetime or a list/tuple of " "0 to 2 ints/floats/datetimes") # Simplify future logic by always making value a list if single_value: value = [value] def all_same_type(items): return len(set(map(type, items))) < 2 if not all_same_type(value): raise StreamlitAPIException( "Slider tuple/list components must be of the same type.\n" f"But were: {list(map(type, value))}") if len(value) == 0: data_type = SliderProto.INT else: data_type = SUPPORTED_TYPES[type(value[0])] datetime_min = time.min datetime_max = time.max if data_type == SliderProto.TIME: datetime_min = time.min.replace(tzinfo=value[0].tzinfo) datetime_max = time.max.replace(tzinfo=value[0].tzinfo) if data_type in (SliderProto.DATETIME, SliderProto.DATE): datetime_min = value[0] - timedelta(days=14) datetime_max = value[0] + timedelta(days=14) DEFAULTS = { SliderProto.INT: { "min_value": 0, "max_value": 100, "step": 1, "format": "%d", }, SliderProto.FLOAT: { "min_value": 0.0, "max_value": 1.0, "step": 0.01, "format": "%0.2f", }, SliderProto.DATETIME: { "min_value": datetime_min, "max_value": datetime_max, "step": timedelta(days=1), "format": "YYYY-MM-DD", }, SliderProto.DATE: { "min_value": datetime_min, "max_value": datetime_max, "step": timedelta(days=1), "format": "YYYY-MM-DD", }, SliderProto.TIME: { "min_value": datetime_min, "max_value": datetime_max, "step": timedelta(minutes=15), "format": "HH:mm", }, } if min_value is None: min_value = DEFAULTS[data_type]["min_value"] if max_value is None: max_value = DEFAULTS[data_type]["max_value"] if step is None: step = DEFAULTS[data_type]["step"] if (data_type in ( SliderProto.DATETIME, SliderProto.DATE, ) and max_value - min_value < timedelta(days=1)): step = timedelta(minutes=15) if format is None: format = DEFAULTS[data_type]["format"] if step == 0: raise StreamlitAPIException( "Slider components cannot be passed a `step` of 0.") # Ensure that all arguments are of the same type. slider_args = [min_value, max_value, step] int_args = all(map(lambda a: isinstance(a, int), slider_args)) float_args = all(map(lambda a: isinstance(a, float), slider_args)) # When min and max_value are the same timelike, step should be a timedelta timelike_args = (data_type in TIMELIKE_TYPES and isinstance(step, timedelta) and type(min_value) == type(max_value)) if not int_args and not float_args and not timelike_args: raise StreamlitAPIException( "Slider value arguments must be of matching types." "\n`min_value` has %(min_type)s type." "\n`max_value` has %(max_type)s type." "\n`step` has %(step)s type." % { "min_type": type(min_value).__name__, "max_type": type(max_value).__name__, "step": type(step).__name__, }) # Ensure that the value matches arguments' types. all_ints = data_type == SliderProto.INT and int_args all_floats = data_type == SliderProto.FLOAT and float_args all_timelikes = data_type in TIMELIKE_TYPES and timelike_args if not all_ints and not all_floats and not all_timelikes: raise StreamlitAPIException( "Both value and arguments must be of the same type." "\n`value` has %(value_type)s type." "\n`min_value` has %(min_type)s type." "\n`max_value` has %(max_type)s type." % { "value_type": type(value).__name__, "min_type": type(min_value).__name__, "max_type": type(max_value).__name__, }) # Ensure that min <= value(s) <= max, adjusting the bounds as necessary. min_value = min(min_value, max_value) max_value = max(min_value, max_value) if len(value) == 1: min_value = min(value[0], min_value) max_value = max(value[0], max_value) elif len(value) == 2: start, end = value if start > end: # Swap start and end, since they seem reversed start, end = end, start value = start, end min_value = min(start, min_value) max_value = max(end, max_value) else: # Empty list, so let's just use the outer bounds value = [min_value, max_value] # Bounds checks. JSNumber produces human-readable exceptions that # we simply re-package as StreamlitAPIExceptions. # (We check `min_value` and `max_value` here; `value` and `step` are # already known to be in the [min_value, max_value] range.) try: if all_ints: JSNumber.validate_int_bounds(min_value, "`min_value`") JSNumber.validate_int_bounds(max_value, "`max_value`") elif all_floats: JSNumber.validate_float_bounds(min_value, "`min_value`") JSNumber.validate_float_bounds(max_value, "`max_value`") elif all_timelikes: # No validation yet. TODO: check between 0001-01-01 to 9999-12-31 pass except JSNumberBoundsException as e: raise StreamlitAPIException(str(e)) # Convert dates or times into datetimes if data_type == SliderProto.TIME: def _time_to_datetime(time): # Note, here we pick an arbitrary date well after Unix epoch. # This prevents pre-epoch timezone issues (https://bugs.python.org/issue36759) # We're dropping the date from datetime laters, anyways. return datetime.combine(date(2000, 1, 1), time) value = list(map(_time_to_datetime, value)) min_value = _time_to_datetime(min_value) max_value = _time_to_datetime(max_value) if data_type == SliderProto.DATE: def _date_to_datetime(date): return datetime.combine(date, time()) value = list(map(_date_to_datetime, value)) min_value = _date_to_datetime(min_value) max_value = _date_to_datetime(max_value) # Now, convert to microseconds (so we can serialize datetime to a long) if data_type in TIMELIKE_TYPES: SECONDS_TO_MICROS = 1000 * 1000 DAYS_TO_MICROS = 24 * 60 * 60 * SECONDS_TO_MICROS def _delta_to_micros(delta): return (delta.microseconds + delta.seconds * SECONDS_TO_MICROS + delta.days * DAYS_TO_MICROS) UTC_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc) def _datetime_to_micros(dt): # If dt is naive, Python converts from local time utc_dt = dt.astimezone(timezone.utc) return _delta_to_micros(utc_dt - UTC_EPOCH) # Restore times/datetimes to original timezone (dates are always naive) orig_tz = (value[0].tzinfo if data_type in (SliderProto.TIME, SliderProto.DATETIME) else None) def _micros_to_datetime(micros): utc_dt = UTC_EPOCH + timedelta(microseconds=micros) # Convert from utc back to original time (local time if naive) return utc_dt.astimezone(orig_tz).replace(tzinfo=orig_tz) value = list(map(_datetime_to_micros, value)) min_value = _datetime_to_micros(min_value) max_value = _datetime_to_micros(max_value) step = _delta_to_micros(step) # It would be great if we could guess the number of decimal places from # the `step` argument, but this would only be meaningful if step were a # decimal. As a possible improvement we could make this function accept # decimals and/or use some heuristics for floats. slider_proto = SliderProto() slider_proto.label = label slider_proto.format = format slider_proto.default[:] = value slider_proto.min = min_value slider_proto.max = max_value slider_proto.step = step slider_proto.data_type = data_type slider_proto.options[:] = [] slider_proto.form_id = current_form_id(self.dg) if help is not None: slider_proto.help = dedent(help) def deserialize_slider(ui_value: Optional[List[float]], widget_id=""): if ui_value is not None: val = ui_value else: # Widget has not been used; fallback to the original value, val = cast(List[float], value) # The widget always returns a float array, so fix the return type if necessary if data_type == SliderProto.INT: val = [int(v) for v in val] if data_type == SliderProto.DATETIME: val = [_micros_to_datetime(int(v)) for v in val] if data_type == SliderProto.DATE: val = [_micros_to_datetime(int(v)).date() for v in val] if data_type == SliderProto.TIME: val = [ _micros_to_datetime(int(v)).time().replace(tzinfo=orig_tz) for v in val ] return val[0] if single_value else tuple(val) def serialize_slider(v: Any) -> List[Any]: value = [v] if single_value else list(v) if data_type == SliderProto.DATE: value = [ _datetime_to_micros(_date_to_datetime(v)) for v in value ] if data_type == SliderProto.TIME: value = [ _datetime_to_micros(_time_to_datetime(v)) for v in value ] if data_type == SliderProto.DATETIME: value = [_datetime_to_micros(v) for v in value] return value current_value, set_frontend_value = register_widget( "slider", slider_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_slider, serializer=serialize_slider, ) if set_frontend_value: slider_proto.value[:] = serialize_slider(current_value) slider_proto.set_value = True self.dg._enqueue("slider", slider_proto) return current_value
def _slider( self, label: str, min_value=None, max_value=None, value=None, step=None, format=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, ctx: Optional[ScriptRunContext] = None, ): key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=value, key=key) # Set value default. if value is None: value = min_value if min_value is not None else 0 SUPPORTED_TYPES = { int: SliderProto.INT, float: SliderProto.FLOAT, datetime: SliderProto.DATETIME, date: SliderProto.DATE, time: SliderProto.TIME, } TIMELIKE_TYPES = (SliderProto.DATETIME, SliderProto.TIME, SliderProto.DATE) # Ensure that the value is either a single value or a range of values. single_value = isinstance(value, tuple(SUPPORTED_TYPES.keys())) range_value = isinstance(value, (list, tuple)) and len(value) in (0, 1, 2) if not single_value and not range_value: raise StreamlitAPIException( "Slider value should either be an int/float/datetime or a list/tuple of " "0 to 2 ints/floats/datetimes") # Simplify future logic by always making value a list if single_value: value = [value] def all_same_type(items): return len(set(map(type, items))) < 2 if not all_same_type(value): raise StreamlitAPIException( "Slider tuple/list components must be of the same type.\n" f"But were: {list(map(type, value))}") if len(value) == 0: data_type = SliderProto.INT else: data_type = SUPPORTED_TYPES[type(value[0])] datetime_min = time.min datetime_max = time.max if data_type == SliderProto.TIME: datetime_min = time.min.replace(tzinfo=value[0].tzinfo) datetime_max = time.max.replace(tzinfo=value[0].tzinfo) if data_type in (SliderProto.DATETIME, SliderProto.DATE): datetime_min = value[0] - timedelta(days=14) datetime_max = value[0] + timedelta(days=14) DEFAULTS = { SliderProto.INT: { "min_value": 0, "max_value": 100, "step": 1, "format": "%d", }, SliderProto.FLOAT: { "min_value": 0.0, "max_value": 1.0, "step": 0.01, "format": "%0.2f", }, SliderProto.DATETIME: { "min_value": datetime_min, "max_value": datetime_max, "step": timedelta(days=1), "format": "YYYY-MM-DD", }, SliderProto.DATE: { "min_value": datetime_min, "max_value": datetime_max, "step": timedelta(days=1), "format": "YYYY-MM-DD", }, SliderProto.TIME: { "min_value": datetime_min, "max_value": datetime_max, "step": timedelta(minutes=15), "format": "HH:mm", }, } if min_value is None: min_value = DEFAULTS[data_type]["min_value"] if max_value is None: max_value = DEFAULTS[data_type]["max_value"] if step is None: step = DEFAULTS[data_type]["step"] if (data_type in ( SliderProto.DATETIME, SliderProto.DATE, ) and max_value - min_value < timedelta(days=1)): step = timedelta(minutes=15) if format is None: format = DEFAULTS[data_type]["format"] if step == 0: raise StreamlitAPIException( "Slider components cannot be passed a `step` of 0.") # Ensure that all arguments are of the same type. slider_args = [min_value, max_value, step] int_args = all(map(lambda a: isinstance(a, int), slider_args)) float_args = all(map(lambda a: isinstance(a, float), slider_args)) # When min and max_value are the same timelike, step should be a timedelta timelike_args = (data_type in TIMELIKE_TYPES and isinstance(step, timedelta) and type(min_value) == type(max_value)) if not int_args and not float_args and not timelike_args: raise StreamlitAPIException( "Slider value arguments must be of matching types." "\n`min_value` has %(min_type)s type." "\n`max_value` has %(max_type)s type." "\n`step` has %(step)s type." % { "min_type": type(min_value).__name__, "max_type": type(max_value).__name__, "step": type(step).__name__, }) # Ensure that the value matches arguments' types. all_ints = data_type == SliderProto.INT and int_args all_floats = data_type == SliderProto.FLOAT and float_args all_timelikes = data_type in TIMELIKE_TYPES and timelike_args if not all_ints and not all_floats and not all_timelikes: raise StreamlitAPIException( "Both value and arguments must be of the same type." "\n`value` has %(value_type)s type." "\n`min_value` has %(min_type)s type." "\n`max_value` has %(max_type)s type." % { "value_type": type(value).__name__, "min_type": type(min_value).__name__, "max_type": type(max_value).__name__, }) # Ensure that min <= value(s) <= max, adjusting the bounds as necessary. min_value = min(min_value, max_value) max_value = max(min_value, max_value) if len(value) == 1: min_value = min(value[0], min_value) max_value = max(value[0], max_value) elif len(value) == 2: start, end = value if start > end: # Swap start and end, since they seem reversed start, end = end, start value = start, end min_value = min(start, min_value) max_value = max(end, max_value) else: # Empty list, so let's just use the outer bounds value = [min_value, max_value] # Bounds checks. JSNumber produces human-readable exceptions that # we simply re-package as StreamlitAPIExceptions. # (We check `min_value` and `max_value` here; `value` and `step` are # already known to be in the [min_value, max_value] range.) try: if all_ints: JSNumber.validate_int_bounds(min_value, "`min_value`") JSNumber.validate_int_bounds(max_value, "`max_value`") elif all_floats: JSNumber.validate_float_bounds(min_value, "`min_value`") JSNumber.validate_float_bounds(max_value, "`max_value`") elif all_timelikes: # No validation yet. TODO: check between 0001-01-01 to 9999-12-31 pass except JSNumberBoundsException as e: raise StreamlitAPIException(str(e)) # Convert dates or times into datetimes if data_type == SliderProto.TIME: def _time_to_datetime(time): # Note, here we pick an arbitrary date well after Unix epoch. # This prevents pre-epoch timezone issues (https://bugs.python.org/issue36759) # We're dropping the date from datetime laters, anyways. return datetime.combine(date(2000, 1, 1), time) value = list(map(_time_to_datetime, value)) min_value = _time_to_datetime(min_value) max_value = _time_to_datetime(max_value) if data_type == SliderProto.DATE: def _date_to_datetime(date): return datetime.combine(date, time()) value = list(map(_date_to_datetime, value)) min_value = _date_to_datetime(min_value) max_value = _date_to_datetime(max_value) # Now, convert to microseconds (so we can serialize datetime to a long) if data_type in TIMELIKE_TYPES: SECONDS_TO_MICROS = 1000 * 1000 DAYS_TO_MICROS = 24 * 60 * 60 * SECONDS_TO_MICROS def _delta_to_micros(delta): return (delta.microseconds + delta.seconds * SECONDS_TO_MICROS + delta.days * DAYS_TO_MICROS) UTC_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc) def _datetime_to_micros(dt): # The frontend is not aware of timezones and only expects a UTC-based timestamp (in microseconds). # Since we want to show the date/time exactly as it is in the given datetime object, # we just set the tzinfo to UTC and do not do any timezone conversions. # Only the backend knows about original timezone and will replace the UTC timestamp in the deserialization. utc_dt = dt.replace(tzinfo=timezone.utc) return _delta_to_micros(utc_dt - UTC_EPOCH) # Restore times/datetimes to original timezone (dates are always naive) orig_tz = (value[0].tzinfo if data_type in (SliderProto.TIME, SliderProto.DATETIME) else None) def _micros_to_datetime(micros): utc_dt = UTC_EPOCH + timedelta(microseconds=micros) # Add the original timezone. No conversion is required here, # since in the serialization, we also just replace the timestamp with UTC. return utc_dt.replace(tzinfo=orig_tz) value = list(map(_datetime_to_micros, value)) min_value = _datetime_to_micros(min_value) max_value = _datetime_to_micros(max_value) step = _delta_to_micros(step) # It would be great if we could guess the number of decimal places from # the `step` argument, but this would only be meaningful if step were a # decimal. As a possible improvement we could make this function accept # decimals and/or use some heuristics for floats. slider_proto = SliderProto() slider_proto.label = label slider_proto.format = format slider_proto.default[:] = value slider_proto.min = min_value slider_proto.max = max_value slider_proto.step = step slider_proto.data_type = data_type slider_proto.options[:] = [] slider_proto.form_id = current_form_id(self.dg) if help is not None: slider_proto.help = dedent(help) def deserialize_slider(ui_value: Optional[List[float]], widget_id=""): if ui_value is not None: val = ui_value else: # Widget has not been used; fallback to the original value, val = cast(List[float], value) # The widget always returns a float array, so fix the return type if necessary if data_type == SliderProto.INT: val = [int(v) for v in val] if data_type == SliderProto.DATETIME: val = [_micros_to_datetime(int(v)) for v in val] if data_type == SliderProto.DATE: val = [_micros_to_datetime(int(v)).date() for v in val] if data_type == SliderProto.TIME: val = [ _micros_to_datetime(int(v)).time().replace(tzinfo=orig_tz) for v in val ] return val[0] if single_value else tuple(val) def serialize_slider(v: Any) -> List[Any]: range_value = isinstance(v, (list, tuple)) value = list(v) if range_value else [v] if data_type == SliderProto.DATE: value = [ _datetime_to_micros(_date_to_datetime(v)) for v in value ] if data_type == SliderProto.TIME: value = [ _datetime_to_micros(_time_to_datetime(v)) for v in value ] if data_type == SliderProto.DATETIME: value = [_datetime_to_micros(v) for v in value] return value current_value, set_frontend_value = register_widget( "slider", slider_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_slider, serializer=serialize_slider, 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. slider_proto.disabled = disabled if set_frontend_value: slider_proto.value[:] = serialize_slider(current_value) slider_proto.set_value = True self.dg._enqueue("slider", slider_proto) return current_value
def checkbox( self, label: str, value: bool = False, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, ) -> bool: """Display a checkbox widget. Parameters ---------- label : str A short label explaining to the user what this checkbox is for. value : bool Preselect the checkbox when it first renders. This will be cast to bool internally. 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 checkbox. on_change : callable An optional callback invoked when this checkbox'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. Returns ------- bool Whether or not the checkbox is checked. Example ------- >>> agree = st.checkbox('I agree') >>> >>> if agree: ... st.write('Great!') """ key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules( default_value=None if value is False else value, key=key) checkbox_proto = CheckboxProto() checkbox_proto.label = label checkbox_proto.default = bool(value) checkbox_proto.form_id = current_form_id(self.dg) if help is not None: checkbox_proto.help = dedent(help) def deserialize_checkbox(ui_value: Optional[bool], widget_id: str = "") -> bool: return bool(ui_value if ui_value is not None else value) current_value, set_frontend_value = register_widget( "checkbox", checkbox_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_checkbox, serializer=bool, ) if set_frontend_value: checkbox_proto.value = current_value checkbox_proto.set_value = True self.dg._enqueue("checkbox", checkbox_proto) return cast(bool, current_value)
def text_input( self, label: str, value: str = "", max_chars: Optional[int] = None, key: Optional[Key] = None, type: str = "default", help: Optional[str] = None, autocomplete: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, *, # This makes placeholder a keyword-only argument placeholder: Optional[str] = None, ) -> str: """Display a single-line text input widget. Parameters ---------- label : str A short label explaining to the user what this input is for. value : any The text value of this widget when it first renders. This will be cast to str internally. max_chars : int or None Max number of characters allowed in text input. 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. type : str The type of the text input. This can be either "default" (for a regular text input), or "password" (for a text input that masks the user's typed value). Defaults to "default". help : str An optional tooltip that gets displayed next to the input. autocomplete : str An optional value that will be passed to the <input> element's autocomplete property. If unspecified, this value will be set to "new-password" for "password" inputs, and the empty string for "default" inputs. For more details, see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete on_change : callable An optional callback invoked when this text_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. placeholder : str or None An optional string displayed when the text input is empty. If None, no text is displayed. This is a keyword only argument. Returns ------- str The current value of the text input widget. Example ------- >>> title = st.text_input('Movie title', 'Life of Brian') >>> st.write('The current movie title is', title) """ key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=None if value == "" else value, key=key) text_input_proto = TextInputProto() text_input_proto.label = label text_input_proto.default = str(value) text_input_proto.form_id = current_form_id(self.dg) if help is not None: text_input_proto.help = dedent(help) if max_chars is not None: text_input_proto.max_chars = max_chars if placeholder is not None: text_input_proto.placeholder = str(placeholder) if type == "default": text_input_proto.type = TextInputProto.DEFAULT elif type == "password": text_input_proto.type = TextInputProto.PASSWORD else: raise StreamlitAPIException( "'%s' is not a valid text_input type. Valid types are 'default' and 'password'." % type ) # Marshall the autocomplete param. If unspecified, this will be # set to "new-password" for password inputs. if autocomplete is None: autocomplete = "new-password" if type == "password" else "" text_input_proto.autocomplete = autocomplete def deserialize_text_input(ui_value, widget_id="") -> str: return str(ui_value if ui_value is not None else value) current_value, set_frontend_value = register_widget( "text_input", text_input_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_text_input, serializer=lambda x: x, ) if set_frontend_value: text_input_proto.value = current_value text_input_proto.set_value = True self.dg._enqueue("text_input", text_input_proto) return cast(str, current_value)
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, ) -> bool: """Display a download button widget. Download button has a few constraints: - Download button is designed to download data that is stored in the Streamlit server's memory and works best when file size is reasonably small, <50MB. - For large file sizes, it is recommended to use a third party cloud based object storage solution. - We recommend doing any file transformation operations outside the download button declaration. Caching such transformations also prevents from slowing down the app on every rerun. See the examples below to learn more. Parameters ---------- label : str A short label explaining to what this button is for. data : str or bytes or file The contents of the file to be downloaded. file_name: str An optional string to use as the name of the file to be downloaded, eg. 'my_file.csv'. If file name is not specified, then we provide a generic name for the download. mime : str or None The MIME type of the data. If None, defaults to "text/plain" or "application/octet-stream" depending on the data type. 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. Returns ------- bool If the button was clicked on the last run of the app. Examples -------- While download button can be used to download arbitrary files, here are some common use-cases to get you started. Download a large DataFrame: >>> @st.cache ... def convert_df(df): ... # 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="Press to Download", ... data=csv, ... file_name='large_df.csv', ... mime='text/csv', ... ) Download a CSV file: >>> text_contents = ''' ... Col1, Col2 ... 123, 456 ... 789, 000 ... ''' >>> st.download_button( ... label='Download CSV', data=text_contents, ... file_name='file.csv', mime='text/csv' ... ) 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" ... ) """ key = to_key(key) check_session_state_rules(default_value=None, key=key, writes_allowed=False) if is_in_form(self.dg): raise StreamlitAPIException( f"`st.download_button()` can't be used in an `st.form()`.{FORM_DOCS_INFO}" ) download_button_proto = DownloadButtonProto() download_button_proto.label = label download_button_proto.default = False marshall_file(self.dg._get_delta_path_str(), data, download_button_proto, mime, file_name) if help is not None: download_button_proto.help = dedent(help) def deserialize_button(ui_value, widget_id=""): return ui_value or False current_value, _ = register_widget( "download_button", download_button_proto, user_key=key, on_change_handler=on_click, args=args, kwargs=kwargs, deserializer=deserialize_button, serializer=bool, ) self.dg._enqueue("download_button", download_button_proto) return cast(bool, current_value)
def selectbox( self, label: str, options: OptionSequence, index: int = 0, format_func: Callable[[Any], str] = str, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, ) -> Any: """Display a select widget. Parameters ---------- label : str A short label explaining to the user what this select widget is for. options : Sequence, numpy.ndarray, pandas.Series, pandas.DataFrame, or pandas.Index Labels for the select 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 the labels. It receives the option as an argument and its output will be cast to str. 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 selectbox. on_change : callable An optional callback invoked when this selectbox'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. Returns ------- any The selected option Example ------- >>> option = st.selectbox( ... 'How would you like to be contacted?', ... ('Email', 'Home phone', 'Mobile phone')) >>> >>> st.write('You selected:', option) """ key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=None if index == 0 else index, key=key) opt = ensure_indexable(options) if not isinstance(index, int): raise StreamlitAPIException( "Selectbox Value has invalid type: %s" % type(index).__name__) if len(opt) > 0 and not 0 <= index < len(opt): raise StreamlitAPIException( "Selectbox index must be between 0 and length of options") selectbox_proto = SelectboxProto() selectbox_proto.label = label selectbox_proto.default = index selectbox_proto.options[:] = [ str(format_func(option)) for option in opt ] selectbox_proto.form_id = current_form_id(self.dg) if help is not None: selectbox_proto.help = dedent(help) def deserialize_select_box(ui_value, widget_id=""): idx = ui_value if ui_value is not None else index return opt[idx] if len(opt) > 0 and opt[idx] is not None else None def serialize_select_box(v): if len(opt) == 0: return 0 return index_(opt, v) current_value, set_frontend_value = register_widget( "selectbox", selectbox_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_select_box, serializer=serialize_select_box, ) if set_frontend_value: selectbox_proto.value = serialize_select_box(current_value) selectbox_proto.set_value = True self.dg._enqueue("selectbox", selectbox_proto) return cast(str, current_value)
def text_area( self, label: str, value: str = "", height: Optional[int] = None, max_chars: Optional[int] = None, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, *, # This makes placeholder a keyword-only argument placeholder: Optional[str] = None, ) -> str: """Display a multi-line text input widget. Parameters ---------- label : str A short label explaining to the user what this input is for. value : any The text value of this widget when it first renders. This will be cast to str internally. height : int or None Desired height of the UI element expressed in pixels. If None, a default height is used. max_chars : int or None Maximum number of characters allowed in text area. 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 textarea. on_change : callable An optional callback invoked when this text_area'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. placeholder : str or None An optional string displayed when the text area is empty. If None, no text is displayed. This is a keyword only argument. Returns ------- str The current value of the text input widget. Example ------- >>> txt = st.text_area('Text to analyze', ''' ... It was the best of times, it was the worst of times, it was ... the age of wisdom, it was the age of foolishness, it was ... the epoch of belief, it was the epoch of incredulity, it ... was the season of Light, it was the season of Darkness, it ... was the spring of hope, it was the winter of despair, (...) ... ''') >>> st.write('Sentiment:', run_sentiment_analysis(txt)) """ key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=None if value == "" else value, key=key) text_area_proto = TextAreaProto() text_area_proto.label = label text_area_proto.default = str(value) text_area_proto.form_id = current_form_id(self.dg) if help is not None: text_area_proto.help = dedent(help) if height is not None: text_area_proto.height = height if max_chars is not None: text_area_proto.max_chars = max_chars if placeholder is not None: text_area_proto.placeholder = str(placeholder) def deserialize_text_area(ui_value, widget_id="") -> str: return str(ui_value if ui_value is not None else value) current_value, set_frontend_value = register_widget( "text_area", text_area_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_text_area, serializer=lambda x: x, ) if set_frontend_value: text_area_proto.value = current_value text_area_proto.set_value = True self.dg._enqueue("text_area", text_area_proto) return cast(str, current_value)
def _file_uploader( self, label: str, type: Optional[Union[str, List[str]]] = None, accept_multiple_files: bool = False, 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, ): key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=None, key=key, writes_allowed=False) if type: if isinstance(type, str): type = [type] # May need a regex or a library to validate file types are valid # extensions. type = [ file_type if file_type[0] == "." else f".{file_type}" for file_type in type ] file_uploader_proto = FileUploaderProto() file_uploader_proto.label = label file_uploader_proto.type[:] = type if type is not None else [] file_uploader_proto.max_upload_size_mb = config.get_option( "server.maxUploadSize") file_uploader_proto.multiple_files = accept_multiple_files file_uploader_proto.form_id = current_form_id(self.dg) file_uploader_proto.disabled = disabled if help is not None: file_uploader_proto.help = dedent(help) def deserialize_file_uploader( ui_value: Optional[FileUploaderStateProto], widget_id: str) -> SomeUploadedFiles: file_recs = self._get_file_recs(widget_id, ui_value) if len(file_recs) == 0: return_value: Optional[Union[List[UploadedFile], UploadedFile]] = ( [] if accept_multiple_files else None) else: files = [UploadedFile(rec) for rec in file_recs] return_value = files if accept_multiple_files else files[0] return return_value 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 # FileUploader's widget value is a list of file IDs # representing the current set of files that this uploader should # know about. widget_value, _ = register_widget( "file_uploader", file_uploader_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_file_uploader, serializer=serialize_file_uploader, ctx=ctx, ) file_uploader_state = serialize_file_uploader(widget_value) uploaded_file_info = file_uploader_state.uploaded_file_info if ctx is not None and len(uploaded_file_info) != 0: newest_file_id = file_uploader_state.max_file_id active_file_ids = [f.id for f in uploaded_file_info] ctx.uploaded_file_mgr.remove_orphaned_files( session_id=ctx.session_id, widget_id=file_uploader_proto.id, newest_file_id=newest_file_id, active_file_ids=active_file_ids, ) self.dg._enqueue("file_uploader", file_uploader_proto) return cast(SomeUploadedFiles, widget_value)
def time_input( self, label: str, value=None, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, ) -> time: """Display a time input widget. Parameters ---------- label : str A short label explaining to the user what this time input is for. value : datetime.time/datetime.datetime The value of this widget when it first renders. This will be cast to str internally. Defaults to the current time. 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 input. on_change : callable An optional callback invoked when this time_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. Returns ------- datetime.time The current value of the time input widget. Example ------- >>> t = st.time_input('Set an alarm for', datetime.time(8, 45)) >>> st.write('Alarm is set for', t) """ key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=value, key=key) # Set value default. if value is None: value = datetime.now().time().replace(second=0, microsecond=0) # Ensure that the value is either datetime/time if not isinstance(value, datetime) and not isinstance(value, time): raise StreamlitAPIException( "The type of the value should be either datetime or time.") # Convert datetime to time if isinstance(value, datetime): value = value.time().replace(second=0, microsecond=0) time_input_proto = TimeInputProto() time_input_proto.label = label time_input_proto.default = time.strftime(value, "%H:%M") time_input_proto.form_id = current_form_id(self.dg) if help is not None: time_input_proto.help = dedent(help) def deserialize_time_input(ui_value, widget_id=""): return (datetime.strptime(ui_value, "%H:%M").time() if ui_value is not None else value) def serialize_time_input(v): if isinstance(v, datetime): v = v.time() return time.strftime(v, "%H:%M") current_value, set_frontend_value = register_widget( "time_input", time_input_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_time_input, serializer=serialize_time_input, ) if set_frontend_value: time_input_proto.value = serialize_time_input(current_value) time_input_proto.set_value = True self.dg._enqueue("time_input", time_input_proto) return cast(time, current_value)
def date_input( self, label: str, value=None, min_value=None, max_value=None, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, ) -> date: """Display a date input widget. Parameters ---------- label : str A short label explaining to the user what this date input is for. value : datetime.date or datetime.datetime or list/tuple of datetime.date or datetime.datetime or None The value of this widget when it first renders. If a list/tuple with 0 to 2 date/datetime values is provided, the datepicker will allow users to provide a range. Defaults to today as a single-date picker. min_value : datetime.date or datetime.datetime The minimum selectable date. If value is a date, defaults to value - 10 years. If value is the interval [start, end], defaults to start - 10 years. max_value : datetime.date or datetime.datetime The maximum selectable date. If value is a date, defaults to value + 10 years. If value is the interval [start, end], defaults to end + 10 years. 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 input. on_change : callable An optional callback invoked when this date_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. Returns ------- datetime.date The current value of the date input widget. Example ------- >>> d = st.date_input( ... "When\'s your birthday", ... datetime.date(2019, 7, 6)) >>> st.write('Your birthday is:', d) """ key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=value, key=key) # Set value default. if value is None: value = datetime.now().date() single_value = isinstance(value, (date, datetime)) range_value = isinstance(value, (list, tuple)) and len(value) in (0, 1, 2) if not single_value and not range_value: raise StreamlitAPIException( "DateInput value should either be an date/datetime or a list/tuple of " "0 - 2 date/datetime values") if single_value: value = [value] date_input_proto = DateInputProto() date_input_proto.is_range = range_value if help is not None: date_input_proto.help = dedent(help) value = [v.date() if isinstance(v, datetime) else v for v in value] date_input_proto.label = label date_input_proto.default[:] = [ date.strftime(v, "%Y/%m/%d") for v in value ] if isinstance(min_value, datetime): min_value = min_value.date() elif min_value is None: if value: min_value = value[0] - relativedelta.relativedelta(years=10) else: min_value = date.today() - relativedelta.relativedelta( years=10) if isinstance(max_value, datetime): max_value = max_value.date() elif max_value is None: if value: max_value = value[-1] + relativedelta.relativedelta(years=10) else: max_value = date.today() + relativedelta.relativedelta( years=10) date_input_proto.min = date.strftime(min_value, "%Y/%m/%d") date_input_proto.max = date.strftime(max_value, "%Y/%m/%d") date_input_proto.form_id = current_form_id(self.dg) def deserialize_date_input(ui_value, widget_id=""): if ui_value is not None: return_value = [ datetime.strptime(v, "%Y/%m/%d").date() for v in ui_value ] else: return_value = value return return_value[0] if single_value else tuple(return_value) def serialize_date_input(v): to_serialize = [v] if single_value else list(v) return [date.strftime(v, "%Y/%m/%d") for v in to_serialize] current_value, set_frontend_value = register_widget( "date_input", date_input_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_date_input, serializer=serialize_date_input, ) if set_frontend_value: date_input_proto.value[:] = serialize_date_input(current_value) date_input_proto.set_value = True self.dg._enqueue("date_input", date_input_proto) return cast(date, current_value)
def _select_slider( self, label: str, options: OptionSequence = [], value: Any = None, 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, disabled: bool = False, ctx: Optional[ScriptRunContext] = None, ) -> Any: key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=value, key=key) opt = ensure_indexable(options) if len(opt) == 0: raise StreamlitAPIException( "The `options` argument needs to be non-empty") is_range_value = isinstance(value, (list, tuple)) def as_index_list(v): is_range_value = isinstance(v, (list, tuple)) if is_range_value: slider_value = [index_(opt, val) for val in v] start, end = slider_value if start > end: slider_value = [end, start] return slider_value else: # Simplify future logic by always making value a list try: return [index_(opt, v)] except ValueError: if value is not None: raise return [0] # Convert element to index of the elements slider_value = as_index_list(value) slider_proto = SliderProto() slider_proto.label = label slider_proto.format = "%s" slider_proto.default[:] = slider_value slider_proto.min = 0 slider_proto.max = len(opt) - 1 slider_proto.step = 1 # default for index changes slider_proto.data_type = SliderProto.INT slider_proto.options[:] = [str(format_func(option)) for option in opt] slider_proto.form_id = current_form_id(self.dg) slider_proto.disabled = disabled if help is not None: slider_proto.help = dedent(help) def deserialize_select_slider(ui_value, widget_id=""): if not ui_value: # Widget has not been used; fallback to the original value, ui_value = slider_value # The widget always returns floats, so convert to ints before indexing return_value = list(map(lambda x: opt[int(x)], ui_value)) # type: ignore[no-any-return] # If the original value was a list/tuple, so will be the output (and vice versa) return tuple(return_value) if is_range_value else return_value[0] def serialize_select_slider(v): return as_index_list(v) current_value, set_frontend_value = register_widget( "slider", slider_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_select_slider, serializer=serialize_select_slider, ctx=ctx, ) if set_frontend_value: slider_proto.value[:] = serialize_select_slider(current_value) slider_proto.set_value = True self.dg._enqueue("slider", slider_proto) return current_value
def file_uploader( self, label: str, type: Optional[Union[str, List[str]]] = None, accept_multiple_files: bool = False, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, ) -> SomeUploadedFiles: """Display a file uploader widget. By default, uploaded files are limited to 200MB. You can configure this using the `server.maxUploadSize` config option. For more info on how to set config options, see https://docs.streamlit.io/library/advanced-features/configuration#set-configuration-options Parameters ---------- label : str A short label explaining to the user what this file uploader is for. type : str or list of str or None Array of allowed extensions. ['png', 'jpg'] The default is None, which means all extensions are allowed. accept_multiple_files : bool If True, allows the user to upload multiple files at the same time, in which case the return value will be a list of files. Default: False 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 file uploader. on_change : callable An optional callback invoked when this file_uploader'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. Returns ------- None or UploadedFile or list of UploadedFile - If accept_multiple_files is False, returns either None or an UploadedFile object. - If accept_multiple_files is True, returns a list with the uploaded files as UploadedFile objects. If no files were uploaded, returns an empty list. 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 -------- Insert a file uploader that accepts a single file at a time: >>> uploaded_file = st.file_uploader("Choose a file") >>> if uploaded_file is not None: ... # To read file as bytes: ... bytes_data = uploaded_file.getvalue() ... st.write(bytes_data) >>> ... # To convert to a string based IO: ... stringio = StringIO(uploaded_file.getvalue().decode("utf-8")) ... st.write(stringio) >>> ... # To read file as string: ... string_data = stringio.read() ... st.write(string_data) >>> ... # Can be used wherever a "file-like" object is accepted: ... dataframe = pd.read_csv(uploaded_file) ... st.write(dataframe) Insert a file uploader that accepts multiple files at a time: >>> uploaded_files = st.file_uploader("Choose a CSV file", accept_multiple_files=True) >>> for uploaded_file in uploaded_files: ... bytes_data = uploaded_file.read() ... st.write("filename:", uploaded_file.name) ... st.write(bytes_data) """ key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=None, key=key, writes_allowed=False) if type: if isinstance(type, str): type = [type] # May need a regex or a library to validate file types are valid # extensions. type = [ file_type if file_type[0] == "." else f".{file_type}" for file_type in type ] file_uploader_proto = FileUploaderProto() file_uploader_proto.label = label file_uploader_proto.type[:] = type if type is not None else [] file_uploader_proto.max_upload_size_mb = config.get_option( "server.maxUploadSize") file_uploader_proto.multiple_files = accept_multiple_files file_uploader_proto.form_id = current_form_id(self.dg) if help is not None: file_uploader_proto.help = dedent(help) def deserialize_file_uploader( ui_value: Optional[FileUploaderStateProto], widget_id: str) -> SomeUploadedFiles: file_recs = self._get_file_recs(widget_id, ui_value) if len(file_recs) == 0: return_value: Optional[Union[List[UploadedFile], UploadedFile]] = ( [] if accept_multiple_files else None) else: files = [UploadedFile(rec) for rec in file_recs] return_value = files if accept_multiple_files else files[0] return return_value def serialize_file_uploader( files: SomeUploadedFiles) -> FileUploaderStateProto: state_proto = FileUploaderStateProto() ctx = get_report_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 # FileUploader's widget value is a list of file IDs # representing the current set of files that this uploader should # know about. widget_value, _ = register_widget( "file_uploader", file_uploader_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_file_uploader, serializer=serialize_file_uploader, ) ctx = get_report_ctx() file_uploader_state = serialize_file_uploader(widget_value) uploaded_file_info = file_uploader_state.uploaded_file_info if ctx is not None and len(uploaded_file_info) != 0: newest_file_id = file_uploader_state.max_file_id active_file_ids = [f.id for f in uploaded_file_info] ctx.uploaded_file_mgr.remove_orphaned_files( session_id=ctx.session_id, widget_id=file_uploader_proto.id, newest_file_id=newest_file_id, active_file_ids=active_file_ids, ) self.dg._enqueue("file_uploader", file_uploader_proto) return cast(SomeUploadedFiles, widget_value)
def radio( self, label: str, options: OptionSequence, index: int = 0, format_func: Callable[[Any], str] = str, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, ) -> 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. 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.") """ key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=None if index == 0 else index, key=key) opt = ensure_indexable(options) if not isinstance(index, int): raise StreamlitAPIException("Radio Value has invalid type: %s" % type(index).__name__) if len(opt) > 0 and not 0 <= index < len(opt): raise StreamlitAPIException( "Radio index must be between 0 and length of options") radio_proto = RadioProto() radio_proto.label = label radio_proto.default = index radio_proto.options[:] = [str(format_func(option)) for option in opt] radio_proto.form_id = current_form_id(self.dg) if help is not None: radio_proto.help = dedent(help) def deserialize_radio(ui_value, widget_id=""): idx = ui_value if ui_value is not None else index return opt[idx] if len(opt) > 0 and opt[idx] is not None else None def serialize_radio(v): if len(options) == 0: return 0 return index_(options, v) current_value, set_frontend_value = register_widget( "radio", radio_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_radio, serializer=serialize_radio, ) if set_frontend_value: radio_proto.value = serialize_radio(current_value) radio_proto.set_value = True self.dg._enqueue("radio", radio_proto) return cast(str, current_value)
def _color_picker( self, label: str, value: 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, ctx: Optional[ScriptRunContext] = None, ) -> str: key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=value, key=key) # set value default if value is None: value = "#000000" # make sure the value is a string if not isinstance(value, str): raise StreamlitAPIException(""" Color Picker Value has invalid type: %s. Expects a hex string like '#00FFAA' or '#000'. """ % type(value).__name__) # validate the value and expects a hex string match = re.match(r"^#(?:[0-9a-fA-F]{3}){1,2}$", value) if not match: raise StreamlitAPIException(""" '%s' is not a valid hex code for colors. Valid ones are like '#00FFAA' or '#000'. """ % value) color_picker_proto = ColorPickerProto() color_picker_proto.label = label color_picker_proto.default = str(value) color_picker_proto.form_id = current_form_id(self.dg) color_picker_proto.disabled = disabled if help is not None: color_picker_proto.help = dedent(help) def deserialize_color_picker(ui_value: Optional[str], widget_id: str = "") -> str: return str(ui_value if ui_value is not None else value) current_value, set_frontend_value = register_widget( "color_picker", color_picker_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_color_picker, serializer=str, ctx=ctx, ) if set_frontend_value: color_picker_proto.value = current_value color_picker_proto.set_value = True self.dg._enqueue("color_picker", color_picker_proto) return cast(str, current_value)
def _time_input( self, label: str, value=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, ctx: Optional[ScriptRunContext] = None, ) -> time: key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=value, key=key) # Set value default. if value is None: value = datetime.now().time().replace(second=0, microsecond=0) # Ensure that the value is either datetime/time if not isinstance(value, datetime) and not isinstance(value, time): raise StreamlitAPIException( "The type of the value should be either datetime or time.") # Convert datetime to time if isinstance(value, datetime): value = value.time().replace(second=0, microsecond=0) time_input_proto = TimeInputProto() time_input_proto.label = label time_input_proto.default = time.strftime(value, "%H:%M") time_input_proto.form_id = current_form_id(self.dg) time_input_proto.disabled = disabled if help is not None: time_input_proto.help = dedent(help) def deserialize_time_input(ui_value, widget_id=""): return (datetime.strptime(ui_value, "%H:%M").time() if ui_value is not None else value) def serialize_time_input(v): if isinstance(v, datetime): v = v.time() return time.strftime(v, "%H:%M") current_value, set_frontend_value = register_widget( "time_input", time_input_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_time_input, serializer=serialize_time_input, ctx=ctx, ) if set_frontend_value: time_input_proto.value = serialize_time_input(current_value) time_input_proto.set_value = True self.dg._enqueue("time_input", time_input_proto) return cast(time, current_value)
def multiselect( self, label: str, options: OptionSequence, default: Optional[List[str]] = None, format_func=str, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, ) -> List[str]: """Display a multiselect widget. The multiselect widget starts as empty. Parameters ---------- label : str A short label explaining to the user what this select widget is for. options : Sequence, numpy.ndarray, pandas.Series, pandas.DataFrame, or pandas.Index Labels for the select options. This will be cast to str internally by default. For pandas.DataFrame, the first column is selected. default: [str] or None List of default values. format_func : function Function to modify the display of selectbox 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 selectbox. 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 multiselect. on_change : callable An optional callback invoked when this multiselect'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. Returns ------- list A list with the selected options Example ------- >>> options = st.multiselect( ... 'What are your favorite colors', ... ['Green', 'Yellow', 'Red', 'Blue'], ... ['Yellow', 'Red']) >>> >>> st.write('You selected:', options) .. note:: User experience can be degraded for large lists of `options` (100+), as this widget is not designed to handle arbitrary text search efficiently. See this `thread <https://discuss.streamlit.io/t/streamlit-loading-column-data-takes-too-much-time/1791>`_ on the Streamlit community forum for more information and `GitHub issue #1059 <https://github.com/streamlit/streamlit/issues/1059>`_ for updates on the issue. """ key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=default, key=key) opt = ensure_indexable(options) # Perform validation checks and return indices base on the default values. def _check_and_convert_to_indices(opt, default_values): if default_values is None and None not in opt: return None if not isinstance(default_values, list): # This if is done before others because calling if not x (done # right below) when x is of type pd.Series() or np.array() throws a # ValueError exception. if is_type(default_values, "numpy.ndarray") or is_type( default_values, "pandas.core.series.Series"): default_values = list(default_values) elif not default_values or default_values in opt: default_values = [default_values] else: default_values = list(default_values) for value in default_values: if value not in opt: raise StreamlitAPIException( "Every Multiselect default value must exist in options" ) return [opt.index(value) for value in default_values] indices = _check_and_convert_to_indices(opt, default) multiselect_proto = MultiSelectProto() multiselect_proto.label = label default_value = [] if indices is None else indices multiselect_proto.default[:] = default_value multiselect_proto.options[:] = [ str(format_func(option)) for option in opt ] multiselect_proto.form_id = current_form_id(self.dg) if help is not None: multiselect_proto.help = dedent(help) def deserialize_multiselect(ui_value: Optional[List[int]], widget_id: str = "") -> List[str]: current_value = ui_value if ui_value is not None else default_value return [opt[i] for i in current_value] def serialize_multiselect(value): return _check_and_convert_to_indices(opt, value) current_value, set_frontend_value = register_widget( "multiselect", multiselect_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_multiselect, serializer=serialize_multiselect, ) if set_frontend_value: multiselect_proto.value[:] = _check_and_convert_to_indices( opt, current_value) multiselect_proto.set_value = True self.dg._enqueue("multiselect", multiselect_proto) return cast(List[str], current_value)
def number_input( self, label: str, min_value: Optional[Number] = None, max_value: Optional[Number] = None, value=NoValue(), step: Optional[Number] = None, format=None, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, ) -> Number: """Display a numeric input widget. Parameters ---------- label : str A short label explaining to the user what this input is for. min_value : int or float or None The minimum permitted value. If None, there will be no minimum. max_value : int or float or None The maximum permitted value. If None, there will be no maximum. value : int or float or None The value of this widget when it first renders. Defaults to min_value, or 0.0 if min_value is None step : int or float or None The stepping interval. Defaults to 1 if the value is an int, 0.01 otherwise. If the value is not specified, the format parameter will be used. format : str or None A printf-style format string controlling how the interface should display numbers. Output must be purely numeric. This does not impact the return value. Valid formatters: %d %e %f %g %i %u 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 input. on_change : callable An optional callback invoked when this number_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. Returns ------- int or float The current value of the numeric input widget. The return type will match the data type of the value parameter. Example ------- >>> number = st.number_input('Insert a number') >>> st.write('The current number is ', number) """ key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=value, key=key) # Ensure that all arguments are of the same type. number_input_args = [min_value, max_value, value, step] int_args = all( isinstance(a, (numbers.Integral, type(None), NoValue)) for a in number_input_args) float_args = all( isinstance(a, (float, type(None), NoValue)) for a in number_input_args) if not int_args and not float_args: raise StreamlitAPIException( "All numerical arguments must be of the same type." f"\n`value` has {type(value).__name__} type." f"\n`min_value` has {type(min_value).__name__} type." f"\n`max_value` has {type(max_value).__name__} type." f"\n`step` has {type(step).__name__} type.") if isinstance(value, NoValue): if min_value is not None: value = min_value elif int_args and float_args: value = 0.0 # if no values are provided, defaults to float elif int_args: value = 0 else: value = 0.0 int_value = isinstance(value, numbers.Integral) float_value = isinstance(value, float) if value is None: raise StreamlitAPIException( "Default value for number_input should be an int or a float.") else: if format is None: format = "%d" if int_value else "%0.2f" # Warn user if they format an int type as a float or vice versa. if format in ["%d", "%u", "%i"] and float_value: import streamlit as st st.warning("Warning: NumberInput value below has type float," f" but format {format} displays as integer.") elif format[-1] == "f" and int_value: import streamlit as st st.warning( "Warning: NumberInput value below has type int so is" f" displayed as int despite format string {format}.") if step is None: step = 1 if int_value else 0.01 try: float(format % 2) except (TypeError, ValueError): raise StreamlitAPIException( "Format string for st.number_input contains invalid characters: %s" % format) # Ensure that the value matches arguments' types. all_ints = int_value and int_args if (min_value and min_value > value) or (max_value and max_value < value): raise StreamlitAPIException( "The default `value` of %(value)s " "must lie between the `min_value` of %(min)s " "and the `max_value` of %(max)s, inclusively." % { "value": value, "min": min_value, "max": max_value }) # Bounds checks. JSNumber produces human-readable exceptions that # we simply re-package as StreamlitAPIExceptions. try: if all_ints: if min_value is not None: JSNumber.validate_int_bounds(min_value, "`min_value`") # type: ignore if max_value is not None: JSNumber.validate_int_bounds(max_value, "`max_value`") # type: ignore if step is not None: JSNumber.validate_int_bounds(step, "`step`") # type: ignore JSNumber.validate_int_bounds(value, "`value`") else: if min_value is not None: JSNumber.validate_float_bounds(min_value, "`min_value`") if max_value is not None: JSNumber.validate_float_bounds(max_value, "`max_value`") if step is not None: JSNumber.validate_float_bounds(step, "`step`") JSNumber.validate_float_bounds(value, "`value`") except JSNumberBoundsException as e: raise StreamlitAPIException(str(e)) number_input_proto = NumberInputProto() number_input_proto.data_type = (NumberInputProto.INT if all_ints else NumberInputProto.FLOAT) number_input_proto.label = label number_input_proto.default = value number_input_proto.form_id = current_form_id(self.dg) if help is not None: number_input_proto.help = dedent(help) if min_value is not None: number_input_proto.min = min_value number_input_proto.has_min = True if max_value is not None: number_input_proto.max = max_value number_input_proto.has_max = True if step is not None: number_input_proto.step = step if format is not None: number_input_proto.format = format def deserialize_number_input(ui_value, widget_id=""): return ui_value if ui_value is not None else value current_value, set_frontend_value = register_widget( "number_input", number_input_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_number_input, serializer=lambda x: x, ) if set_frontend_value: number_input_proto.value = current_value number_input_proto.set_value = True self.dg._enqueue("number_input", number_input_proto) return cast(Number, current_value)
def _date_input( self, label: str, value=None, min_value=None, max_value=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, ctx: Optional[ScriptRunContext] = None, ) -> Union[date, Tuple[date, ...]]: key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=value, key=key) # Set value default. if value is None: value = datetime.now().date() single_value = isinstance(value, (date, datetime)) range_value = isinstance(value, (list, tuple)) and len(value) in (0, 1, 2) if not single_value and not range_value: raise StreamlitAPIException( "DateInput value should either be an date/datetime or a list/tuple of " "0 - 2 date/datetime values") if single_value: value = [value] value = [v.date() if isinstance(v, datetime) else v for v in value] if isinstance(min_value, datetime): min_value = min_value.date() elif min_value is None: if value: min_value = value[0] - relativedelta.relativedelta(years=10) else: min_value = date.today() - relativedelta.relativedelta( years=10) if isinstance(max_value, datetime): max_value = max_value.date() elif max_value is None: if value: max_value = value[-1] + relativedelta.relativedelta(years=10) else: max_value = date.today() + relativedelta.relativedelta( years=10) if value: start_value = value[0] end_value = value[-1] if (start_value < min_value) or (end_value > max_value): raise StreamlitAPIException( f"The default `value` of {value} " f"must lie between the `min_value` of {min_value} " f"and the `max_value` of {max_value}, inclusively.") date_input_proto = DateInputProto() date_input_proto.is_range = range_value if help is not None: date_input_proto.help = dedent(help) date_input_proto.label = label date_input_proto.default[:] = [ date.strftime(v, "%Y/%m/%d") for v in value ] date_input_proto.min = date.strftime(min_value, "%Y/%m/%d") date_input_proto.max = date.strftime(max_value, "%Y/%m/%d") date_input_proto.form_id = current_form_id(self.dg) date_input_proto.disabled = disabled def deserialize_date_input(ui_value, widget_id=""): if ui_value is not None: return_value = [ datetime.strptime(v, "%Y/%m/%d").date() for v in ui_value ] else: return_value = value return return_value[0] if single_value else tuple(return_value) def serialize_date_input(v): range_value = isinstance(v, (list, tuple)) to_serialize = list(v) if range_value else [v] return [date.strftime(v, "%Y/%m/%d") for v in to_serialize] current_value, set_frontend_value = register_widget( "date_input", date_input_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_date_input, serializer=serialize_date_input, ctx=ctx, ) if set_frontend_value: date_input_proto.value[:] = serialize_date_input(current_value) date_input_proto.set_value = True self.dg._enqueue("date_input", date_input_proto) return cast(date, current_value)
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, ctx: Optional[ScriptRunContext], ) -> Any: key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=None if index == 0 else index, key=key) opt = ensure_indexable(options) if not isinstance(index, int): raise StreamlitAPIException("Radio Value has invalid type: %s" % type(index).__name__) if len(opt) > 0 and not 0 <= index < len(opt): raise StreamlitAPIException( "Radio index must be between 0 and length of options") radio_proto = RadioProto() radio_proto.label = label radio_proto.default = index radio_proto.options[:] = [str(format_func(option)) for option in opt] radio_proto.form_id = current_form_id(self.dg) radio_proto.disabled = disabled if help is not None: radio_proto.help = dedent(help) def deserialize_radio(ui_value, widget_id=""): idx = ui_value if ui_value is not None else index return opt[idx] if len(opt) > 0 and opt[idx] is not None else None def serialize_radio(v): if len(options) == 0: return 0 return index_(options, v) current_value, set_frontend_value = register_widget( "radio", radio_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_radio, serializer=serialize_radio, ctx=ctx, ) if set_frontend_value: radio_proto.value = serialize_radio(current_value) radio_proto.set_value = True self.dg._enqueue("radio", radio_proto) return cast(str, current_value)
def _number_input( self, label: str, min_value: Optional[Number] = None, max_value: Optional[Number] = None, value: Union[NoValue, Number, None] = NoValue(), step: Optional[Number] = 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, ctx: Optional[ScriptRunContext] = None, ) -> Number: key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules( default_value=None if isinstance(value, NoValue) else value, key=key) # Ensure that all arguments are of the same type. number_input_args = [min_value, max_value, value, step] int_args = all( isinstance(a, (numbers.Integral, type(None), NoValue)) for a in number_input_args) float_args = all( isinstance(a, (float, type(None), NoValue)) for a in number_input_args) if not int_args and not float_args: raise StreamlitAPIException( "All numerical arguments must be of the same type." f"\n`value` has {type(value).__name__} type." f"\n`min_value` has {type(min_value).__name__} type." f"\n`max_value` has {type(max_value).__name__} type." f"\n`step` has {type(step).__name__} type.") if isinstance(value, NoValue): if min_value is not None: value = min_value elif int_args and float_args: value = 0.0 # if no values are provided, defaults to float elif int_args: value = 0 else: value = 0.0 int_value = isinstance(value, numbers.Integral) float_value = isinstance(value, float) if value is None: raise StreamlitAPIException( "Default value for number_input should be an int or a float.") else: if format is None: format = "%d" if int_value else "%0.2f" # Warn user if they format an int type as a float or vice versa. if format in ["%d", "%u", "%i"] and float_value: import streamlit as st st.warning("Warning: NumberInput value below has type float," f" but format {format} displays as integer.") elif format[-1] == "f" and int_value: import streamlit as st st.warning( "Warning: NumberInput value below has type int so is" f" displayed as int despite format string {format}.") if step is None: step = 1 if int_value else 0.01 try: float(format % 2) except (TypeError, ValueError): raise StreamlitAPIException( "Format string for st.number_input contains invalid characters: %s" % format) # Ensure that the value matches arguments' types. all_ints = int_value and int_args if (min_value and min_value > value) or (max_value and max_value < value): raise StreamlitAPIException( "The default `value` of %(value)s " "must lie between the `min_value` of %(min)s " "and the `max_value` of %(max)s, inclusively." % { "value": value, "min": min_value, "max": max_value }) # Bounds checks. JSNumber produces human-readable exceptions that # we simply re-package as StreamlitAPIExceptions. try: if all_ints: if min_value is not None: JSNumber.validate_int_bounds(min_value, "`min_value`") # type: ignore if max_value is not None: JSNumber.validate_int_bounds(max_value, "`max_value`") # type: ignore if step is not None: JSNumber.validate_int_bounds(step, "`step`") # type: ignore JSNumber.validate_int_bounds(value, "`value`") # type: ignore else: if min_value is not None: JSNumber.validate_float_bounds(min_value, "`min_value`") if max_value is not None: JSNumber.validate_float_bounds(max_value, "`max_value`") if step is not None: JSNumber.validate_float_bounds(step, "`step`") JSNumber.validate_float_bounds(value, "`value`") except JSNumberBoundsException as e: raise StreamlitAPIException(str(e)) number_input_proto = NumberInputProto() number_input_proto.data_type = (NumberInputProto.INT if all_ints else NumberInputProto.FLOAT) number_input_proto.label = label number_input_proto.default = value number_input_proto.form_id = current_form_id(self.dg) number_input_proto.disabled = disabled if help is not None: number_input_proto.help = dedent(help) if min_value is not None: number_input_proto.min = min_value number_input_proto.has_min = True if max_value is not None: number_input_proto.max = max_value number_input_proto.has_max = True if step is not None: number_input_proto.step = step if format is not None: number_input_proto.format = format def deserialize_number_input(ui_value, widget_id=""): return ui_value if ui_value is not None else value current_value, set_frontend_value = register_widget( "number_input", number_input_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_number_input, serializer=lambda x: x, ctx=ctx, ) if set_frontend_value: number_input_proto.value = current_value number_input_proto.set_value = True self.dg._enqueue("number_input", number_input_proto) return cast(Number, current_value)
def _multiselect( self, label: str, options: OptionSequence, default: Optional[Any] = None, 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 arguments: disabled: bool = False, ctx: Optional[ScriptRunContext] = None, ) -> List[Any]: key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=default, key=key) opt = ensure_indexable(options) # Perform validation checks and return indices base on the default values. def _check_and_convert_to_indices(opt, default_values): if default_values is None and None not in opt: return None if not isinstance(default_values, list): # This if is done before others because calling if not x (done # right below) when x is of type pd.Series() or np.array() throws a # ValueError exception. if is_type(default_values, "numpy.ndarray") or is_type( default_values, "pandas.core.series.Series"): default_values = list(default_values) elif not default_values or default_values in opt: default_values = [default_values] else: default_values = list(default_values) for value in default_values: if value not in opt: raise StreamlitAPIException( "Every Multiselect default value must exist in options" ) return [opt.index(value) for value in default_values] indices = _check_and_convert_to_indices(opt, default) multiselect_proto = MultiSelectProto() multiselect_proto.label = label default_value = [] if indices is None else indices multiselect_proto.default[:] = default_value multiselect_proto.options[:] = [ str(format_func(option)) for option in opt ] multiselect_proto.form_id = current_form_id(self.dg) multiselect_proto.disabled = disabled if help is not None: multiselect_proto.help = dedent(help) def deserialize_multiselect(ui_value: Optional[List[int]], widget_id: str = "") -> List[str]: current_value = ui_value if ui_value is not None else default_value return [opt[i] for i in current_value] def serialize_multiselect(value): return _check_and_convert_to_indices(opt, value) current_value, set_frontend_value = register_widget( "multiselect", multiselect_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_multiselect, serializer=serialize_multiselect, ctx=ctx, ) if set_frontend_value: multiselect_proto.value[:] = _check_and_convert_to_indices( opt, current_value) multiselect_proto.set_value = True self.dg._enqueue("multiselect", multiselect_proto) return cast(List[str], current_value)
def select_slider( self, label: str, options: OptionSequence = [], value=None, format_func=str, key: Optional[Key] = None, help: Optional[str] = None, on_change: Optional[WidgetCallback] = None, args: Optional[WidgetArgs] = None, kwargs: Optional[WidgetKwargs] = None, ): """ Display a slider widget to select items from a list. This also allows you to render a range slider by passing a two-element tuple or list as the `value`. The difference between `st.select_slider` and `st.slider` is that `select_slider` accepts any datatype and takes an iterable set of options, while `slider` only accepts numerical or date/time data and takes a range as input. Parameters ---------- label : str A short label explaining to the user what this slider is for. options : Sequence, numpy.ndarray, pandas.Series, pandas.DataFrame, or pandas.Index Labels for the slider options. All options will be cast to str internally by default. For pandas.DataFrame, the first column is selected. 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 first option. format_func : function Function to modify the display of the labels from the options. argument. It receives the option as an argument and its output will be cast to str. 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 select slider. on_change : callable An optional callback invoked when this select_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. Returns ------- any value or tuple of any value The current value of the slider widget. The return type will match the data type of the value parameter. Examples -------- >>> color = st.select_slider( ... 'Select a color of the rainbow', ... options=['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']) >>> st.write('My favorite color is', color) And here's an example of a range select slider: >>> start_color, end_color = st.select_slider( ... 'Select a range of color wavelength', ... options=['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'], ... value=('red', 'blue')) >>> st.write('You selected wavelengths between', start_color, 'and', end_color) """ key = to_key(key) check_callback_rules(self.dg, on_change) check_session_state_rules(default_value=value, key=key) opt = ensure_indexable(options) if len(opt) == 0: raise StreamlitAPIException("The `options` argument needs to be non-empty") is_range_value = isinstance(value, (list, tuple)) def as_index_list(v): is_range_value = isinstance(v, (list, tuple)) if is_range_value: slider_value = [index_(opt, val) for val in v] start, end = slider_value if start > end: slider_value = [end, start] return slider_value else: # Simplify future logic by always making value a list try: return [index_(opt, v)] except ValueError: if value is not None: raise return [0] # Convert element to index of the elements slider_value = as_index_list(value) slider_proto = SliderProto() slider_proto.label = label slider_proto.format = "%s" slider_proto.default[:] = slider_value slider_proto.min = 0 slider_proto.max = len(opt) - 1 slider_proto.step = 1 # default for index changes slider_proto.data_type = SliderProto.INT slider_proto.options[:] = [str(format_func(option)) for option in opt] slider_proto.form_id = current_form_id(self.dg) if help is not None: slider_proto.help = dedent(help) def deserialize_select_slider(ui_value, widget_id=""): if not ui_value: # Widget has not been used; fallback to the original value, ui_value = slider_value # The widget always returns floats, so convert to ints before indexing return_value = list(map(lambda x: opt[int(x)], ui_value)) # type: ignore[no-any-return] # If the original value was a list/tuple, so will be the output (and vice versa) return tuple(return_value) if is_range_value else return_value[0] def serialize_select_slider(v): return as_index_list(v) current_value, set_frontend_value = register_widget( "slider", slider_proto, user_key=key, on_change_handler=on_change, args=args, kwargs=kwargs, deserializer=deserialize_select_slider, serializer=serialize_select_slider, ) if set_frontend_value: slider_proto.value[:] = serialize_select_slider(current_value) slider_proto.set_value = True self.dg._enqueue("slider", slider_proto) return current_value