"""Small Redis lock helpers for background tasks.""" from dataclasses import dataclass import uuid import redis from app.core.config import settings @dataclass(frozen=True) class RedisLockHandle: client: redis.Redis key: str token: bytes def acquire_redis_lock(key: str, *, ttl_seconds: int) -> RedisLockHandle | None: """Acquire a single-owner Redis lock or return None when unavailable.""" client = redis.from_url(settings.redis_url, decode_responses=False) token = uuid.uuid4().hex.encode("utf-8") if not client.set(key, token, nx=True, ex=ttl_seconds): return None return RedisLockHandle(client=client, key=key, token=token) def release_redis_lock(handle: RedisLockHandle | None) -> None: """Release the lock only if we still own it.""" if handle is None: return handle.client.eval( """ if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) end return 0 """, 1, handle.key, handle.token, )