-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinfrastructure.py
More file actions
128 lines (111 loc) · 4.47 KB
/
Copy pathinfrastructure.py
File metadata and controls
128 lines (111 loc) · 4.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
"""Optional production infrastructure that never blocks local development."""
from copy import deepcopy
from hashlib import sha256
import json
import logging
import os
import time
LOGGER = logging.getLogger(__name__)
class RedisJSONCache:
"""JSON-only shared cache and fixed-window limiter backed by Redis."""
backend_name = "redis"
def __init__(self, client, prefix="skycast"):
self._client = client
self._prefix = prefix
def _cache_key(self, key):
serialized = json.dumps(key, ensure_ascii=True, separators=(",", ":"))
digest = sha256(serialized.encode("utf-8")).hexdigest()
return f"{self._prefix}:cache:{digest}"
def clear(self):
try:
keys = list(self._client.scan_iter(f"{self._prefix}:cache:*", count=250))
if keys:
self._client.delete(*keys)
except Exception as error: # Redis is an optional acceleration layer.
LOGGER.warning("Redis cache clear failed: %s", type(error).__name__)
def get(self, key):
try:
value = self._client.get(self._cache_key(key))
return deepcopy(json.loads(value)) if value is not None else None
except Exception as error:
LOGGER.warning("Redis cache read failed: %s", type(error).__name__)
return None
def set(self, key, value, ttl_seconds):
try:
payload = json.dumps(value, ensure_ascii=False, separators=(",", ":"))
self._client.setex(
self._cache_key(key),
max(1, int(ttl_seconds)),
payload,
)
except Exception as error:
LOGGER.warning("Redis cache write failed: %s", type(error).__name__)
def check_rate_limit(self, namespace, client_identifier, maximum, window_seconds):
"""Return (allowed, retry_after), or None when Redis is unavailable."""
try:
window_seconds = max(1, int(window_seconds))
window = int(time.time()) // window_seconds
client_digest = sha256(
str(client_identifier).encode("utf-8")
).hexdigest()[:24]
key = f"{self._prefix}:rate:{namespace}:{client_digest}:{window}"
pipeline = self._client.pipeline(transaction=True)
pipeline.incr(key)
pipeline.expire(key, window_seconds + 1)
count, _ = pipeline.execute()
retry_after = max(1, self._client.ttl(key))
return int(count) <= int(maximum), retry_after
except Exception as error:
LOGGER.warning("Redis rate limit failed: %s", type(error).__name__)
return None
def create_redis_cache():
"""Create a Redis backend only when REDIS_URL is configured and reachable."""
redis_url = os.getenv("REDIS_URL", "").strip()
if not redis_url:
return None
try:
import redis
client = redis.Redis.from_url(
redis_url,
decode_responses=True,
socket_connect_timeout=1,
socket_timeout=1,
health_check_interval=30,
)
client.ping()
return RedisJSONCache(client, os.getenv("REDIS_PREFIX", "skycast"))
except Exception as error:
LOGGER.warning(
"Redis is configured but unavailable; using in-memory storage (%s).",
type(error).__name__,
)
return None
def initialize_sentry():
"""Enable privacy-conscious Sentry monitoring only when a DSN is configured."""
dsn = os.getenv("SENTRY_DSN", "").strip()
if not dsn:
return False
try:
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration
try:
traces_sample_rate = float(
os.getenv("SENTRY_TRACES_SAMPLE_RATE", "0.05")
)
except ValueError:
traces_sample_rate = 0.05
sentry_sdk.init(
dsn=dsn,
integrations=[FlaskIntegration()],
traces_sample_rate=max(0.0, min(1.0, traces_sample_rate)),
send_default_pii=False,
environment=os.getenv("APP_ENV", "production"),
release=os.getenv("APP_RELEASE") or None,
)
return True
except Exception as error:
LOGGER.warning(
"Sentry is configured but could not initialize (%s).",
type(error).__name__,
)
return False