"""HIVE API Client.""" from os import environ from typing import Any, Dict import httpx import httpx_retries # https://docs.thehive.ai/reference/submit-a-task-synchronously API_BASE_URL = "https://api.thehive.ai/api/v2" HIVE_CREDENTIALS = environ.get("HIVE_CREDENTIALS") HIVE_RETRIES = int(environ.get("HIVE_RETRIES", "3")) HIVE_TIMEOUT = int(environ.get("HIVE_TIMEOUT", "300")) def run_task(url: str) -> dict: """Run a task in The Hive.""" headers = { "accept": "application/json", "authorization": f"token {HIVE_CREDENTIALS}", } transport = httpx_retries.RetryTransport( retry=httpx_retries.Retry( total=HIVE_RETRIES, backoff_factor=2, status_forcelist=[429] ) ) with httpx.Client(transport=transport) as client: response = client.post( f"{API_BASE_URL}/task/sync", headers=headers, json={"url": url}, timeout=HIVE_TIMEOUT, ) response.raise_for_status() return response.json() def flatten_hive_response(data: Dict[str, Any]): flattened_rows = [] # 1. Root level fields (excluding lists/nested complex objects that we iterate) root_data = {} for k, v in data.items(): if k != 'status' and not isinstance(v, (list, dict)): root_data[k] = v # Assuming there aren't other complex root objects relevant to every row other than status # 2. Iterate status if 'status' in data and isinstance(data['status'], list): for status_item in data['status']: # Flatten status item level status_data = root_data.copy() # Extract 'status' dict inside status_item if 'status' in status_item and isinstance(status_item['status'], dict): for k, v in status_item['status'].items(): status_data[f"status_{k}"] = v # Extract 'response' if 'response' in status_item and isinstance(status_item['response'], dict): response = status_item['response'] # Extract 'input' input_data = status_data.copy() if 'input' in response and isinstance(response['input'], dict): # Flatten input recursively 1 level or explicitly for k, v in response['input'].items(): if isinstance(v, dict): for sub_k, sub_v in v.items(): input_data[f"input_{k}_{sub_k}"] = sub_v else: input_data[f"input_{k}"] = v # Extract 'output' array if 'output' in response and isinstance(response['output'], list): for output_item in response['output']: row = input_data.copy() # Flatten output item # Assuming structure: classes (list of dicts), time, maybe others for k, v in output_item.items(): if k == 'classes' and isinstance(v, list): for cls in v: if isinstance(cls, dict) and 'class' in cls and 'score' in cls: row[cls['class']] = cls['score'] elif not isinstance(v, (dict, list)): row[k] = v else: # Fallback for unknown nested output fields row[k] = str(v) flattened_rows.append(row) else: # No output array, maybe just store the metadata row? # User specifically asked for one row per "output" array item (implied) # If empty, we might skip or record one row with empty fields pass else: # No response object pass if not flattened_rows: print("No output rows found to flatten.") return # 3. Determine all headers headers = set() for row in flattened_rows: headers.update(row.keys()) # Sort headers for consistency # We can prioritize some headers like time, id at the start sorted_headers = list(headers) # Simple sort or custom sort def header_sort_key(h): if h == 'asset_final_id': return '00_asset_final_id' if h == 'input_model': return '01_input_model' if h == 'input_model_version': return '02_input_model_version' if h == 'time': return '03_time' if h.startswith('input_'): return f'04_{h}' if h.startswith('status_'): return f'05_{h}' return f'06_{h}' sorted_headers.sort(key=header_sort_key) return sorted_headers, flattened_rows