Skip to content

Commit befebca

Browse files
committed
Add debouncing to superzip
1 parent 0f701b3 commit befebca

File tree

2 files changed

+106
-1
lines changed

2 files changed

+106
-1
lines changed

Diff for: examples/superzip/app.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import ipywidgets
55
import pandas as pd
66
from faicons import icon_svg
7+
from ratelimit import debounce
78
from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui
89
from utils import col_numeric, create_map, density_plot, heatmap_gradient
910

@@ -173,6 +174,7 @@ def _():
173174
with reactive.isolate():
174175
current_bounds.set(bb)
175176

177+
@debounce(0.3)
176178
@reactive.calc
177179
def zips_in_bounds():
178180
bb = req(current_bounds())
@@ -326,4 +328,4 @@ def _on_click(**kwargs):
326328
return m
327329

328330

329-
app = App(app_ui, server)
331+
app = App(app_ui, server, static_assets=app_dir / "www")

Diff for: examples/superzip/ratelimit.py

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# From https://gist.github.com/jcheng5/427de09573816c4ce3a8c6ec1839e7c0
2+
import functools
3+
import time
4+
5+
from shiny import reactive
6+
7+
8+
def debounce(delay_secs):
9+
def wrapper(f):
10+
when = reactive.Value(None)
11+
trigger = reactive.Value(0)
12+
13+
@reactive.Calc
14+
def cached():
15+
"""
16+
Just in case f isn't a reactive calc already, wrap it in one. This ensures
17+
that f() won't execute any more than it needs to.
18+
"""
19+
return f()
20+
21+
@reactive.Effect(priority=102)
22+
def primer():
23+
"""
24+
Whenever cached() is invalidated, set a new deadline for when to let
25+
downstream know--unless cached() invalidates again
26+
"""
27+
try:
28+
cached()
29+
except Exception:
30+
...
31+
finally:
32+
when.set(time.time() + delay_secs)
33+
34+
@reactive.Effect(priority=101)
35+
def timer():
36+
"""
37+
Watches changes to the deadline and triggers downstream if it's expired; if
38+
not, use invalidate_later to wait the necessary time and then try again.
39+
"""
40+
deadline = when()
41+
if deadline is None:
42+
return
43+
time_left = deadline - time.time()
44+
if time_left <= 0:
45+
# The timer expired
46+
with reactive.isolate():
47+
when.set(None)
48+
trigger.set(trigger() + 1)
49+
else:
50+
reactive.invalidate_later(time_left)
51+
52+
@reactive.Calc
53+
@reactive.event(trigger, ignore_none=False)
54+
@functools.wraps(f)
55+
def debounced():
56+
return cached()
57+
58+
return debounced
59+
60+
return wrapper
61+
62+
63+
def throttle(delay_secs):
64+
def wrapper(f):
65+
last_signaled = reactive.Value(None)
66+
last_triggered = reactive.Value(None)
67+
trigger = reactive.Value(0)
68+
69+
@reactive.Calc
70+
def cached():
71+
return f()
72+
73+
@reactive.Effect(priority=102)
74+
def primer():
75+
try:
76+
cached()
77+
except Exception:
78+
...
79+
finally:
80+
last_signaled.set(time.time())
81+
82+
@reactive.Effect(priority=101)
83+
def timer():
84+
if last_triggered() is not None and last_signaled() < last_triggered():
85+
return
86+
87+
now = time.time()
88+
if last_triggered() is None or (now - last_triggered()) >= delay_secs:
89+
last_triggered.set(now)
90+
with reactive.isolate():
91+
trigger.set(trigger() + 1)
92+
else:
93+
reactive.invalidate_later(delay_secs - (now - last_triggered()))
94+
95+
@reactive.Calc
96+
@reactive.event(trigger, ignore_none=False)
97+
@functools.wraps(f)
98+
def throttled():
99+
return cached()
100+
101+
return throttled
102+
103+
return wrapper

0 commit comments

Comments
 (0)