Skip to main content

Thread-safe rate limiter and retry decorator for Python - token bucket pacing, resilient API call management, zero dependencies.

Project description

call-limiter ๐Ÿš€

PyPI - Version Documentation Build Status Python Versions License

A high-precision concurrency control library for distributed systems resilience.

Call-Limiter Design

โœจ Key Features

  • Low-Jitter Timing: Uses time.perf_counter() and resolution-aware sleeping to prevent the "creeping delays" common in standard rate limiters.
  • Dynamic Jitter Compensation: Automatically adjusts for OS-level scheduling delays to ensure time.sleep intervals remain precise under heavy system load.
  • Thread-Safe: Designed for multithreaded environments where multiple workers hit the same limited resource.
  • Thread-Synchronized State: Shared locks ensure that 10 threads hitting the same limiter behave as a single unit.
  • Synchronized Pacing: In hybrid mode, retries are queued through the global limiter, preventing a 'thundering herd' and ensuring you never exceed your quota during recovery.

๐Ÿ“ฆ Core Components

  • CallLimiter: A high-precision throttler that paces function calls to stay within specific rate limits.
โฑ๏ธ View Throttling Strategy Diagram
Burst mode for 5 calls per 10 seconds:  
Time (s)  0              5              10                            20
          |-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----  
Call-1    [โ–ˆโ–ˆโ–ˆโ–ˆ]                        |                             |
Call-2    [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ]                       |                             |
Call-3    [โ–ˆโ–ˆ]                          |                             |
Call-4    [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ]                       |                             |
Call-5    [โ–ˆ]                           |                             |
Call-6                                  |[โ–ˆโ–ˆโ–ˆโ–ˆ]                       |  
Call-7                                  |[โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ]                      |       
Call-8                                  |[โ–ˆโ–ˆ]                         |        
Call-9                                  |[โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ]                      |     
Call-10                                 |[โ–ˆ]                          |     

Drip mode for 5 calls per 10 seconds:
Time (s)  0              5              10                            20
          |-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|
Call-1    [โ–ˆโ–ˆโ–ˆโ–ˆ]                        |                             |
Call-2          [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ]                 |                             |
Call-3                [โ–ˆโ–ˆ]              |                             |
Call-4                      [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ]     |                             |
Call-5                            [โ–ˆ]   |                             |
Call-6                                  |[โ–ˆโ–ˆโ–ˆโ–ˆ]                       | 
Call-7                                  |      [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ]                |
Call-8                                  |            [โ–ˆโ–ˆ]             |
Call-9                                  |                 [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ]     |
Call-10                                 |                       [โ–ˆ]   |
  • CallRetry: A resilience decorator that re-runs failed functions with a configurable delay and exception handling.
โฑ๏ธ View Retry Strategy Diagram
sequenceDiagram
    autonumber
    participant App as "Client Code"
    participant R as "CallRetry Decorator"
    participant API as "Downstream Service"

    App->>R: Invoke Function
    
    loop "Up to max_retry times"
        R->>API: Attempt Execution
        alt "Success"
            API-->>R: Return Data
            R-->>App: Return Result
        else "Error (Retryable)"
            Note over R, API: "Trigger on_retry hook"
            R->>R: "Wait (retry_interval)"
        end
    end

    alt "All Attempts Exhausted"
        R->>R: "Execute fallback_handler"
        R-->>App: "Return Fallback Result"
    else "No Fallback"
        R-->>App: "Raise Final Exception"
    end
  • ResilientLimiter: A hybrid solution that combines pacing with Coordinated Recovery, ensuring retries never exceed your defined rate limit across threads.
โฑ๏ธ View Resilient Strategy Diagram
sequenceDiagram
    autonumber
    participant App as "Client Code"
    participant RL as "ResilientLimiter"
    participant L as "Shared CallLimiter"
    participant API as "Downstream Service"

    App->>RL: Invoke Function
    
    loop "Retry Loop (Max 3)"
        Note over RL, L: Check Rate Limit Contract
        RL->>L: Request Execution Slot
        L->>L: Calculate Window (perf_counter)
        L-->>RL: Slot Granted (after Paced Wait)
        
        RL->>API: Attempt Execution
        
        alt "Success"
            API-->>RL: 200 OK
            RL-->>App: Return Result
        else "Error"
            Note over RL: Trigger on_retry
            RL->>RL: Backoff Delay
        end
    end
    
    alt "Final Failure"
        RL->>RL: Execute Fallback
        RL-->>App: Fallback Result
    end

๐Ÿ›  Installation

pip install call-limiter

Component 1: CallLimiter

Scenario: I want to "rate limit" (throttle) my function so it limits my calls to 5 calls per second. I also want to have an option to select if I want 5 calls to fire instantly or spread across evenly in the 1 second period.

Usage-1: 5 calls per 1 second with burst (instantly fire all 5 calls) Best for: Maximizing throughput when the target API allows short spikes.

My function to throttle: my_function

from call_limiter import CallLimiter

limiter = CallLimiter(calls=5, period=1, allow_burst=True)
throttled_func = limiter(my_function)

Usage-2: 5 calls per 1 second paced (evenly spread calls) Best for: Avoiding "spiky" traffic patterns that trigger anti-bot protections.

from call_limiter import CallLimiter

# This forces a call exactly every 0.2 seconds (1s / 5 calls)
limiter = CallLimiter(calls=5, period=1, allow_burst=False)
throttled_func = limiter(my_function)

Component 2: CallRetry

Scenario: I want a retry logic to use with my function calls. If my_function raises ValueError exception, it should retry up to 5 times with 1-second delay between attempts. I want to log every retry with retry_logger function. if it still fails, it should use fail_handler function. (if not provided, raise error)

from call_limiter import CallRetry

# This configuration perfectly mirrors your scenario:
retry = CallRetry(
    retry_count=5,
    retry_interval=1.0,
    retry_exceptions=(ValueError,), # Trigger
    on_retry=retry_logger,           # Observability
    fallback=fail_handler            # Outcome (Plan B)
)

# If fail_handler is a function, this returns its result on ultimate failure.
# If you didn't pass fail_handler, it would raise the ValueError.
resilient_func = retry(my_function)

Component 3: ResilientLimiter

Scenario: I want a rate limiter that can also handle failed calls. my_function should be called
Flow Logic:

  • 5 calls/per second with burst (or drip),
  • max_retry = 3 (if it fails)
  • on_retry=retry_handler, notify me by calling optional retry_handler, if not provided ignore!
  • fallback=falback_handler if it still fails notify me, if not provided raise error! Note: each retry will comply "5 calls/per second with burst (or drip)" tempo to respect rate limiter
    Note: on_retry receives (exception, attempt_number), while fallback is a simple callable.
from call_limiter import ResilientLimiter


limiter = ResilientLimiter(
    calls=5,
    period=1.0,
    allow_burst=True,
    retry_count=3,
    on_retry=retry_handler,
    fallback=fail_handler
)

@limiter
def my_function():
    # This will respect the 5/sec pace, even during retries.
    pass

๐Ÿ“‹ Links


Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

call_limiter-1.0.2.tar.gz (755.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

call_limiter-1.0.2-py3-none-any.whl (9.5 kB view details)

Uploaded Python 3

File details

Details for the file call_limiter-1.0.2.tar.gz.

File metadata

  • Download URL: call_limiter-1.0.2.tar.gz
  • Upload date:
  • Size: 755.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for call_limiter-1.0.2.tar.gz
Algorithm Hash digest
SHA256 63f6bbb36ef8a042e96611dd0eb83f5c4b79c242b517d04b31b0a5e3b46068fa
MD5 ff27cbefd69b4c7549d6dcc44bb7ef72
BLAKE2b-256 fed22bc838fd6cec76e9a00955f7321a4f8b4e45b0bd9e479bc6c944cf642d8a

See more details on using hashes here.

Provenance

The following attestation bundles were made for call_limiter-1.0.2.tar.gz:

Publisher: publish.yml on eyukselen/call-limiter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file call_limiter-1.0.2-py3-none-any.whl.

File metadata

  • Download URL: call_limiter-1.0.2-py3-none-any.whl
  • Upload date:
  • Size: 9.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for call_limiter-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 42d0184a31ecf0c545db76564e41907ad31e13508f4938d39df6fc7ede70e52c
MD5 871180c0a8c8de18af32e2dd97b2d26f
BLAKE2b-256 0b5b4704a892b9e0e767e183627276cb49d7442ff8c83d4224b68bcfa76e1771

See more details on using hashes here.

Provenance

The following attestation bundles were made for call_limiter-1.0.2-py3-none-any.whl:

Publisher: publish.yml on eyukselen/call-limiter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page