Skip to content

Commit

Permalink
add debouncing; add min execution time; remove 1.37 disallowed
Browse files Browse the repository at this point in the history
  • Loading branch information
m-wrzr committed Aug 23, 2024
1 parent a32a06a commit f50955a
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 8 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ style_absolute: bool = False

Will position the searchbox as an absolute element. *NOTE:* this will affect all searchbox instances and should either be set for all boxes or none. See [#46](https://github.com/m-wrzr/streamlit-searchbox/issues/46) for inital workaround by [@JoshElgar](https://github.com/JoshElgar).

```python
debounce: int = 0
```

Delay executing the callback from the react component by `x` milliseconds to avoid too many / redudant requests, i.e. during fast typing.

```python
min_execution_time: int = 0
```

Delay execution after the search function finished to reach a minimum amount of `x` milliseconds. This can be used to avoid fast consecutive reruns, which can cause resets of the component in some streamlit versions `>=1.35`.

```python
key: str = "searchbox"
```
Expand Down
16 changes: 16 additions & 0 deletions example.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ def search_kwargs(searchterm: str, **kwargs) -> List[str]:
clear_on_submit=False,
key=search.__name__,
),
dict(
search_function=search,
default=None,
label=f"{search.__name__}_debounce_250ms",
clear_on_submit=False,
debounce=250,
key=f"{search.__name__}_debounce_250ms",
),
dict(
search_function=search,
default=None,
label=f"{search.__name__}_min_execution_time_500ms",
clear_on_submit=False,
min_execution_time=500,
key=f"{search.__name__}_min_execution_time_500ms",
),
dict(
search_function=search_rnd_delay,
default=None,
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setuptools.setup(
name="streamlit-searchbox",
version="0.1.14",
version="0.1.15",
author="m-wrzr",
description="Autocomplete Searchbox",
long_description="Streamlit searchbox that dynamically updates "
Expand All @@ -14,9 +14,9 @@
classifiers=[],
python_requires=">=3.8, !=3.9.7",
install_requires=[
# version 1.37 reruns lead to constant iFrame resets
# version 1.37 reruns can lead to constant iFrame
# version 1.35/1.36 also have reset issues but less frequent
"streamlit >= 1.0, != 1.37.0",
"streamlit >= 1.0",
],
extras_require={
"tests": [
Expand Down
36 changes: 32 additions & 4 deletions streamlit_searchbox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from __future__ import annotations

import datetime
import functools
import logging
import os
Expand All @@ -14,7 +15,7 @@
import streamlit.components.v1 as components

try:
from streamlit import rerun as rerun # type: ignore
from streamlit import rerun # type: ignore
except ImportError:
# conditional import for streamlit version <1.27
from streamlit import experimental_rerun as rerun # type: ignore
Expand Down Expand Up @@ -78,13 +79,17 @@ def _process_search(
key: str,
searchterm: str,
rerun_on_update: bool,
min_execution_time: int = 0,
**kwargs,
) -> None:
# nothing changed, avoid new search
if searchterm == st.session_state[key]["search"]:
return st.session_state[key]["result"]
return

st.session_state[key]["search"] = searchterm

ts_start = datetime.datetime.now()

search_results = search_function(searchterm, **kwargs)

if search_results is None:
Expand All @@ -94,6 +99,13 @@ def _process_search(
st.session_state[key]["options_py"] = _list_to_options_py(search_results)

if rerun_on_update:
ts_stop = datetime.datetime.now()
execution_time_ms = (ts_stop - ts_start).total_seconds() * 1000

# wait until minimal execution time is reached
if execution_time_ms < min_execution_time:
time.sleep((min_execution_time - execution_time_ms) / 1000)

rerun()


Expand Down Expand Up @@ -176,6 +188,8 @@ def st_searchbox(
edit_after_submit: Literal["disabled", "current", "option", "concat"] = "disabled",
style_absolute: bool = False,
style_overrides: StyleOverrides | None = None,
debounce: int = 0,
min_execution_time: int = 0,
key: str = "searchbox",
**kwargs,
) -> Any:
Expand Down Expand Up @@ -207,6 +221,13 @@ def st_searchbox(
searchboxes and should be passed to every element. Defaults to False.
style_overrides (StyleOverrides, optional):
CSS styling passed directly to the react components. Defaults to None.
debounce (int, optional):
Time in milliseconds to wait before sending the input to the search function
to avoid too many requests, i.e. during fast keystrokes. Defaults to 0.
min_execution_time (int, optional):
Minimal execution time for the search function in milliseconds. This is used
to avoid fast consecutive reruns, where fast reruns can lead to resets
within the component in some streamlit versions. Defaults to 0.
key (str, optional):
Streamlit session key. Defaults to "searchbox".
Expand All @@ -225,6 +246,7 @@ def st_searchbox(
label=label,
edit_after_submit=edit_after_submit,
style_overrides=style_overrides,
debounce=debounce,
# react return state within streamlit session_state
key=st.session_state[key]["key_react"],
)
Expand All @@ -251,8 +273,14 @@ def st_searchbox(
if default_use_searchterm:
st.session_state[key]["result"] = value

# triggers rerun, no ops afterwards executed
_process_search(search_function, key, value, rerun_on_update, **kwargs)
_process_search(
search_function,
key,
value,
rerun_on_update,
min_execution_time=min_execution_time,
**kwargs,
)

if interaction == "submit":
st.session_state[key]["result"] = (
Expand Down
3 changes: 2 additions & 1 deletion streamlit_searchbox/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-select": "^5.8.0",
"streamlit-component-lib": "^2.0.0"
"streamlit-component-lib": "^2.0.0",
"lodash": "^4.17.21"
},
"devDependencies": {
"@types/lodash": "^4.14.150",
Expand Down
13 changes: 13 additions & 0 deletions streamlit_searchbox/frontend/src/Searchbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import React, { ReactNode } from "react";

import SearchboxStyle from "./styling";
import Select, { InputActionMeta, components } from "react-select";
import { debounce } from "lodash";

type Option = {
value: string;
Expand Down Expand Up @@ -45,6 +46,18 @@ class Searchbox extends StreamlitComponentBase<State> {
);
private ref: any = React.createRef();

constructor(props: any) {
super(props);

// bind the search function and debounce to avoid too many requests
if (props.args.debounce && props.args.debounce > 0) {
this.callbackSearch = debounce(
this.callbackSearch.bind(this),
props.args.debounce,
);
}
}

/**
* new keystroke on searchbox
* @param input
Expand Down

0 comments on commit f50955a

Please sign in to comment.