Exemple #1
0
        def marshall_component(dg,
                               element: Element) -> Union[Any, Type[NoValue]]:
            element.component_instance.component_name = self.name
            element.component_instance.form_id = current_form_id(dg)
            if self.url is not None:
                element.component_instance.url = self.url

            # Normally, a widget's element_hash (which determines
            # its identity across multiple runs of an app) is computed
            # by hashing the entirety of its protobuf. This means that,
            # if any of the arguments to the widget are changed, Streamlit
            # considers it a new widget instance and it loses its previous
            # state.
            #
            # However! If a *component* has a `key` argument, then the
            # component's hash identity is determined by entirely by
            # `component_name + url + key`. This means that, when `key`
            # exists, the component will maintain its identity even when its
            # other arguments change, and the component's iframe won't be
            # remounted on the frontend.
            #
            # So: if `key` is None, we marshall the element's arguments
            # *before* computing its widget_ui_value (which creates its hash).
            # If `key` is not None, we marshall the arguments *after*.

            def marshall_element_args():
                element.component_instance.json_args = serialized_json_args
                element.component_instance.special_args.extend(special_args)

            if key is None:
                marshall_element_args()

            def deserialize_component(ui_value, widget_id=""):
                # ui_value is an object from json, an ArrowTable proto, or a bytearray
                return ui_value

            ctx = get_script_run_ctx()
            widget_value, _ = register_widget(
                element_type="component_instance",
                element_proto=element.component_instance,
                user_key=key,
                widget_func_name=self.name,
                deserializer=deserialize_component,
                serializer=lambda x: x,
                ctx=ctx,
            )

            if key is not None:
                marshall_element_args()

            if widget_value is None:
                widget_value = default
            elif isinstance(widget_value, ArrowTableProto):
                widget_value = component_arrow.arrow_proto_to_dataframe(
                    widget_value)

            # widget_value will be either None or whatever the component's most
            # recent setWidgetValue value is. We coerce None -> NoValue,
            # because that's what DeltaGenerator._enqueue expects.
            return widget_value if widget_value is not None else NoValue
Exemple #2
0
    def _button(
        self,
        label: str,
        key: Optional[str],
        help: Optional[str],
        is_form_submitter: bool,
        on_click: Optional[WidgetCallback] = None,
        args: Optional[WidgetArgs] = None,
        kwargs: Optional[WidgetKwargs] = None,
        *,  # keyword-only arguments:
        disabled: bool = False,
        ctx: Optional[ScriptRunContext] = None,
    ) -> bool:
        if not is_form_submitter:
            check_callback_rules(self.dg, on_click)
        check_session_state_rules(default_value=None,
                                  key=key,
                                  writes_allowed=False)

        # It doesn't make sense to create a button inside a form (except
        # for the "Form Submitter" button that's automatically created in
        # every form). We throw an error to warn the user about this.
        # We omit this check for scripts running outside streamlit, because
        # they will have no script_run_ctx.
        if streamlit._is_running_with_streamlit:
            if is_in_form(self.dg) and not is_form_submitter:
                raise StreamlitAPIException(
                    f"`st.button()` can't be used in an `st.form()`.{FORM_DOCS_INFO}"
                )
            elif not is_in_form(self.dg) and is_form_submitter:
                raise StreamlitAPIException(
                    f"`st.form_submit_button()` must be used inside an `st.form()`.{FORM_DOCS_INFO}"
                )

        button_proto = ButtonProto()
        button_proto.label = label
        button_proto.default = False
        button_proto.is_form_submitter = is_form_submitter
        button_proto.form_id = current_form_id(self.dg)
        button_proto.disabled = disabled
        if help is not None:
            button_proto.help = dedent(help)

        def deserialize_button(ui_value: bool, widget_id: str = "") -> bool:
            return ui_value or False

        current_value, _ = register_widget(
            "button",
            button_proto,
            user_key=key,
            on_change_handler=on_click,
            args=args,
            kwargs=kwargs,
            deserializer=deserialize_button,
            serializer=bool,
            ctx=ctx,
        )
        self.dg._enqueue("button", button_proto)
        return cast(bool, current_value)
Exemple #3
0
    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)
Exemple #4
0
    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)
Exemple #5
0
    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)
Exemple #6
0
    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)
Exemple #7
0
    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 _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 _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)
        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,
        )

        # 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_select_slider(current_value)
            slider_proto.set_value = True

        self.dg._enqueue("slider", slider_proto)
        return current_value
Exemple #10
0
    def _selectbox(
        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 arguments:
        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=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,
            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.
        selectbox_proto.disabled = disabled
        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)
Exemple #11
0
    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)
Exemple #12
0
    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)
Exemple #13
0
    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)
Exemple #14
0
    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)
Exemple #15
0
    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)
Exemple #16
0
    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)
Exemple #17
0
    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)