def _button(
        self,
        label: str,
        key: Optional[str],
        help: Optional[str],
        is_form_submitter: bool,
    ) -> bool:
        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.form_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 #2
0
def set_option(key: str, value: Any) -> None:
    """Set config option.

    Currently, only the following config options can be set within the script itself:
        * client.caching
        * client.displayEnabled
        * deprecation.*

    Calling with any other options will raise StreamlitAPIException.

    Run `streamlit config show` in the terminal to see all available options.

    Parameters
    ----------
    key : str
        The config option key of the form "section.optionName". To see all
        available options, run `streamlit config show` on a terminal.

    value
        The new value to assign to this config option.

    """
    try:
        opt = _config._config_options_template[key]
    except KeyError as ke:
        raise StreamlitAPIException(
            "Unrecognized config option: {key}".format(key=key)) from ke
    if opt.scriptable:
        _config.set_option(key, value)
        return

    raise StreamlitAPIException(
        "{key} cannot be set on the fly. Set as command line option, e.g. streamlit run script.py --{key}, or in config.toml instead."
        .format(key=key))
Beispiel #3
0
    def selectbox(self, label, options, index=0, format_func=str, key=None):
        """Display a select widget.

        Parameters
        ----------
        label : str
            A short label explaining to the user what this select widget is for.
        options : list, tuple, numpy.ndarray, pandas.Series, or pandas.DataFrame
            Labels for the select options. This will be cast to str internally
            by default. For pandas.DataFrame, the first column is selected.
        index : int
            The index of the preselected option on first render.
        format_func : function
            Function to modify the display of the labels. It receives the option
            as an argument and its output will be cast to str.
        key : str
            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
        -------
        any
            The selected option

        Example
        -------
        >>> option = st.selectbox(
        ...     'How would you like to be contacted?',
        ...     ('Email', 'Home phone', 'Mobile phone'))
        >>>
        >>> st.write('You selected:', option)

        """
        options = ensure_iterable(options)

        if not isinstance(index, int):
            raise StreamlitAPIException(
                "Selectbox Value has invalid type: %s" % type(index).__name__)

        if len(options) > 0 and not 0 <= index < len(options):
            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 options
        ]

        ui_value = _get_widget_ui_value("selectbox",
                                        selectbox_proto,
                                        user_key=key)
        current_value = ui_value if ui_value is not None else index

        return_value = (options[current_value] if len(options) > 0
                        and options[current_value] is not None else NoValue)
        return self.dg._enqueue("selectbox", selectbox_proto, return_value)
Beispiel #4
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)
Beispiel #5
0
    def color_picker(self, label, value=None, key=None):
        """Display a color picker widget.

        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 #6
0
def validate_menu_items(dict):
    for k, v in dict.items():
        if not valid_menu_item_key(k):
            raise StreamlitAPIException(
                "We only accept the keys: "
                f'"Get help", "Report a bug", and "About" ("{k}" is not a valid key.)'
            )
        if v is not None:
            if not valid_url(v) and k != ABOUT_KEY:
                raise StreamlitAPIException(f'"{v}" is a not a valid URL!')
Beispiel #7
0
def _verify_np_shape(array):
    if len(array.shape) not in (2, 3):
        raise StreamlitAPIException("Numpy shape has to be of length 2 or 3.")
    if len(array.shape) == 3 and array.shape[-1] not in (1, 3, 4):
        raise StreamlitAPIException(
            "Channel can only be 1, 3, or 4 got %d. Shape is %s" %
            (array.shape[-1], str(array.shape)))

    # If there's only one channel, convert is to x, y
    if len(array.shape) == 3 and array.shape[-1] == 1:
        array = array[:, :, 0]

    return array
Beispiel #8
0
    def progress(self, value: FloatOrInt) -> "DeltaGenerator":
        """Display a progress bar.

        Parameters
        ----------
        value : int or float
            0 <= value <= 100 for int

            0.0 <= value <= 1.0 for float

        Example
        -------
        Here is an example of a progress bar increasing over time:

        >>> import time
        >>>
        >>> my_bar = st.progress(0)
        >>>
        >>> for percent_complete in range(100):
        ...     time.sleep(0.1)
        ...     my_bar.progress(percent_complete + 1)

        """
        # TODO: standardize numerical type checking across st.* functions.
        progress_proto = ProgressProto()

        if isinstance(value, int):
            if 0 <= value <= 100:
                progress_proto.value = value
            else:
                raise StreamlitAPIException(
                    "Progress Value has invalid value [0, 100]: %d" % value
                )

        elif isinstance(value, float):
            if 0.0 <= value <= 1.0:
                progress_proto.value = int(value * 100)
            else:
                raise StreamlitAPIException(
                    "Progress Value has invalid value [0.0, 1.0]: %f" % value
                )
        else:
            raise StreamlitAPIException(
                "Progress Value has invalid type: %s" % type(value).__name__
            )

        return cast(
            "DeltaGenerator",
            self.dg._enqueue("progress", progress_proto),
        )
Beispiel #9
0
    def beta_columns(self, weights):
        """Create several columns, side-by-side.

        Parameters
        ----------
        weights : int or list of numbers
            If a single int: lay out that many columns of equal width.

            If a list of numbers: create a column for each number.
            Each column's width is proportional to the number provided.
            For example, `st.beta_columns([3, 1, 2])` would create 3 columns of varying widths.
            The first column would be 3x the width of the second column;
            the last column would be 2x the width of the second.

        Returns
        -------
        A list of containers, each of which can have their own elements.

        Examples
        --------
        >>> col1, col2, col3 = st.beta_columns(3)
        >>> col1.write('Hello?')
        >>> col2.button('Press me!')
        >>> col3.checkbox('Good to go~')

        """

        if isinstance(weights, int):
            if weights <= 0:
                raise StreamlitAPIException(
                    "You have to create at least one column!")
            if weights == 1:
                raise StreamlitAPIException(
                    "Instead of creating only one column, use st.beta_container."
                )
            # If the user provided a single number, expand into equal weights.
            # E.g. 3 => (1, 1, 1)
            weights = (1, ) * weights

        def column_proto(weight):
            col_proto = Block_pb2.Block()
            col_proto.column.weight = weight
            col_proto.allow_empty = True
            return col_proto

        horiz_proto = Block_pb2.Block()
        horiz_proto.horizontal.unused = True
        row = self._block(horiz_proto)
        return [row._block(column_proto(w)) for w in weights]
Beispiel #10
0
    def text_input(self,
                   label,
                   value="",
                   max_chars=None,
                   key=None,
                   type="default"):
        """Display a single-line text input widget.

        Parameters
        ----------
        label : str
            A short label explaining to the user what this input is for.
        value : any
            The text value of this widget when it first renders. This will be
            cast to str internally.
        max_chars : int or None
            Max number of characters allowed in text input.
        key : str
            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.
        type : str
            The type of the text input. This can be either "default" (for
            a regular text input), or "password" (for a text input that
            masks the user's typed value). Defaults to "default".

        Returns
        -------
        str
            The current value of the text input widget.

        Example
        -------
        >>> title = st.text_input('Movie title', 'Life of Brian')
        >>> st.write('The current movie title is', title)

        """
        text_input_proto = TextInputProto()
        text_input_proto.label = label
        text_input_proto.default = str(value)

        if max_chars is not None:
            text_input_proto.max_chars = max_chars

        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)

        ui_value = _get_widget_ui_value("text_input",
                                        text_input_proto,
                                        user_key=key)
        current_value = ui_value if ui_value is not None else value
        return self.dg._enqueue("text_input", text_input_proto,
                                str(current_value))
Beispiel #11
0
    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 #12
0
    def determine_delta_color_and_direction(self, delta_color, delta):
        cd = MetricColorAndDirection(color=None, direction=None)

        if delta is None or delta == "":
            cd.color = MetricProto.MetricColor.GRAY
            cd.direction = MetricProto.MetricDirection.NONE
            return cd

        if self.is_negative(delta):
            if delta_color == "normal":
                cd.color = MetricProto.MetricColor.RED
            elif delta_color == "inverse":
                cd.color = MetricProto.MetricColor.GREEN
            elif delta_color == "off":
                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
            elif delta_color == "off":
                cd.color = MetricProto.MetricColor.GRAY
            cd.direction = MetricProto.MetricDirection.UP

        if cd.color is None or cd.direction is None:
            raise StreamlitAPIException(
                f"'{str(delta_color)}' is not an accepted value. delta_color only accepts: "
                "'normal', 'inverse', or 'off'")
        return cd
Beispiel #13
0
def set_option(key, value):
    """Set config option.

    Currently, only two config options can be set within the script itself:
        * client.caching
        * client.displayEnabled

    Calling with any other options will raise StreamlitAPIException.

    Run `streamlit config show` in the terminal to see all available options.

    Parameters
    ----------
    key : str
        The config option key of the form "section.optionName". To see all
        available options, run `streamlit config show` on a terminal.

    value
        The new value to assign to this config option.

    """
    opt = _config._config_options[key]
    if opt.scriptable:
        _config.set_option(key, value)
        return

    raise StreamlitAPIException(
        "{key} cannot be set on the fly. Set as command line option, e.g. streamlit run script.py --{key}, or in config.toml instead."
        .format(key=key))
Beispiel #14
0
    def beta_expander(self, label=None, expanded=False):
        """Create a container that can be expanded and collapsed.

        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

        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 #15
0
    def form(self, key: str):
        """Create a form that batches elements together with a "Submit" button.

        A form is a container that visually groups other elements and
        widgets together, and contains a Submit button. When the form's
        Submit button is pressed, all widget values inside the form will be
        sent to Streamlit in a batch.

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

        Forms have a few constraints:
        - Every form must contain a `st.form_submit_button`.
        - You cannot add a normal `st.button` to a form.
        - Forms can appear anywhere in your app (sidebar, columns, etc),
          but they cannot be embedded inside other forms.

        Parameters
        ----------
        key : str
            A string that identifies the form. Each form must have its own
            key. (This key is not displayed to the user in the interface.)

        """

        if is_in_form(self.dg):
            raise StreamlitAPIException(
                "Forms cannot be nested in other forms.")

        # A form is uniquely identified by its key.
        form_id = key

        ctx = get_report_ctx()
        if ctx is not None:
            added_form_id = ctx.form_ids_this_run.add(form_id)
            if not added_form_id:
                raise StreamlitAPIException(_build_duplicate_form_message(key))

        block_proto = Block_pb2.Block()
        block_proto.form_id = form_id
        block_dg = self.dg._block(block_proto)

        # Attach the form's button info to the newly-created block's
        # DeltaGenerator.
        block_dg._form_data = FormData(form_id)
        return block_dg
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 isinstance(image, 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

        # 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 = in_memory_file_manager.add(data, mimetype, image_id)
    return this_file.url
Beispiel #17
0
def check_callback_rules(dg: "DeltaGenerator",
                         on_change: Optional[WidgetCallback]) -> None:
    if (streamlit._is_running_with_streamlit and is_in_form(dg)
            and on_change is not None):
        raise StreamlitAPIException(
            "With forms, callbacks can only be defined on the `st.form_submit_button`."
            " Defining callbacks on other widgets inside a form is not allowed."
        )
Beispiel #18
0
    def _block(self, block_proto=Block_pb2.Block()) -> "DeltaGenerator":
        # Operate on the active DeltaGenerator, in case we're in a `with` block.
        dg = self._active_dg

        # Prevent nested columns & expanders by checking all parents.
        block_type = block_proto.WhichOneof("type")
        # Convert the generator to a list, so we can use it multiple times.
        parent_block_types = frozenset(dg._parent_block_types)
        if block_type == "column" and block_type in parent_block_types:
            raise StreamlitAPIException(
                "Columns may not be nested inside other columns."
            )
        if block_type == "expandable" and block_type in parent_block_types:
            raise StreamlitAPIException(
                "Expanders may not be nested inside other expanders."
            )

        if dg._root_container is None or dg._cursor is None:
            return dg

        msg = ForwardMsg_pb2.ForwardMsg()
        msg.metadata.delta_path[:] = dg._cursor.delta_path
        msg.delta.add_block.CopyFrom(block_proto)

        # Normally we'd return a new DeltaGenerator that uses the locked cursor
        # below. But in this case we want to return a DeltaGenerator that uses
        # a brand new cursor for this new block we're creating.
        block_cursor = cursor.RunningCursor(
            root_container=dg._root_container,
            parent_path=dg._cursor.parent_path + (dg._cursor.index,),
        )
        block_dg = DeltaGenerator(
            root_container=dg._root_container,
            cursor=block_cursor,
            parent=dg,
            block_type=block_type,
        )
        # Blocks inherit their parent form ids.
        # NOTE: Container form ids aren't set in proto.
        block_dg._form_data = FormData(current_form_id(dg))

        # Must be called to increment this cursor's index.
        dg._cursor.get_locked_cursor(last_index=None)
        _enqueue_message(msg)

        return block_dg
Beispiel #19
0
    def bokeh_chart(self, figure, use_container_width=False):
        """Display an interactive Bokeh chart.

        Bokeh is a charting library for Python. The arguments to this function
        closely follow the ones for Bokeh's `show` function. You can find
        more about Bokeh at https://bokeh.pydata.org.

        To show Bokeh charts in Streamlit, call `st.bokeh_chart`
        wherever you would call Bokeh's `show`.

        Parameters
        ----------
        figure : bokeh.plotting.figure.Figure
            A Bokeh figure to plot.

        use_container_width : bool
            If True, set the chart width to the column width. This takes
            precedence over Bokeh's native `width` value.

        Example
        -------
        >>> import streamlit as st
        >>> from bokeh.plotting import figure
        >>>
        >>> x = [1, 2, 3, 4, 5]
        >>> y = [6, 7, 2, 4, 5]
        >>>
        >>> p = figure(
        ...     title='simple line example',
        ...     x_axis_label='x',
        ...     y_axis_label='y')
        ...
        >>> p.line(x, y, legend_label='Trend', line_width=2)
        >>>
        >>> st.bokeh_chart(p, use_container_width=True)

        .. output::
           https://share.streamlit.io/streamlit/docs/main/python/api-examples-source/charts.bokeh_chart.py
           height: 700px

        """
        import bokeh

        if bokeh.__version__ != ST_BOKEH_VERSION:
            raise StreamlitAPIException(
                f"Streamlit only supports Bokeh version {ST_BOKEH_VERSION}, "
                f"but you have version {bokeh.__version__} installed. Please "
                f"run `pip install --force-reinstall --no-deps bokeh=="
                f"{ST_BOKEH_VERSION}` to install the correct version.")

        # Generate element ID from delta path
        delta_path = self.dg._get_delta_path_str()
        element_id = hashlib.md5(delta_path.encode()).hexdigest()

        bokeh_chart_proto = BokehChartProto()
        marshall(bokeh_chart_proto, figure, use_container_width, element_id)
        return self.dg._enqueue("bokeh_chart", bokeh_chart_proto)
Beispiel #20
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)
Beispiel #21
0
    def time_input(self, label, value=None, key=None, help=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.
        help : str
            A tooltip that gets displayed next to the input.

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

        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)
    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 #23
0
    def _block(self, block_proto=Block_pb2.Block()):
        # Switch to the active DeltaGenerator, in case we're in a `with` block.
        self = self._active_dg

        # Prevent nested columns & expanders by checking all parents.
        block_type = block_proto.WhichOneof("type")
        # Convert the generator to a list, so we can use it multiple times.
        parent_block_types = [t for t in self._parent_block_types]
        if block_type == "column" and block_type in parent_block_types:
            raise StreamlitAPIException(
                "Columns may not be nested inside other columns.")
        if block_type == "expandable" and block_type in parent_block_types:
            raise StreamlitAPIException(
                "Expanders may not be nested inside other expanders.")

        if self._container is None or self._cursor is None:
            return self

        msg = ForwardMsg_pb2.ForwardMsg()
        msg.metadata.parent_block.container = self._container
        msg.metadata.parent_block.path[:] = self._cursor.path
        msg.metadata.delta_id = self._cursor.index
        msg.delta.add_block.CopyFrom(block_proto)

        # Normally we'd return a new DeltaGenerator that uses the locked cursor
        # below. But in this case we want to return a DeltaGenerator that uses
        # a brand new cursor for this new block we're creating.
        block_cursor = cursor.RunningCursor(path=self._cursor.path +
                                            (self._cursor.index, ))
        block_dg = DeltaGenerator(
            container=self._container,
            cursor=block_cursor,
            parent=self,
            block_type=block_type,
        )

        # Must be called to increment this cursor's index.
        self._cursor.get_locked_cursor(last_index=None)
        _enqueue_message(msg)

        return block_dg
Beispiel #24
0
    def bokeh_chart(self, figure, use_container_width=False):
        """Display an interactive Bokeh chart.

        Bokeh is a charting library for Python. The arguments to this function
        closely follow the ones for Bokeh's `show` function. You can find
        more about Bokeh at https://bokeh.pydata.org.

        Parameters
        ----------
        figure : bokeh.plotting.figure.Figure
            A Bokeh figure to plot.

        use_container_width : bool
            If True, set the chart width to the column width. This takes
            precedence over Bokeh's native `width` value.

        To show Bokeh charts in Streamlit, call `st.bokeh_chart`
        wherever you would call Bokeh's `show`.

        Example
        -------
        >>> import streamlit as st
        >>> from bokeh.plotting import figure
        >>>
        >>> x = [1, 2, 3, 4, 5]
        >>> y = [6, 7, 2, 4, 5]
        >>>
        >>> p = figure(
        ...     title='simple line example',
        ...     x_axis_label='x',
        ...     y_axis_label='y')
        ...
        >>> p.line(x, y, legend_label='Trend', line_width=2)
        >>>
        >>> st.bokeh_chart(p, use_container_width=True)

        .. output::
           https://static.streamlit.io/0.56.0-xTAd/index.html?id=Fdhg51uMbGMLRRxXV6ubzp
           height: 600px

        """
        import bokeh

        if bokeh.__version__ != ST_BOKEH_VERSION:
            raise StreamlitAPIException(
                f"Streamlit only supports Bokeh version {ST_BOKEH_VERSION}, "
                f"but you have version {bokeh.__version__} installed. Please "
                f"run `pip install --force-reinstall --no-deps bokeh=="
                f"{ST_BOKEH_VERSION}` to install the correct version.")

        bokeh_chart_proto = BokehChartProto()
        marshall(bokeh_chart_proto, figure, use_container_width)
        return self.dg._enqueue("bokeh_chart", bokeh_chart_proto)
Beispiel #25
0
    def enqueue(self, msg):
        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._enqueue(msg)
Beispiel #26
0
    def beta_expander(self, label=None, expanded=False):
        """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=LzfUAiT1eM9Xy8EDMRkWyF
            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)
Beispiel #27
0
    def beta_columns(self, weights):
        """Create several columns, side-by-side.

        Parameters
        ----------
        weights : int or list of positive floats
            If a single int: lay out that many columns of equal width.

            If a list of numbers: create a column for each number.
            Each column's width is proportional to the number provided.
            For example, `st.beta_columns([3, 1, 2])` would create 3 columns of varying widths.
            The first column would be 3x the width of the second column;
            the last column would be 2x the width of the second.

        Returns
        -------
        A list of containers, each of which can have their own elements.

        Examples
        --------
        >>> col1, col2, col3 = st.beta_columns(3)
        >>> col1.write('Hello?')
        >>> col2.button('Press me!')
        >>> col3.checkbox('Good to go~')

        """
        weights_exception = StreamlitAPIException(
            "The input argument to st.beta_columns must be either a " +
            "positive integer or a list of numeric weights. " +
            "See [documentation](https://docs.streamlit.io/en/stable/api.html#streamlit.beta_columns) "
            + "for more information.")

        if isinstance(weights, int):
            # If the user provided a single number, expand into equal weights.
            # E.g. (1,) * 3 => (1, 1, 1)
            # NOTE: A negative/zero spec will expand into an empty tuple.
            weights = (1, ) * weights

        if len(weights) == 0 or any(weight <= 0 for weight in weights):
            raise weights_exception

        def column_proto(weight):
            col_proto = Block_pb2.Block()
            col_proto.column.weight = weight
            col_proto.allow_empty = True
            return col_proto

        horiz_proto = Block_pb2.Block()
        horiz_proto.horizontal.unused = True
        row = self._block(horiz_proto)
        return [row._block(column_proto(w)) for w in weights]
Beispiel #28
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 #29
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)
Beispiel #30
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