def beta_expander(self, label=None, expanded=False) -> "DeltaGenerator":
        """Insert a multi-element container that can be expanded/collapsed.

        Inserts a container into your app that can be used to hold multiple elements
        and can be expanded or collapsed by the user. When collapsed, all that is
        visible is the provided label.

        To add elements to the returned container, you can use "with" notation
        (preferred) or just call methods directly on the returned object. See
        examples below.

        .. warning::
            Currently, you may not put expanders inside another expander.

        Parameters
        ----------
        label : str
            A string to use as the header for the expander.
        expanded : bool
            If True, initializes the expander in "expanded" state. Defaults to
            False (collapsed).

        Examples
        --------
        >>> st.line_chart({"data": [1, 5, 2, 6, 2, 1]})
        >>>
        >>> with st.beta_expander("See explanation"):
        ...     st.write(\"\"\"
        ...         The chart above shows some numbers I picked for you.
        ...         I rolled actual dice for these, so they're *guaranteed* to
        ...         be random.
        ...     \"\"\")
        ...     st.image("https://static.streamlit.io/examples/dice.jpg")

        .. output ::
            https://static.streamlit.io/0.66.0-2BLtg/index.html?id=7v2tgefVbW278gemvYrRny
            height: 750px

        """
        if label is None:
            raise StreamlitAPIException("A label is required for an expander")

        expandable_proto = Block_pb2.Block.Expandable()
        expandable_proto.expanded = expanded
        expandable_proto.label = label

        block_proto = Block_pb2.Block()
        block_proto.allow_empty = True
        block_proto.expandable.CopyFrom(expandable_proto)

        return self._block(block_proto=block_proto)
    def enqueue(self, msg: ForwardMsg) -> None:
        if msg.HasField(
                "page_config_changed") and not self._set_page_config_allowed:
            raise StreamlitAPIException(
                "`set_page_config()` can only be called once per app, " +
                "and must be called as the first Streamlit command in your script.\n\n"
                + "For more information refer to the [docs]" +
                "(https://docs.streamlit.io/en/stable/api.html#streamlit.set_page_config)."
            )

        if msg.HasField("delta") or msg.HasField("page_config_changed"):
            self._set_page_config_allowed = False

        self._enqueue(msg)
Beispiel #3
0
    def __init__(
        self,
        name: str,
        path: Optional[str] = None,
        url: Optional[str] = None,
    ):
        if (path is None and url is None) or (path is not None
                                              and url is not None):
            raise StreamlitAPIException(
                "Either 'path' or 'url' must be set, but not both.")

        self.name = name
        self.path = path
        self.url = url
Beispiel #4
0
    def time_input(self, label, value=None, key=None):
        """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
            An optional string 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.

        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)

        """
        # Set value default.
        if value is None:
            value = datetime.now().time()

        # 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()

        time_input_proto = TimeInputProto()
        time_input_proto.label = label
        time_input_proto.default = time.strftime(value, "%H:%M")

        ui_value = register_widget("time_input",
                                   time_input_proto,
                                   user_key=key)
        current_value = (datetime.strptime(ui_value, "%H:%M").time()
                         if ui_value is not None else value)
        return self.dg._enqueue("time_input", time_input_proto, current_value)
Beispiel #5
0
    def __setitem__(self, key: str, value: Any) -> None:
        from streamlit.report_thread import get_report_ctx, ReportContext

        ctx = get_report_ctx()

        if ctx is not None:
            widget_ids = ctx.widget_ids_this_run.items()
            form_ids = ctx.form_ids_this_run.items()

            if key in widget_ids or key in form_ids:
                raise StreamlitAPIException(
                    f"`st.session_state.{key}` cannot be modified after the widget"
                    f" with key `{key}` is instantiated.")
        self._new_session_state[key] = value
Beispiel #6
0
    def test_markdown_flag(self):
        """Test that ExceptionProtos for StreamlitAPIExceptions (and
        subclasses) have the "message_is_markdown" flag set.
        """
        proto = ExceptionProto()
        exception.marshall(proto, RuntimeError("oh no!"))
        self.assertFalse(proto.message_is_markdown)

        proto = ExceptionProto()
        exception.marshall(proto, StreamlitAPIException("oh no!"))
        self.assertTrue(proto.message_is_markdown)

        proto = ExceptionProto()
        exception.marshall(proto, errors.DuplicateWidgetID("oh no!"))
        self.assertTrue(proto.message_is_markdown)
    def _button(
        self,
        label: str,
        key: Optional[str],
        help: Optional[str],
        is_form_submitter: bool,
    ) -> "streamlit.delta_generator.DeltaGenerator":
        button_proto = ButtonProto()

        # 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 report_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.submit_button()` must be used inside an `st.form()`.{FORM_DOCS_INFO}"
                )

        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)
        if help is not None:
            button_proto.help = help

        ui_value = register_widget("button", button_proto, user_key=key)
        current_value = ui_value if ui_value is not None else False

        return self.dg._enqueue("button", button_proto,
                                current_value)  # type: ignore
Beispiel #8
0
def marshall(proto, figure_or_dot, use_container_width):
    """Construct a GraphViz chart object.

    See DeltaGenerator.graphviz_chart for docs.
    """

    if type_util.is_graphviz_chart(figure_or_dot):
        dot = figure_or_dot.source
    elif isinstance(figure_or_dot, str):
        dot = figure_or_dot
    else:
        raise StreamlitAPIException("Unhandled type for graphviz chart: %s" %
                                    type(figure_or_dot))

    proto.spec = dot
    proto.use_container_width = use_container_width
def clean_filename(name: str) -> str:
    """
    Taken from https://github.com/django/django/blob/196a99da5d9c4c33a78259a58d38fb114a4d2ee8/django/utils/text.py#L225-L238

    Return the given string converted to a string that can be used for a clean
    filename. Remove leading and trailing spaces; convert other spaces to
    underscores; and remove anything that is not an alphanumeric, dash,
    underscore, or dot.
    """
    s = str(name).strip().replace(" ", "_")
    s = re.sub(r"(?u)[^-\w.]", "", s)

    if s in {"", ".", ".."}:
        raise StreamlitAPIException("Could not derive file name from '%s'" %
                                    name)
    return s
Beispiel #10
0
    def __setitem__(self, user_key: str, value: Any) -> None:
        from streamlit.report_thread import get_report_ctx

        ctx = get_report_ctx()

        if ctx is not None:
            widget_id = self._key_id_mapping.get(user_key, None)
            widget_ids = ctx.widget_ids_this_run.items()
            form_ids = ctx.form_ids_this_run.items()

            if widget_id in widget_ids or user_key in form_ids:
                raise StreamlitAPIException(
                    f"`st.session_state.{user_key}` cannot be modified after the widget"
                    f" with key `{user_key}` is instantiated.")

        self._new_session_state[user_key] = value
Beispiel #11
0
    def enqueue(self, msg: ForwardMsg) -> None:
        if msg.HasField(
                "page_config_changed") and not self._set_page_config_allowed:
            raise StreamlitAPIException(
                "`set_page_config()` can only be called once per app, " +
                "and must be called as the first Streamlit command in your script.\n\n"
                + "For more information refer to the [docs]" +
                "(https://docs.streamlit.io/library/api-reference/utilities/st.set_page_config)."
            )

        # We want to disallow set_page config if one of the following occurs:
        # - set_page_config was called on this message
        # - The script has already started and a different st call occurs (a delta)
        if msg.HasField("page_config_changed") or (msg.HasField("delta") and
                                                   self._has_script_started):
            self._set_page_config_allowed = False

        self._enqueue(msg)
Beispiel #12
0
        def wrapper(*args, **kwargs):
            if name in streamlit_methods:
                if self._root_container == RootContainer.SIDEBAR:
                    message = (
                        "Method `%(name)s()` does not exist for "
                        "`st.sidebar`. Did you mean `st.%(name)s()`?" % {"name": name}
                    )
                else:
                    message = (
                        "Method `%(name)s()` does not exist for "
                        "`DeltaGenerator` objects. Did you mean "
                        "`st.%(name)s()`?" % {"name": name}
                    )
            else:
                message = "`%(name)s()` is not a valid Streamlit command." % {
                    "name": name
                }

            raise StreamlitAPIException(message)
    def __setitem__(self, user_key: str, value: Any) -> None:
        """Set the value of the session_state entry with the given user_key.

        If the key corresponds to a widget or form that's been instantiated
        during the current script run, raise an Exception instead.
        """
        from streamlit.script_run_context import get_script_run_ctx

        ctx = get_script_run_ctx()

        if ctx is not None:
            widget_id = self._key_id_mapping.get(user_key, None)
            widget_ids = ctx.widget_ids_this_run
            form_ids = ctx.form_ids_this_run

            if widget_id in widget_ids or user_key in form_ids:
                raise StreamlitAPIException(
                    f"`st.session_state.{user_key}` cannot be modified after the widget"
                    f" with key `{user_key}` is instantiated.")

        self._new_session_state[user_key] = value
Beispiel #14
0
    def enqueue(self, msg):
        """Enqueue a new ForwardMsg to our browser queue.

        This can be called on both the main thread and a ScriptRunner
        run thread.

        Parameters
        ----------
        msg : ForwardMsg
            The message to enqueue

        """
        if not config.get_option("client.displayEnabled"):
            return

        # Avoid having two maybe_handle_execution_control_request running on
        # top of each other when tracer is installed. This leads to a lock
        # contention.
        if not config.get_option("runner.installTracer"):
            # If we have an active ScriptRunner, signal that it can handle an
            # execution control request. (Copy the scriptrunner reference to
            # avoid it being unset from underneath us, as this function can be
            # called outside the main thread.)
            scriptrunner = self._scriptrunner

            if scriptrunner is not None:
                scriptrunner.maybe_handle_execution_control_request()

        if msg.HasField("page_config_changed") and not self._set_page_config_allowed:
            raise StreamlitAPIException(
                "`beta_set_page_config()` can only be called once per app, "
                + "and must be called as the first Streamlit command in your script.\n\n"
                + "For more information refer to the [docs]"
                + "(https://docs.streamlit.io/en/stable/api.html#streamlit.beta_set_page_config)."
            )

        if msg.HasField("delta") or msg.HasField("page_config_changed"):
            self._set_page_config_allowed = False

        self._report.enqueue(msg)
Beispiel #15
0
    def beta_expander(self, label=None, expanded=False):
        """Creates a container that can be expanded and collapsed.

        [TODO: get more container verbage]
        Similar to `st.container`, `st.expander` provides a container
        to add elements to. However, it has the added benefit of being expandable and
        collapsible. Users will be able to expand and collapse the container that is
        identifiable with the provided label.

        Parameters
        ----------
        label : str
            A short label used as the header for the expander.
            This will always be displayed even when the container is collapsed.
        expanded : boolean
            The default state for the expander.
            Defaults to False

        Returns
        -------
        [TODO] Technically a delta generator but let's please not tell the users that...

        Examples
        --------
        >>> expander = st.beta_expander("Expand Me")
        >>> expander.write("I can be expanded")

        """
        if label is None:
            raise StreamlitAPIException("A label is required for an expander")

        expandable_proto = Block_pb2.Block.Expandable()
        expandable_proto.expanded = expanded
        expandable_proto.label = label

        block_proto = Block_pb2.Block()
        block_proto.expandable.CopyFrom(expandable_proto)

        return self._block(block_proto=block_proto)
Beispiel #16
0
def check_session_state_rules(default_value: Any,
                              key: Optional[str],
                              writes_allowed: bool = True) -> None:
    global _shown_default_value_warning

    if key is None or not streamlit._is_running_with_streamlit:
        return

    session_state = get_session_state()
    if not session_state.is_new_state_value(key):
        return

    if not writes_allowed:
        raise StreamlitAPIException(
            "Values for st.button, st.download_button, st.file_uploader, and "
            "st.form cannot be set using st.session_state.")

    if default_value is not None and not _shown_default_value_warning:
        streamlit.warning(
            f'The widget with key "{key}" was created with a default value but'
            " also had its value set via the Session State API.")
        _shown_default_value_warning = True
Beispiel #17
0
def _maybe_melt_data_for_add_rows(data, delta_type, last_index):
    import pandas as pd

    # For some delta types we have to reshape the data structure
    # otherwise the input data and the actual data used
    # by vega_lite will be different and it will throw an error.
    if (
        delta_type in DELTA_TYPES_THAT_MELT_DATAFRAMES
        or delta_type in ARROW_DELTA_TYPES_THAT_MELT_DATAFRAMES
    ):
        if not isinstance(data, pd.DataFrame):
            data = type_util.convert_anything_to_df(data)

        if type(data.index) is pd.RangeIndex:
            old_step = _get_pandas_index_attr(data, "step")

            # We have to drop the predefined index
            data = data.reset_index(drop=True)

            old_stop = _get_pandas_index_attr(data, "stop")

            if old_step is None or old_stop is None:
                raise StreamlitAPIException(
                    "'RangeIndex' object has no attribute 'step'"
                )

            start = last_index + old_step
            stop = last_index + old_step + old_stop

            data.index = pd.RangeIndex(start=start, stop=stop, step=old_step)
            last_index = stop - 1

        index_name = data.index.name
        if index_name is None:
            index_name = "index"

        data = pd.melt(data.reset_index(), id_vars=[index_name])

    return data, last_index
Beispiel #18
0
        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]
Beispiel #19
0
    def determine_delta_color_and_direction(
        self,
        delta_color: DeltaColor,
        delta: Delta,
    ) -> MetricColorAndDirection:
        if delta_color not in {"normal", "inverse", "off"}:
            raise StreamlitAPIException(
                f"'{str(delta_color)}' is not an accepted value. delta_color only accepts: "
                "'normal', 'inverse', or 'off'")

        if delta is None or delta == "":
            return MetricColorAndDirection(
                color=MetricProto.MetricColor.GRAY,
                direction=MetricProto.MetricDirection.NONE,
            )

        if self.is_negative(delta):
            if delta_color == "normal":
                cd_color = MetricProto.MetricColor.RED
            elif delta_color == "inverse":
                cd_color = MetricProto.MetricColor.GREEN
            else:
                cd_color = MetricProto.MetricColor.GRAY
            cd_direction = MetricProto.MetricDirection.DOWN
        else:
            if delta_color == "normal":
                cd_color = MetricProto.MetricColor.GREEN
            elif delta_color == "inverse":
                cd_color = MetricProto.MetricColor.RED
            else:
                cd_color = MetricProto.MetricColor.GRAY
            cd_direction = MetricProto.MetricDirection.UP

        return MetricColorAndDirection(
            color=cd_color,
            direction=cd_direction,
        )
    def register_component(self, component: CustomComponent) -> None:
        """Register a CustomComponent.

        Parameters
        ----------
        component : CustomComponent
            The component to register.
        """

        # Validate the component's path
        abspath = component.abspath
        if abspath is not None and not os.path.isdir(abspath):
            raise StreamlitAPIException(f"No such component directory: '{abspath}'")

        with self._lock:
            existing = self._components.get(component.name)
            self._components[component.name] = component

        if existing is not None and component != existing:
            LOGGER.warning(
                "%s overriding previously-registered %s", component, existing,
            )

        LOGGER.debug("Registered component %s", component)
Beispiel #21
0
    def color_picker(self, label, value=None, key=None):
        """Display a color picker widget.

        Note: This is a beta feature. See
        https://docs.streamlit.io/en/latest/api.html#pre-release-features for more
        information.

        Parameters
        ----------
        label : str
            A short label explaining to the user what this input is for.
        value : str or None
            The hex value of this widget when it first renders. If None,
            defaults to black.
        key : str
            An optional string 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.

        Returns
        -------
        str
            The selected color as a hex string.

        Example
        -------
        >>> color = st.color_picker('Pick A Color', '#00f900')
        >>> st.write('The current color is', color)

        """
        # 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)

        ui_value = register_widget("color_picker", color_picker_proto, user_key=key)
        current_value = ui_value if ui_value is not None else value
        return self.dg._enqueue("color_picker", color_picker_proto, str(current_value))
Beispiel #22
0
    def create_instance(
        self,
        *args,
        default: Any = None,
        key: Optional[str] = None,
        **kwargs,
    ) -> Any:
        """Create a new instance of the component.

        Parameters
        ----------
        *args
            Must be empty; all args must be named. (This parameter exists to
            enforce correct use of the function.)
        default: any or None
            The default return value for the component. This is returned when
            the component's frontend hasn't yet specified a value with
            `setComponentValue`.
        key: str or None
            If not None, this is the user key we use to generate the
            component's "widget ID".
        **kwargs
            Keyword args to pass to the component.

        Returns
        -------
        any or None
            The component's widget value.

        """
        if len(args) > 0:
            raise MarshallComponentException(
                f"Argument '{args[0]}' needs a label")

        try:
            import pyarrow
            from streamlit.elements import arrow_table
        except ImportError:
            import sys

            if sys.version_info >= (3, 9):
                raise StreamlitAPIException(
                    """To use Custom Components in Streamlit, you need to install
PyArrow. Unfortunately, PyArrow does not yet support Python 3.9.

You can either switch to Python 3.8 with an environment manager like PyEnv, or stay on 3.9 by
[installing Streamlit with conda](https://discuss.streamlit.io/t/note-installation-issues-with-python-3-9-and-streamlit/6946):

`conda install -c conda-forge streamlit`

""")
            else:
                raise StreamlitAPIException(
                    """To use Custom Components in Streamlit, you need to install
PyArrow. To do so locally:

`pip install pyarrow`

And if you're using Streamlit Sharing, add "pyarrow" to your requirements.txt."""
                )

        # In addition to the custom kwargs passed to the component, we also
        # send the special 'default' and 'key' params to the component
        # frontend.
        all_args = dict(kwargs, **{"default": default, "key": key})

        json_args = {}
        special_args = []
        for arg_name, arg_val in all_args.items():
            if type_util.is_bytes_like(arg_val):
                bytes_arg = SpecialArg()
                bytes_arg.key = arg_name
                bytes_arg.bytes = to_bytes(arg_val)
                special_args.append(bytes_arg)
            elif type_util.is_dataframe_like(arg_val):
                dataframe_arg = SpecialArg()
                dataframe_arg.key = arg_name
                arrow_table.marshall(dataframe_arg.arrow_dataframe.data,
                                     arg_val)
                special_args.append(dataframe_arg)
            else:
                json_args[arg_name] = arg_val

        try:
            serialized_json_args = json.dumps(json_args)
        except BaseException as e:
            raise MarshallComponentException(
                "Could not convert component args to JSON", e)

        def marshall_component(element: Element) -> Union[Any, Type[NoValue]]:
            element.component_instance.component_name = self.name
            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()

            widget_value = register_widget(
                element_type="component_instance",
                element_proto=element.component_instance,
                user_key=key,
                widget_func_name=self.name,
            )

            if key is not None:
                marshall_element_args()

            if widget_value is None:
                widget_value = default
            elif isinstance(widget_value, ArrowTableProto):
                widget_value = arrow_table.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

        # We currently only support writing to st._main, but this will change
        # when we settle on an improved API in a post-layout world.
        element = Element()
        return_value = marshall_component(element)
        result = streamlit._main._enqueue("component_instance",
                                          element.component_instance,
                                          return_value)

        return result
Beispiel #23
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)
Beispiel #24
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)
    def number_input(
            self,
            label,
            min_value=None,
            max_value=None,
            value=NoValue(),
            step=None,
            format=None,
            key=None,
            help=None,
    ):
        """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
            An optional string 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 input.

        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)
        """

        # Ensure that all arguments are of the same type.
        args = [min_value, max_value, value, step]

        int_args = all(
            isinstance(a, (numbers.Integral, type(None), NoValue))
            for a in args)

        float_args = all(
            isinstance(a, (float, type(None), NoValue)) for a in 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`")
                if max_value is not None:
                    JSNumber.validate_int_bounds(max_value, "`max_value`")
                if step is not None:
                    JSNumber.validate_int_bounds(step, "`step`")
                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 = 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

        ui_value = register_widget("number_input",
                                   number_input_proto,
                                   user_key=key)

        return_value = ui_value if ui_value is not None else value
        return self.dg._enqueue("number_input", number_input_proto,
                                return_value)
Beispiel #26
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,
    ) -> 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)
Beispiel #27
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
Beispiel #28
0
    def image(
        dg,
        image,
        caption=None,
        width=None,
        use_column_width=False,
        clamp=False,
        channels="RGB",
        output_format="auto",
        **kwargs,
    ):
        """Display an image or list of images.

        Parameters
        ----------
        image : numpy.ndarray, [numpy.ndarray], BytesIO, str, or [str]
            Monochrome image of shape (w,h) or (w,h,1)
            OR a color image of shape (w,h,3)
            OR an RGBA image of shape (w,h,4)
            OR a URL to fetch the image from
            OR an SVG XML string like `<svg xmlns=...</svg>`
            OR a list of one of the above, to display multiple images.
        caption : str or list of str
            Image caption. If displaying multiple images, caption should be a
            list of captions (one for each image).
        width : int or None
            Image width. None means use the image width.
            Should be set for SVG images, as they have no default image width.
        use_column_width : bool
            If True, set the image width to the column width. This takes
            precedence over the `width` parameter.
        clamp : bool
            Clamp image pixel values to a valid range ([0-255] per channel).
            This is only meaningful for byte array images; the parameter is
            ignored for image URLs. If this is not set, and an image has an
            out-of-range value, an error will be thrown.
        channels : 'RGB' or 'BGR'
            If image is an nd.array, this parameter denotes the format used to
            represent color information. Defaults to 'RGB', meaning
            `image[:, :, 0]` is the red channel, `image[:, :, 1]` is green, and
            `image[:, :, 2]` is blue. For images coming from libraries like
            OpenCV you should set this to 'BGR', instead.
        output_format : 'JPEG', 'PNG', or 'auto'
            This parameter specifies the format to use when transferring the
            image data. Photos should use the JPEG format for lossy compression
            while diagrams should use the PNG format for lossless compression.
            Defaults to 'auto' which identifies the compression type based
            on the type and format of the image argument.

        Example
        -------
        >>> from PIL import Image
        >>> image = Image.open('sunrise.jpg')
        >>>
        >>> st.image(image, caption='Sunrise by the mountains',
        ...          use_column_width=True)

        .. output::
           https://share.streamlit.io/0.61.0-yRE1/index.html?id=Sn228UQxBfKoE5C7A7Y2Qk
           height: 630px

        """

        format = kwargs.get("format")
        if format != None:
            # override output compression type if specified
            output_format = format

            if config.get_option("deprecation.showImageFormat"):
                dg.exception(ImageFormatWarning(format))  # type: ignore

        if use_column_width:
            width = -2
        elif width is None:
            width = -1
        elif width <= 0:
            raise StreamlitAPIException("Image width must be positive.")

        image_list_proto = ImageListProto()
        marshall_images(
            dg._get_coordinates(),  # type: ignore
            image,
            caption,
            width,
            image_list_proto,
            clamp,
            channels,
            output_format,
        )
        return dg._enqueue("imgs", image_list_proto)  # type: ignore
Beispiel #29
0
def image_to_url(image,
                 width,
                 clamp,
                 channels,
                 output_format,
                 image_id,
                 allow_emoji=False):
    # PIL Images
    if isinstance(image, ImageFile.ImageFile) or isinstance(
            image, Image.Image):
        format = _format_from_image_type(image, output_format)
        data = _PIL_to_bytes(image, format)

    # BytesIO
    # Note: This doesn't support SVG. We could convert to png (cairosvg.svg2png)
    # or just decode BytesIO to string and handle that way.
    elif type(image) is io.BytesIO:
        data = _BytesIO_to_bytes(image)

    # Numpy Arrays (ie opencv)
    elif type(image) is np.ndarray:
        data = _verify_np_shape(image)
        data = _clip_image(data, clamp)

        if channels == "BGR":
            if len(data.shape) == 3:
                data = data[:, :, [2, 1, 0]]
            else:
                raise StreamlitAPIException(
                    'When using `channels="BGR"`, the input image should '
                    "have exactly 3 color channels")

        data = _np_array_to_bytes(data, output_format=output_format)

    # Strings
    elif isinstance(image, str):
        # If it's a url, then set the protobuf and continue
        try:
            p = urlparse(image)
            if p.scheme:
                return image
        except UnicodeDecodeError:
            pass

        # Unpack local SVG image file to an SVG string
        if image.endswith(".svg"):
            with open(image) as textfile:
                image = textfile.read()
        # If it's an SVG string, then format and return an SVG data url
        if image.startswith("<svg") or image.strip().startswith("<svg"):
            return f"data:image/svg+xml,{image}"

        # Finally, see if it's a file.
        try:
            with open(image, "rb") as f:
                data = f.read()
        except:
            if allow_emoji:
                # This might be an emoji string, so just pass it to the frontend
                return image
            else:
                # Allow OS filesystem errors to raise
                raise

    # Assume input in bytes.
    else:
        data = image

    (data, mimetype) = _normalize_to_bytes(data, width, output_format)
    this_file = media_file_manager.add(data, mimetype, image_id)
    return this_file.url
Beispiel #30
0
    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)