Nivel: Intermedio+
Tema: Control de tasa (rate limiting), tiempo, estado, diseño defensivo, tests con pytest
Objetivo: Implementar un rate limiter estilo Token Bucket para limitar cuántas operaciones se permiten por unidad de tiempo.
Concepto (rápido)
Un Token Bucket tiene:
- una capacidad máxima de tokens,
- una tasa de recarga (tokens por segundo),
- cada operación consume 1 token,
- si no hay tokens disponibles, la operación se rechaza.
Enunciado
Crea una clase TokenBucketRateLimiter con:
Constructor
TokenBucketRateLimiter(capacidad, tokens_por_segundo, ahora_fn=None)
Reglas:
capacidaddebe serint> 0 (si no:TypeErroroValueError)tokens_por_segundodebe serfloatoint> 0 (si no:TypeErroroValueError)ahora_fn(opcional) debe ser callable si se proporciona.- Si no se proporciona, usa
time.time. - Esto permite tests deterministas.
- Si no se proporciona, usa
Método
allow() -> bool
Debe:
- “Recargar” tokens según el tiempo transcurrido desde la última actualización:
tokens += (tiempo_transcurrido * tokens_por_segundo)- nunca superar
capacidad
- Si hay al menos 1 token disponible:
- consumir 1 token,
- devolver
True
- Si no:
- devolver
False
- devolver
- Debe funcionar correctamente aunque se llame muchas veces seguidas.
Ejemplos
rl = TokenBucketRateLimiter(capacidad=3, tokens_por_segundo=1)
rl.allow() # True (2 tokens quedan)
rl.allow() # True (1 token queda)
rl.allow() # True (0 tokens quedan)
rl.allow() # False (sin tokens)
# tras ~1 segundo, debería recargar ~1 token
Pistas
- Guarda:
self.tokenscomofloat(para recargas fraccionales),self.ultimo_tiempo
- En
allow():- calcula
delta = ahora - ultimo_tiempo, - recarga,
- actualiza
ultimo_tiempo, - decide si permitir.
- calcula
- Para tests, inyecta
ahora_fnque tú puedas controlar.
Solución explicada (paso a paso)
- Al crear el limiter:
- inicia
tokens = capacidad ultimo_tiempo = ahora_fn()
- inicia
- En cada
allow():- calcula cuánto tiempo pasó,
- recarga tokens proporcionalmente,
- limita tokens a
capacidad, - si tokens >= 1: consume y permite,
- si no: rechaza.
Ejecuta:
pytest -q
Variantes para subir de nivel (opcional)
- Coste por operación:
allow(cost=1)consumiendo más de 1 token - Multiple buckets por “cliente” (diccionario de buckets por API key)
- Devolver tiempo estimado de espera cuando
allow()esFalse - Persistencia (guardar estado entre ejecuciones)
Lo que aprendiste
- Modelo Token Bucket y por qué se usa en APIs
- Cómo manejar tiempo y estado de forma segura
- Tests deterministas inyectando un reloj
- Validación defensiva de parámetros
Accede al código completo y a los tests en GitHub para ejecutar y modificar la solución localmente.
Siguiente reto recomendado: Reto #27 — Retry con backoff exponencial (sin librerías) para patrones reales de resiliencia en redes y automatización.