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.
# Structure: HASH key = quota:{ns}:daily:{date} field = {ip} value = counter
# HASH key = quota:{ns}:weekly:{week} field = {ip} value = counter
# Structure: HASH key = api_quota:{ns}:daily:{date} 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.
# 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.
# Memory: ~63 bytes/field vs ~148 bytes for per-IP STRING keys (~57% saving).
# ---------------------------------------------------------------------------
QUOTA_DAILY_HASH = 'quota:{ns}:daily:{date}'
QUOTA_WEEKLY_HASH = 'quota:{ns}:weekly:{week}'
API_QUOTA_DAILY_HASH = 'api_quota:{ns}:daily:{date}'
API_QUOTA_WEEKLY_HASH = 'api_quota:{ns}:weekly:{week}'
# ---------------------------------------------------------------------------
# Bot UA fragments
@ -607,13 +607,13 @@ class RateLimitMiddleware:
week_str = f'{iso[0]}-W{iso[1]:02d}'
ip = get_client_ip(request)
d_hash = QUOTA_DAILY_HASH.format(ns=_NAMESPACE, date=date_str)
w_hash = QUOTA_WEEKLY_HASH.format(ns=_NAMESPACE, week=week_str)
api_d_hash = API_QUOTA_DAILY_HASH.format(ns=_NAMESPACE, date=date_str)
api_w_hash = API_QUOTA_WEEKLY_HASH.format(ns=_NAMESPACE, week=week_str)
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'
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'
except Exception:
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,
get_client_ip,
make_ratelimit_cache_key,
QUOTA_DAILY_HASH,
QUOTA_WEEKLY_HASH,
API_QUOTA_DAILY_HASH,
API_QUOTA_WEEKLY_HASH,
RateLimitMiddleware,
RL_API_IP_BLOCKED,
RL_API_IP_REQUESTS,
@ -652,8 +652,8 @@ def test_api_quota_uses_hash_keys():
today = date.today()
iso = today.isocalendar()
expected_daily_hash = QUOTA_DAILY_HASH.format(ns=_NAMESPACE, date=today.isoformat())
expected_weekly_hash = QUOTA_WEEKLY_HASH.format(
expected_daily_hash = API_QUOTA_DAILY_HASH.format(ns=_NAMESPACE, date=today.isoformat())
expected_weekly_hash = API_QUOTA_WEEKLY_HASH.format(
ns=_NAMESPACE, week=f'{iso[0]}-W{iso[1]:02d}'
)
calls = mock_h.call_args_list

Loading…
Cancel
Save