Skip to content

Commit

Permalink
Merge branch 'v0.1.15-debounce' into v0.1.15-debounce-fragments
Browse files Browse the repository at this point in the history
# Conflicts:
#	streamlit_searchbox/__init__.py
  • Loading branch information
m-wrzr committed Aug 23, 2024
2 parents 22dfe79 + f50955a commit 582f505
Show file tree
Hide file tree
Showing 6 changed files with 76 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
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
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",
],
Expand Down
38 changes: 32 additions & 6 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 @@ -79,13 +80,17 @@ def _process_search(
searchterm: str,
rerun_on_update: bool,
rerun_scope: Literal["app", "fragment"] = "app",
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 @@ -95,10 +100,16 @@ def _process_search(
st.session_state[key]["options_py"] = _list_to_options_py(search_results)

if rerun_on_update:
# Only pass scope if the version is >= 1.37
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)

# only pass scope if the version is >= 1.37
if st.__version__ >= "1.37":
rerun(scope=rerun_scope) # Pass scope if present
rerun(scope=rerun_scope)
else:
rerun()

Expand Down Expand Up @@ -182,6 +193,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",
rerun_scope: Literal["app", "fragment"] = "app",
**kwargs,
Expand Down Expand Up @@ -217,7 +230,13 @@ def st_searchbox(
rerun_scope ("app", "fragment", optional):
The scope in which to rerun the Streamlit app. Only applicable if Streamlit
version >= 1.37. Defaults to "app".
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 @@ -236,6 +255,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 Down Expand Up @@ -264,7 +284,13 @@ def st_searchbox(

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

if interaction == "submit":
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 582f505

Please sign in to comment.