From 1fa5a969114830132e6d217dc3d56a9281fb6025 Mon Sep 17 00:00:00 2001 From: orenk Date: Tue, 4 Nov 2025 13:16:22 +0200 Subject: [PATCH 1/2] refactor(errors): better rate limit management in AnalysisRateLimitError --- intezer_sdk/errors.py | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/intezer_sdk/errors.py b/intezer_sdk/errors.py index aad379f..57af24c 100644 --- a/intezer_sdk/errors.py +++ b/intezer_sdk/errors.py @@ -1,5 +1,5 @@ from typing import Optional - +import datetime import requests @@ -151,9 +151,7 @@ def __init__(self, response: requests.Response): class AlertInProgressError(AlertError): def __init__(self, alert_id: str): - super().__init__( - f'The alert {alert_id} is being processed at the moment, please try again later' - ) + super().__init__(f'The alert {alert_id} is being processed at the moment, please try again later') class AlertNotFoundError(AlertError): @@ -182,13 +180,33 @@ def __init__(self, response: requests.Response): class AnalysisRateLimitError(ServerError): - def __init__(self, response: requests.Response): - super().__init__('Analysis rate limit reached', response) + def __init__(self, response: requests.Response, message: str = 'Analysis rate limit reached'): + super().__init__(message, response) self.limit = response.headers.get('X-RateLimit-Limit') self.remaining = response.headers.get('X-RateLimit-Remaining') - self.reset_time_in_sec = response.headers.get('X-RateLimit-Reset') + self.reset_time_in_sec = response.headers.get( + 'X-RateLimit-Reset' + ) # Timestamp at which this rate limit will be reset self.retry_after = response.headers.get('Retry-After') + # Compute rate_limit_remaining_time_in_seconds (seconds to sleep) + now = datetime.datetime.utcnow().timestamp() + sleep_for = None + + if self.retry_after: + try: + sleep_for = float(self.retry_after) + except (ValueError, TypeError): + pass + elif self.reset_time_in_sec: + try: + reset_time = float(self.reset_time_in_sec) + sleep_for = max(1, reset_time - now) + except (ValueError, TypeError): + pass + + self.rate_limit_remaining_time_in_seconds = sleep_for + class IncidentNotFoundError(IntezerError): def __init__(self, incident_id: str): From f4e0431cfd2703b87e51d13384b771a3cb889c8f Mon Sep 17 00:00:00 2001 From: orenk Date: Tue, 4 Nov 2025 15:12:43 +0200 Subject: [PATCH 2/2] chore: bump version to 1.24.1 and update CHANGES for rate limit exception improvements --- CHANGES | 4 ++++ intezer_sdk/__init__.py | 2 +- intezer_sdk/errors.py | 47 +++++++++++++++++++++-------------------- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/CHANGES b/CHANGES index 7402923..9ea5650 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +1.24.1 +------- +- Rate limit exception improvements + 1.24.0 ------- - Introduce File class diff --git a/intezer_sdk/__init__.py b/intezer_sdk/__init__.py index 6376bae..785c3b8 100644 --- a/intezer_sdk/__init__.py +++ b/intezer_sdk/__init__.py @@ -1 +1 @@ -__version__ = '1.24.0' +__version__ = '1.24.1' diff --git a/intezer_sdk/errors.py b/intezer_sdk/errors.py index 57af24c..62583d2 100644 --- a/intezer_sdk/errors.py +++ b/intezer_sdk/errors.py @@ -182,30 +182,31 @@ def __init__(self, response: requests.Response): class AnalysisRateLimitError(ServerError): def __init__(self, response: requests.Response, message: str = 'Analysis rate limit reached'): super().__init__(message, response) - self.limit = response.headers.get('X-RateLimit-Limit') - self.remaining = response.headers.get('X-RateLimit-Remaining') - self.reset_time_in_sec = response.headers.get( - 'X-RateLimit-Reset' - ) # Timestamp at which this rate limit will be reset - self.retry_after = response.headers.get('Retry-After') - - # Compute rate_limit_remaining_time_in_seconds (seconds to sleep) + + self.limit: Optional[int] = self._parse_header(response.headers, 'X-RateLimit-Limit', int) + self.remaining: Optional[int] = self._parse_header(response.headers, 'X-RateLimit-Remaining', int) + self.reset_time_in_sec: Optional[float] = self._parse_header(response.headers, 'X-RateLimit-Reset', float) + self.retry_after: Optional[float] = self._parse_header(response.headers, 'Retry-After', float) + now = datetime.datetime.utcnow().timestamp() - sleep_for = None - - if self.retry_after: - try: - sleep_for = float(self.retry_after) - except (ValueError, TypeError): - pass - elif self.reset_time_in_sec: - try: - reset_time = float(self.reset_time_in_sec) - sleep_for = max(1, reset_time - now) - except (ValueError, TypeError): - pass - - self.rate_limit_remaining_time_in_seconds = sleep_for + if self.retry_after is not None: + remaining_time = self.retry_after + elif self.reset_time_in_sec is not None: + remaining_time = max(1.0, self.reset_time_in_sec - now) + else: + remaining_time = None + + self.rate_limit_remaining_time_in_seconds = remaining_time + + @staticmethod + def _parse_header(headers, name: str, cast_type): + value = headers.get(name) + if not value: + return None + try: + return cast_type(value) + except (ValueError, TypeError): + return value class IncidentNotFoundError(IntezerError):