philiprehberger-debounce
Debounce and throttle decorators for Python functions.
Installation
pip install philiprehberger-debounce
Usage
from philiprehberger_debounce import debounce, throttle
Debounce
Delay execution until a quiet period has passed. Each new call resets the timer.
@debounce(0.5)
def on_resize(width, height):
print(f"Resized to {width}x{height}")
on_resize(800, 600)
on_resize(1024, 768) # cancels the previous call
# Only the last call executes after 0.5s
Bounded debounce
Use max_wait to guarantee the function fires at most max_wait seconds after the first pending call, even if calls keep arriving and continuously reset the debounce timer. Mirrors lodash debounce({ maxWait }) semantics.
@debounce(0.5, max_wait=2.0)
def autosave(content):
print(f"Saving: {content[:20]}...")
# Even with continuous edits, autosave fires at least every 2s.
for chunk in stream_keystrokes():
autosave(chunk)
max_wait must be positive and >= seconds; otherwise ValueError is raised.
Cancel and flush
The wrapper exposes .cancel() (drop any pending trailing call) and .flush() (fire it immediately). Mirrors the lodash debounce control API.
@debounce(0.5)
def save(content):
print(f"saving {content}")
save("draft 1")
save.cancel() # drop the pending call
save("draft 2")
save.flush() # fire immediately, don't wait for the timer
Pending check
.is_pending() returns whether a trailing call is queued — useful for showing "Saving…" indicators or guarding code paths that depend on whether the debounced action will still fire.
@debounce(0.5)
def save(content):
...
save("draft 1")
if save.is_pending():
show("Saving…")
Throttle
Limit a function to a fixed number of calls within a time window. Excess calls are silently dropped.
@throttle(calls=3, per=1.0)
def send_request(data):
print(f"Sending {data}")
for i in range(10):
send_request(i) # only the first 3 calls within 1s execute
Throttle helpers
The throttle wrapper exposes .remaining() (calls still allowed in the current window) and .reset() (clear the window so the full quota is available again). Both are thread-safe.
@throttle(calls=5, per=60.0)
def call_api(payload):
...
call_api({"id": 1})
print(call_api.remaining()) # 4
call_api.reset() # e.g. after the upstream quota is refreshed
print(call_api.remaining()) # 5
Async functions
Both decorators accept async def functions.
A debounced coroutine function is wrapped in a regular (non-async) function that returns None — do not await it. Call it from inside a running event loop; the trailing call is scheduled with loop.call_later and runs as an asyncio.Task on that loop. leading, max_wait, .cancel(), and .is_pending() work as they do for sync functions, and .flush() returns the started asyncio.Task (or None if nothing was pending) so you can await it.
import asyncio
@debounce(0.3)
async def search(query):
return await fetch_results(query)
async def main():
search("p")
search("py")
search("pyt") # only this call runs, 0.3s later
task = search.flush() # or start it right away
if task is not None:
results = await task
asyncio.run(main())
A throttled coroutine function stays a coroutine function: awaiting an allowed call returns the result, and awaiting a dropped call returns None without running the function.
@throttle(calls=2, per=1.0)
async def ping(host):
return await probe(host)
async def main():
print(await ping("a")) # result
print(await ping("b")) # result
print(await ping("c")) # None — dropped
asyncio.run(main())
API
| Function / Class | Description |
|---|---|
debounce(seconds, *, leading=False, max_wait=None) |
Decorator that delays a call until seconds of quiet have passed; each new call restarts the timer. leading=True fires on the leading edge and suppresses calls until seconds of silence. max_wait (positive, >= seconds) bounds how long a call may be deferred from the first pending call. Works on sync and async def functions; the wrapper returns None |
wrapper.cancel() |
Discard any pending trailing call and reset leading-edge state |
wrapper.flush() |
Fire the pending trailing call immediately (no-op if none); for coroutine functions returns the started asyncio.Task or None |
wrapper.is_pending() |
Return True if a trailing call is queued and not yet fired |
throttle(calls, per) |
Decorator that allows at most calls invocations per sliding window of per seconds; extra calls are dropped and return None. Works on sync and async def functions |
wrapper.remaining() |
Return how many calls are still allowed in the current throttle window |
wrapper.reset() |
Clear the throttle window so the full quota is available again |
Development
pip install -e .
python -m pytest tests/ -v
Support
If you find this project useful:
License
Metadata
Release files for philiprehberger-debounce 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| philiprehberger_debounce-0.6.0.tar.gz | 185.5 kB | Without provenance | Details for philiprehberger_debounce-0.6.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| philiprehberger_debounce-0.6.0-py3-none-any.whl | Python 3 | none | any | Details for philiprehberger_debounce-0.6.0-py3-none-any.whl |
Total release size: 193.5 kB
Release files / philiprehberger_debounce-0.6.0.tar.gz
| Download URL | philiprehberger_debounce-0.6.0.tar.gz |
|---|---|
| Size | 185.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
35525cebbd19ad06334272d26583f9cb6ebb05ade637db0a14a49f4ba6272136
|
|
BLAKE2b-256 checksum How to use checksums |
d386d9fa6cb3aca347c6f1670c2d340e72eb235fa21b936ffefa03295812e9a1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.15
|
Release files / philiprehberger_debounce-0.6.0-py3-none-any.whl
| Download URL | philiprehberger_debounce-0.6.0-py3-none-any.whl |
|---|---|
| Size | 8.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4f8cb578c378198ce4a936f07007491f62c069bdcb5b91eb5c5ca759d887987d
|
|
BLAKE2b-256 checksum How to use checksums |
d62ed7e86fce780e64380a0302f7575b2f58c9075d92299577d82115323f92c4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.15
|
Security for philiprehberger-debounce 0.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.