Browse Source

Rename quota key constants and Redis keys with api_ prefix

- QUOTA_DAILY_HASH  → API_QUOTA_DAILY_HASH
- QUOTA_WEEKLY_HASH → API_QUOTA_WEEKLY_HASH
- Redis key templates: quota:{ns}:... → api_quota:{ns}:...
- Local variables d_hash/w_hash → api_d_hash/api_w_hash in _check_api_quota
- Update test imports and assertions accordingly

Migration: flush old keys with
  redis-cli -n 1 --scan --pattern 'quota:*' | xargs -L 100 redis-cli -n 1 DEL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
rate-limiter-2026
Edward Ribeiro 3 days ago
parent
commit
504e1a5639
  1. 16
      sapl/middleware/ratelimit.py
  2. 8
      sapl/middleware/test_ratelimiter.py

16
sapl/middleware/ratelimit.py

@ -82,15 +82,15 @@ RL_INDEX_API_BLOCKED_IPS = 'rl:index:api_blocked_ips'
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# API quota keys — per-tenant HASH, one key per period. # API quota keys — per-tenant HASH, one key per period.
# Structure: HASH key = quota:{ns}:daily:{date} field = {ip} value = counter # Structure: HASH key = api_quota:{ns}:daily:{date} field = {ip} value = counter
# HASH key = quota:{ns}:weekly:{week} field = {ip} value = counter # HASH key = api_quota:{ns}:weekly:{week} field = {ip} value = counter
# Weekly key uses ISO week notation (yyyy-Www) — unambiguous, Monday-anchored. # Weekly key uses ISO week notation (yyyy-Www) — unambiguous, Monday-anchored.
# TTL set once on hash creation (Lua TTL guard); resets are implicit in the # TTL set once on hash creation (Lua TTL guard); resets are implicit in the
# date/week embedded in the key name. Fields are IPs; no per-field TTL. # date/week embedded in the key name. Fields are IPs; no per-field TTL.
# Memory: ~63 bytes/field vs ~148 bytes for per-IP STRING keys (~57% saving). # Memory: ~63 bytes/field vs ~148 bytes for per-IP STRING keys (~57% saving).
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
QUOTA_DAILY_HASH = 'quota:{ns}:daily:{date}' API_QUOTA_DAILY_HASH = 'api_quota:{ns}:daily:{date}'
QUOTA_WEEKLY_HASH = 'quota:{ns}:weekly:{week}' API_QUOTA_WEEKLY_HASH = 'api_quota:{ns}:weekly:{week}'
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Bot UA fragments # Bot UA fragments
@ -607,13 +607,13 @@ class RateLimitMiddleware:
week_str = f'{iso[0]}-W{iso[1]:02d}' week_str = f'{iso[0]}-W{iso[1]:02d}'
ip = get_client_ip(request) ip = get_client_ip(request)
d_hash = QUOTA_DAILY_HASH.format(ns=_NAMESPACE, date=date_str) api_d_hash = API_QUOTA_DAILY_HASH.format(ns=_NAMESPACE, date=date_str)
w_hash = QUOTA_WEEKLY_HASH.format(ns=_NAMESPACE, week=week_str) api_w_hash = API_QUOTA_WEEKLY_HASH.format(ns=_NAMESPACE, week=week_str)
try: try:
if _hincrby_with_ttl(d_hash, ip, 86400) > self.api_quota_daily: if _hincrby_with_ttl(api_d_hash, ip, 86400) > self.api_quota_daily:
return 'daily' return 'daily'
if _hincrby_with_ttl(w_hash, ip, 7 * 86400) > self.api_quota_weekly: if _hincrby_with_ttl(api_w_hash, ip, 7 * 86400) > self.api_quota_weekly:
return 'weekly' return 'weekly'
except Exception: except Exception:
pass # fail open — quota not enforced when Redis unavailable pass # fail open — quota not enforced when Redis unavailable

8
sapl/middleware/test_ratelimiter.py

@ -20,8 +20,8 @@ from sapl.middleware.ratelimit import (
_parse_rate, _parse_rate,
get_client_ip, get_client_ip,
make_ratelimit_cache_key, make_ratelimit_cache_key,
QUOTA_DAILY_HASH, API_QUOTA_DAILY_HASH,
QUOTA_WEEKLY_HASH, API_QUOTA_WEEKLY_HASH,
RateLimitMiddleware, RateLimitMiddleware,
RL_API_IP_BLOCKED, RL_API_IP_BLOCKED,
RL_API_IP_REQUESTS, RL_API_IP_REQUESTS,
@ -652,8 +652,8 @@ def test_api_quota_uses_hash_keys():
today = date.today() today = date.today()
iso = today.isocalendar() iso = today.isocalendar()
expected_daily_hash = QUOTA_DAILY_HASH.format(ns=_NAMESPACE, date=today.isoformat()) expected_daily_hash = API_QUOTA_DAILY_HASH.format(ns=_NAMESPACE, date=today.isoformat())
expected_weekly_hash = QUOTA_WEEKLY_HASH.format( expected_weekly_hash = API_QUOTA_WEEKLY_HASH.format(
ns=_NAMESPACE, week=f'{iso[0]}-W{iso[1]:02d}' ns=_NAMESPACE, week=f'{iso[0]}-W{iso[1]:02d}'
) )
calls = mock_h.call_args_list calls = mock_h.call_args_list

Loading…
Cancel
Save