Ограничить частоту отправки#
Считать нужно отправки.
Rate limiter вызывается перед каждой физической отправкой, а не один раз на логический метод. Поэтому retry, redirect и replay защитной проверки получают отдельную reservation.
# examples/docs/reliability_rate_limit.py
@dataclass(slots=True)
class CountingLimiter:
reservations: list[str] = field(default_factory=list)
def reserve(self, context: RateLimitContext) -> RateLimitDecision:
self.reservations.append(f"{context.authority} attempt={context.attempt}")
return RateLimitDecision(delay=0.0)
def main() -> None:
limiter = CountingLimiter()
config = ClientConfig(resilience=Resilience(rate_limiter=limiter))
with guide_client(GuideSite(), config=config) as client:
result = LimitedApi(client).get()
print(f"result: {result.value}")
print(f"reservation: {limiter.reservations[0]}")
context.authority позволяет разделить buckets по сервисам. Также доступны операция, номер и вид
попытки. Limiter возвращает задержку, но не отправляет HTTP-запрос самостоятельно.
Файл целиком
"""Reserve every physical transport send with a rate limiter."""
from dataclasses import dataclass, field
from pydantic import BaseModel
from eazy_sdk import ClientConfig, Http, HttpOperation, Resilience, SyncApi, op
from eazy_sdk.ratelimit_runtime import RateLimitContext, RateLimitDecision
from examples.docs.guide_site import GuideSite, guide_client
class Result(BaseModel):
value: str
@dataclass(frozen=True, slots=True, kw_only=True)
class GetLimited(HttpOperation[Result]):
__http__ = Http.get("/rate")
class LimitedApi(SyncApi):
get = op(GetLimited)
# region docs: reliability-rate-limit
# examples/docs/reliability_rate_limit.py
@dataclass(slots=True)
class CountingLimiter:
reservations: list[str] = field(default_factory=list)
def reserve(self, context: RateLimitContext) -> RateLimitDecision:
self.reservations.append(f"{context.authority} attempt={context.attempt}")
return RateLimitDecision(delay=0.0)
def main() -> None:
limiter = CountingLimiter()
config = ClientConfig(resilience=Resilience(rate_limiter=limiter))
with guide_client(GuideSite(), config=config) as client:
result = LimitedApi(client).get()
print(f"result: {result.value}")
print(f"reservation: {limiter.reservations[0]}")
# endregion docs: reliability-rate-limit
if __name__ == "__main__":
main()
result: limited
reservation: api.mail.example attempt=1
Ожидание учитывает cancellation и общий timeout. Повтор после 429 создаёт новую reservation;
получение и проверка обязательной protection тоже проходят limiter, когда реализованы обычными
scoped API-операциями, и потому не образуют скрытый transport path.