import time import json import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry from config import SPLITIO_BASE_URL, SPLITIO_WORKSPACE_ID, SPLITIO_HEADERS class SplitIOClient: def __init__(self, workspace_id=SPLITIO_WORKSPACE_ID, headers=SPLITIO_HEADERS): self.workspace_id = workspace_id self.session = self._init_session() self.session.headers.update(headers) def _init_session(self): retry_strategy = Retry( total=5, status_forcelist=[500, 502, 503, 504], # exclude 429 allowed_methods=["GET", "POST", "DELETE", "PATCH"], backoff_factor=1 ) adapter = HTTPAdapter(max_retries=retry_strategy) session = requests.Session() session.mount("https://", adapter) session.mount("http://", adapter) return session def _request_with_custom_retry(self, method, url, retries=5, **kwargs): for attempt in range(retries + 1): resp = self.session.request(method, url, **kwargs) if resp.status_code == 429: org = int(resp.headers.get("X-RateLimit-Reset-Seconds-Org", 1)) ip = int(resp.headers.get("X-RateLimit-Reset-Seconds-IP", 1)) wait_time = max(org, ip) print(f"⏳ Rate limited. Retrying after {wait_time}s (attempt {attempt + 1}/{retries})") time.sleep(wait_time) continue if resp.status_code >= 400: raise Exception(f"HTTP {resp.status_code}: {resp.text}") if "application/json" in resp.headers.get("Content-Type", ""): return resp.json() return resp raise Exception(f"Exceeded retry attempts for {method} {url}") def get_feature_flag_metadata(self, flag_name): url = f"{SPLITIO_BASE_URL}/splits/ws/{self.workspace_id}/{flag_name}" return self._request_with_custom_retry("GET", url) def get_environments(self): url = f"{SPLITIO_BASE_URL}/environments/ws/{self.workspace_id}" return self._request_with_custom_retry("GET", url) def get_feature_flag_definition(self, env_id, flag_name): url = f"{SPLITIO_BASE_URL}/splits/ws/{self.workspace_id}/{flag_name}/environments/{env_id}" try: return self._request_with_custom_retry("GET", url) except Exception as e: if "HTTP 404" in str(e): return None raise def get_traffic_types(self): url = f"{SPLITIO_BASE_URL}/trafficTypes/ws/{self.workspace_id}" return self._request_with_custom_retry("GET", url) def delete_feature_flag(self, flag_name): url = f"{SPLITIO_BASE_URL}/splits/ws/{self.workspace_id}/{flag_name}" return self._request_with_custom_retry("DELETE", url) def recreate_feature_flag(self, flag_name, traffic_type_id, metadata): url = f"{SPLITIO_BASE_URL}/splits/ws/{self.workspace_id}/trafficTypes/{traffic_type_id}" payload = { "name": flag_name, "description": metadata.get("description", ""), "owners": metadata.get("owners", []) } return self._request_with_custom_retry("POST", url, data=json.dumps(payload)) def patch_feature_flag(self, flag_name, patch_ops): url = f"{SPLITIO_BASE_URL}/splits/ws/{self.workspace_id}/{flag_name}" return self._request_with_custom_retry("PATCH", url, data=json.dumps(patch_ops)) def post_definition_to_env(self, flag_name, env_id, definition_data): url = f"{SPLITIO_BASE_URL}/splits/ws/{self.workspace_id}/{flag_name}/environments/{env_id}" return self._request_with_custom_retry("POST", url, data=json.dumps(definition_data))