74
sms/guard.py
Normal file
74
sms/guard.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""Outbound send guard: per-UTC-day counter + structured logging.
|
||||
|
||||
The voip.ms API caps sending at 100 SMS/MMS per day. This guard refuses
|
||||
sends beyond a configurable limit (default 100) before they hit the network,
|
||||
and logs every outbound send.
|
||||
|
||||
Note: this counter is per-process. If you run multiple instances behind a
|
||||
load balancer, replace it with a shared store (Redis, etc.).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from .exceptions import VoipMsRateLimitError
|
||||
|
||||
logger = logging.getLogger("voipms.sms.guard")
|
||||
|
||||
|
||||
class DailySendCounter:
|
||||
"""Counts outbound sends per UTC day, refusing sends past the limit."""
|
||||
|
||||
def __init__(self, limit: int) -> None:
|
||||
self._limit = limit
|
||||
self._date: str | None = None
|
||||
self._count = 0
|
||||
|
||||
@staticmethod
|
||||
def _today() -> str:
|
||||
return datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
||||
|
||||
@property
|
||||
def used_today(self) -> int:
|
||||
if self._date != self._today():
|
||||
return 0
|
||||
return self._count
|
||||
|
||||
@property
|
||||
def limit(self) -> int:
|
||||
return self._limit
|
||||
|
||||
def check_and_increment(self) -> int:
|
||||
"""Raise VoipMsRateLimitError if the daily cap is hit; otherwise increment.
|
||||
|
||||
Returns the count after this send.
|
||||
"""
|
||||
today = self._today()
|
||||
if self._date != today:
|
||||
self._date = today
|
||||
self._count = 0
|
||||
|
||||
if self._count >= self._limit:
|
||||
raise VoipMsRateLimitError(
|
||||
f"daily send limit ({self._limit}) reached",
|
||||
limit=self._limit,
|
||||
used_today=self._count,
|
||||
)
|
||||
|
||||
self._count += 1
|
||||
return self._count
|
||||
|
||||
def log_send(self, *, kind: str, did: str, dst: str, chars: int) -> None:
|
||||
remaining = self._limit - self._count
|
||||
logger.info(
|
||||
"voipms_send kind=%s did=%s dst=%s chars=%d used_today=%d limit=%d remaining=%d",
|
||||
kind,
|
||||
did,
|
||||
dst,
|
||||
chars,
|
||||
self._count,
|
||||
self._limit,
|
||||
remaining,
|
||||
)
|
||||
Reference in New Issue
Block a user