Per-Client Limiting

Step 5: Per-Client Limiting

Real limiters don't throttle everyone together. They throttle each client, API key, or IP independently. Wrap any limiter strategy in a keyed limiter that lazily creates one limiter per key.

Implement KeyedRateLimiter:

  • __init__(factory): factory is a zero-arg callable that returns a fresh limiter (e.g. lambda: TokenBucket(5, 1, clock))
  • allow(key): get or create the limiter for key, then delegate to its allow()

Each key gets its own independent budget. FixedWindowLimiter is provided so your tests can build a factory.

Hints
Starter code
import time


class FixedWindowLimiter:
    def __init__(self, limit, window, clock=time.time):
        self.limit = limit
        self.window = window
        self.clock = clock
        self._window_index = None
        self._count = 0

    def allow(self):
        index = int(self.clock() // self.window)
        if index != self._window_index:
            self._window_index = index
            self._count = 0
        if self._count < self.limit:
            self._count += 1
            return True
        return False


class KeyedRateLimiter:
    def __init__(self, factory):
        self.factory = factory      # zero-arg callable -> a new limiter
        self._limiters = {}

    def allow(self, key):
        """Get or create the limiter for the key, then delegate to its allow()."""
        # TODO: find this key's limiter in self._limiters, minting one from
        #   self.factory() the first time a key shows up, then let that limiter
        #   answer allow() and pass its verdict straight through. The factory
        #   should run once per distinct key and never on a repeat call.
        pass
Write it on a laptop

The editor and the test runner need a wide screen. Open this page on a desktop browser and your progress will be waiting.