Files
finance/backend/app/ratelimit.py
T
k1nq d582f15681 feat: add tests for import polling, login page, money utilities, and query keys
- Implemented tests for import polling functionality, ensuring correct behavior for active and terminal import statuses.
- Created tests for the login page, validating user interactions and error handling.
- Added unit tests for money utility functions, covering parsing, formatting, and summation of monetary values.
- Developed tests for query keys, ensuring proper scoping and invalidation roots.

chore: setup testing environment and configuration

- Added setup file for testing with Vitest and Testing Library.
- Configured TypeScript and Vite for the frontend project.
- Created Docker and Nginx configurations for local development and production environments.
- Established a PostgreSQL initialization script for setting up application roles.

docs: add README and environment configuration for infra

- Included README for the infra directory, detailing local development setup and service descriptions.
- Created an example environment configuration file for local development with placeholders for sensitive information.
2026-09-14 09:26:33 +05:00

43 lines
1.2 KiB
Python

import hashlib
import threading
import time
from collections import defaultdict, deque
from redis import Redis
from redis.exceptions import RedisError
from app.config import get_settings
_lock = threading.Lock()
_hits: dict[str, deque[float]] = defaultdict(deque)
def allow(key: str, limit: int, window_seconds: float) -> bool:
"""Distributed fixed-window limiter with a local fail-safe fallback."""
digest = hashlib.sha256(key.encode()).hexdigest()
window = int(time.time() // window_seconds)
try:
client = Redis.from_url(
get_settings().redis_url,
socket_connect_timeout=0.2,
socket_timeout=0.2,
)
redis_key = f"rate:{digest}:{window}"
with client.pipeline() as pipeline:
pipeline.incr(redis_key)
pipeline.expire(redis_key, int(window_seconds) + 1)
count, _ = pipeline.execute()
return int(count) <= limit
except RedisError:
pass
now = time.monotonic()
with _lock:
bucket = _hits[digest]
while bucket and bucket[0] <= now - window_seconds:
bucket.popleft()
if len(bucket) >= limit:
return False
bucket.append(now)
return True