Skip to content

Commit 5126128

Browse files
committed
✨ publish: resilience system v1.2
1 parent 7f1e39b commit 5126128

2 files changed

Lines changed: 69 additions & 17 deletions

File tree

dymoapi/resilience/__init__.py

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,83 @@
1111
class RateLimitTracker:
1212
def __init__(self):
1313
self.client_limits = {} # client_id -> rate_limit_info
14-
14+
15+
def _parse_header_value(self, value: Any) -> Optional[int]:
16+
"""
17+
Parses a header value that could be a number or "unlimited".
18+
Returns None if the value is "unlimited", None, or invalid.
19+
"""
20+
# Handle non-string types (lists, dicts, None, etc.)
21+
if value is None:
22+
return None
23+
if isinstance(value, (list, dict)):
24+
return None
25+
26+
# Convert to string and normalize
27+
try:
28+
str_value = str(value).strip().lower()
29+
except Exception:
30+
return None
31+
32+
if not str_value or str_value == "unlimited":
33+
return None
34+
35+
try:
36+
# Handle floats by converting to float first, then int
37+
parsed = int(float(str_value))
38+
# Rate limits can't be negative
39+
return parsed if parsed >= 0 else None
40+
except (ValueError, TypeError):
41+
return None
42+
1543
def update_rate_limit(self, client_id: str, headers: Dict[str, str]):
1644
if client_id not in self.client_limits:
1745
self.client_limits[client_id] = {}
18-
46+
1947
limit_info = self.client_limits[client_id]
20-
21-
# Update rate limit headers
22-
if 'X-Ratelimit-Limit-Requests' in headers:
23-
limit_info['limit'] = int(headers['X-Ratelimit-Limit-Requests'])
24-
if 'X-Ratelimit-Remaining-Requests' in headers:
25-
limit_info['remaining'] = int(headers['X-Ratelimit-Remaining-Requests'])
26-
if 'X-Ratelimit-Reset-Requests' in headers:
27-
limit_info['reset_time'] = headers['X-Ratelimit-Reset-Requests']
28-
if 'retry-after' in headers:
29-
limit_info['retry_after'] = int(headers['retry-after'])
30-
48+
49+
# Get header values (case-insensitive lookup)
50+
headers_lower = {k.lower(): v for k, v in headers.items()}
51+
52+
limit_requests = headers_lower.get('x-ratelimit-limit-requests')
53+
remaining_requests = headers_lower.get('x-ratelimit-remaining-requests')
54+
reset_requests = headers_lower.get('x-ratelimit-reset-requests')
55+
retry_after = headers_lower.get('retry-after')
56+
57+
# Only update numeric values if they are valid numbers (not "unlimited")
58+
parsed_limit = self._parse_header_value(limit_requests)
59+
parsed_remaining = self._parse_header_value(remaining_requests)
60+
parsed_retry_after = self._parse_header_value(retry_after)
61+
62+
if parsed_limit is not None:
63+
limit_info['limit'] = parsed_limit
64+
if parsed_remaining is not None:
65+
limit_info['remaining'] = parsed_remaining
66+
# Mark as unlimited if header explicitly says "unlimited"
67+
if remaining_requests is not None:
68+
try:
69+
if str(remaining_requests).strip().lower() == "unlimited":
70+
limit_info['is_unlimited'] = True
71+
except Exception:
72+
pass
73+
if reset_requests:
74+
limit_info['reset_time'] = reset_requests
75+
if parsed_retry_after is not None:
76+
limit_info['retry_after'] = parsed_retry_after
77+
3178
limit_info['last_updated'] = time.time()
32-
79+
3380
def is_rate_limited(self, client_id: str) -> bool:
3481
if client_id not in self.client_limits:
3582
return False
36-
83+
3784
limit_info = self.client_limits[client_id]
38-
return limit_info.get('remaining', 1) <= 0
85+
# If marked as unlimited, never rate limited
86+
if limit_info.get('is_unlimited', False):
87+
return False
88+
# Only consider rate limited if remaining is explicitly set and is 0 or less
89+
remaining = limit_info.get('remaining')
90+
return remaining is not None and remaining <= 0
3991

4092
def get_retry_after(self, client_id: str) -> Optional[int]:
4193
if client_id not in self.client_limits:

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name="dymoapi",
5-
version="0.0.63",
5+
version="0.0.64",
66
packages=find_packages(),
77
description="Dymo Python API library.",
88
long_description=open("README.md").read(),

0 commit comments

Comments
 (0)