"""Apollo GraphOS API client with retry + exponential backoff.""" import json import sys import time import urllib.request from src.config import GRAPHOS_API, USAGE_QUERY def fetch_usage_for_type( api_key: str, graph_id: str, since_str: str, parent_type: str, retries: int = 3, ) -> dict[str, dict[str, int]] | None: """Fetch field usage for all fields of a parentType. Returns: {fieldName: {"executions": int, "operations": int}} or None on failure. """ query = USAGE_QUERY % (graph_id, since_str, parent_type) payload = json.dumps({'query': query}).encode() req = urllib.request.Request( GRAPHOS_API, data=payload, headers={ 'Content-Type': 'application/json', 'x-api-key': api_key, }, ) last_err = None data = None for attempt in range(retries): try: with urllib.request.urlopen(req, timeout=30) as resp: data = json.loads(resp.read()) break except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError) as e: last_err = e if attempt < retries - 1: wait = 2**attempt print(f'RETRY ({attempt + 1}/{retries}, waiting {wait}s: {e})') sys.stdout.flush() time.sleep(wait) sys.stdout.write(f' [retry] {parent_type} ... ') sys.stdout.flush() else: print(f'FAILED after {retries} attempts: {last_err}') return None if data is None: return None if 'errors' in data: msgs = [e.get('message', str(e)) for e in data['errors']] print(f' GraphQL errors for {parent_type}: {"; ".join(msgs)}', file=sys.stderr) return None records = data['data']['service']['statsWindow']['fieldUsage'] usage: dict[str, dict[str, int]] = {} for rec in records: fname = rec['groupBy']['fieldName'] execs = rec['metrics']['estimatedExecutionCount'] ops = rec['metrics']['referencingOperationCount'] if fname in usage: usage[fname]['executions'] += execs usage[fname]['operations'] += ops else: usage[fname] = {'executions': execs, 'operations': ops} return usage