Skip to main content

philiprehberger-debounce

Tests PyPI version Last updated

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:

⭐ Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT

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)

Source distribution for philiprehberger-debounce 0.6.0
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)

Table of built distributions (wheels) for philiprehberger-debounce 0.6.0
File Interpreter ABI Platform Actions
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page