Typorithm · medium

Test a usage quota with an owned clock and cache

pythondjangodrfindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Test a usage quota with an owned clock and cache

0%

Concept and contract

Verify allow, deny and recovery without sleeps or promises of a security perimeter.

A request quota can limit ordinary service usage by a caller or scope. DRF throttles provide an application policy, but their cache-based non-atomic checks are not a complete defense against brute force or denial of service. Keep the claim narrower than the feature name.

The fixture applies a two-per-minute quota to an owned authenticated principal. It uses only the lab LocMemCache and a synthetic clock, allowing allow/deny/recovery behavior to be tested immediately without waiting a minute or sending requests to a live server.

Mechanism and worked example

SimpleRateThrottle uses a stable cache key and the current timer to retain recent request history.

SimpleRateThrottle uses a stable cache key and the current timer to retain recent request history. The protected view runs only after the throttle allows the request. A denied response exposes status 429 and a Retry-After duration that the helper records.

Each inspection resets the owned cache and advances the local clock through supplied timestamps. Two requests at time zero are accepted and the third is denied. A later timestamp outside the window can be accepted again. The test compares actual responses, not a manually computed quota result.

Complete worked example

python

from drf_lab import setup
setup()

from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.test import APIRequestFactory, force_authenticate
from rest_framework.throttling import SimpleRateThrottle
from rest_framework.views import APIView
from drf_lab import reset_fixture

def inspect_example(times):
    alice, _, _ = reset_fixture()
    clock = {"now": 0}
    class Quota(SimpleRateThrottle):
        rate = "2/min"
        timer = staticmethod(lambda: clock["now"])
        def get_cache_key(self, request, view):
            return "owned-quota:" + str(request.user.pk)
    class Read(APIView):
        permission_classes = [IsAuthenticated]
        throttle_classes = [Quota]
        def get(self, request):
            return Response({"ok": True})
    statuses, waits = [], []
    for now in times:
        clock["now"] = now
        request = APIRequestFactory().get("/quota/")
        force_authenticate(request, user=alice)
        response = Read.as_view()(request)
        statuses.append(response.status_code)
        waits.append(int(response["Retry-After"]) if "Retry-After" in response else None)
    return {"statuses": statuses, "waits": waits}
Reasoning
SimpleRateThrottle uses a stable cache key and the current timer to retain recent request history. The protected view runs only after the throttle allows the request. A denied response exposes status 429 and a Retry-After duration that the helper records. Each inspection resets the owned cache and advances the local clock through supplied timestamps. Two requests at time zero are accepted and the third is denied. A later timestamp outside the window can be accepted again. The test compares actual responses, not a manually computed quota result.

Focused decision: context is in the complete example

python

class Quota(SimpleRateThrottle):
    rate = "2/min"
    timer = staticmethod(lambda: clock["now"])
    def get_cache_key(self, request, view):
        return "owned-quota:" + str(request.user.pk)
Focused decision
Reconstruct this focused decision after the warm-up. The complete imports, model or application, and test client remain in the worked example. Typing records practice; only the downloadable computer lab executes Python.

Counterexamples and common mistakes

The counterexample returns no cache key, disabling this throttle policy.

The counterexample returns no cache key, disabling this throttle policy. Every request then reaches the endpoint, so the third immediate call exposes the mistake. A UI label saying rate limited would not establish that the protected operation is actually gated.

A changing random key would also defeat a per-principal quota. Conversely, trusting arbitrary forwarded IP headers can let callers evade or spend someone else allowance. Choose stable authenticated identity where appropriate and enforce hostile-traffic limits at suitable infrastructure boundaries too.

Controlled mistake: observe the actual wrong behavior

python

from drf_lab import setup
setup()

from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.test import APIRequestFactory, force_authenticate
from rest_framework.throttling import SimpleRateThrottle
from rest_framework.views import APIView
from drf_lab import reset_fixture

def inspect_example(times):
    alice, _, _ = reset_fixture()
    clock = {"now": 0}
    class Quota(SimpleRateThrottle):
        rate = "2/min"
        timer = staticmethod(lambda: clock["now"])
        def get_cache_key(self, request, view):
            return None
    class Read(APIView):
        permission_classes = [IsAuthenticated]
        throttle_classes = [Quota]
        def get(self, request):
            return Response({"ok": True})
    statuses, waits = [], []
    for now in times:
        clock["now"] = now
        request = APIRequestFactory().get("/quota/")
        force_authenticate(request, user=alice)
        response = Read.as_view()(request)
        statuses.append(response.status_code)
        waits.append(int(response["Retry-After"]) if "Retry-After" in response else None)
    return {"statuses": statuses, "waits": waits}
Reasoning
The counterexample returns no cache key, disabling this throttle policy. Every request then reaches the endpoint, so the third immediate call exposes the mistake. A UI label saying rate limited would not establish that the protected operation is actually gated. A changing random key would also defeat a per-principal quota. Conversely, trusting arbitrary forwarded IP headers can let callers evade or spend someone else allowance. Choose stable authenticated identity where appropriate and enforce hostile-traffic limits at suitable infrastructure boundaries too.

Independent practice

Reduce the quota from two per minute to one per minute.

Reduce the quota from two per minute to one per minute. Preserve the stable principal key, synthetic timer and recovery behavior. A second request inside the window should now be denied with the remaining wait rather than silently accepted.

Check simultaneous requests, a thirty-second retry and a request after sixty-one seconds. Do not add real sleeps or contact a public endpoint for this exercise. The goal is a small deterministic policy change with an observable Retry-After value.

Step 1: Use a one-per-minute quota while preserving denial, Retry-After and deterministic recovery. Step 2: Retain the working imports and application structure. Change the stated policy, run the supplied practice cases, then explain the newly accepted or rejected boundary.

Practice starter: solve before reading the solution

python

from drf_lab import setup
setup()

from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.test import APIRequestFactory, force_authenticate
from rest_framework.throttling import SimpleRateThrottle
from rest_framework.views import APIView
from drf_lab import reset_fixture

def inspect_example(times):
    alice, _, _ = reset_fixture()
    clock = {"now": 0}
    class Quota(SimpleRateThrottle):
        rate = "2/min"
        timer = staticmethod(lambda: clock["now"])
        def get_cache_key(self, request, view):
            return "owned-quota:" + str(request.user.pk)
    class Read(APIView):
        permission_classes = [IsAuthenticated]
        throttle_classes = [Quota]
        def get(self, request):
            return Response({"ok": True})
    statuses, waits = [], []
    for now in times:
        clock["now"] = now
        request = APIRequestFactory().get("/quota/")
        force_authenticate(request, user=alice)
        response = Read.as_view()(request)
        statuses.append(response.status_code)
        waits.append(int(response["Retry-After"]) if "Retry-After" in response else None)
    return {"statuses": statuses, "waits": waits}

# Independent task: Use a one-per-minute quota while preserving denial, Retry-After and deterministic recovery.
Reasoning
Use a one-per-minute quota while preserving denial, Retry-After and deterministic recovery.
  • Hint 1: Find the one field or branch that owns this policy.
  • Hint 2: Compare the first changed acceptance case with the retained baseline.

Solution, tests and trade-offs

The solution changes the rate string.

The solution changes the rate string. A denied request does not run the endpoint, and recovery follows the same window policy. The fixture retains the actual DRF throttle implementation and owned cache rather than replacing it with expected constants.

Concurrent workers and cache backends can change practical behavior, especially because built-in checks are not atomic. A quota test should not be advertised as exactly-once accounting or a universal attack defense. For hard shared budgets, use an explicitly designed atomic admission mechanism and measure failure behavior.

Practice solution and acceptance checks

python

from drf_lab import setup
setup()

from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.test import APIRequestFactory, force_authenticate
from rest_framework.throttling import SimpleRateThrottle
from rest_framework.views import APIView
from drf_lab import reset_fixture

def inspect_example(times):
    alice, _, _ = reset_fixture()
    clock = {"now": 0}
    class Quota(SimpleRateThrottle):
        rate = "1/min"
        timer = staticmethod(lambda: clock["now"])
        def get_cache_key(self, request, view):
            return "owned-quota:" + str(request.user.pk)
    class Read(APIView):
        permission_classes = [IsAuthenticated]
        throttle_classes = [Quota]
        def get(self, request):
            return Response({"ok": True})
    statuses, waits = [], []
    for now in times:
        clock["now"] = now
        request = APIRequestFactory().get("/quota/")
        force_authenticate(request, user=alice)
        response = Read.as_view()(request)
        statuses.append(response.status_code)
        waits.append(int(response["Retry-After"]) if "Retry-After" in response else None)
    return {"statuses": statuses, "waits": waits}
Reasoning
The solution changes the rate string. A denied request does not run the endpoint, and recovery follows the same window policy. The fixture retains the actual DRF throttle implementation and owned cache rather than replacing it with expected constants. Concurrent workers and cache backends can change practical behavior, especially because built-in checks are not atomic. A quota test should not be advertised as exactly-once accounting or a universal attack defense. For hard shared budgets, use an explicitly designed atomic admission mechanism and measure failure behavior.

Interview reasoning and next step

Explain why a stable cache key and a deterministic clock make this fixture useful.

Explain why a stable cache key and a deterministic clock make this fixture useful. Then distinguish a business quota, a distributed atomic budget and an edge security limit. Name what this test proves and what it deliberately does not prove.

Next, inspect content negotiation and error boundaries. Rejecting an unsupported representation is different from enforcing a quota, even though both can prevent normal endpoint work.

Optional stretch — after the core task works: Add one independently reasoned boundary case and explain its expected value before running it.

Computer practice: https://applydjinn.com/typorithm/drf-lab-20261105.zip. Install pinned dependencies in an isolated Python environment. Run verify.py, then change the working exercise baseline and run --practice drf-throttling. The real DRF clients, serializers, schemas and SQLite transactions use owned fixtures only. No production database, feed, account or paid service is used. This lab executes Python; the PC typing trainer and native reading progress do not execute it.

Verify allow, deny and recovery without sleeps or promises of a security perimeter.