# ================================================ # File: server/utils.py # ================================================ import json import os from typing import Any, Dict, Optional from aiohttp import web # Import necessary components from our modules from ..api.huggingface import HuggingFaceAPI from ..utils.helpers import parse_huggingface_input async def get_request_json(request): """Safely get JSON data from request.""" try: return await request.json() except Exception as e: print(f"Error parsing request JSON: {e}") raise web.HTTPBadRequest(reason=f"Invalid JSON format: {e}") def resolve_huggingface_api_key(payload: Optional[Dict[str, Any]] = None) -> Optional[str]: """ Resolve API key priority: 1) Explicit key from request payload (`api_key`) 2) `HUGGINGFACE_TOKEN` environment variable """ request_key = "" if isinstance(payload, dict): raw_key = payload.get("api_key", "") if isinstance(raw_key, str): request_key = raw_key.strip() if request_key: return request_key env_key = os.getenv("HUGGINGFACE_TOKEN", "").strip() if env_key: return env_key return None async def get_huggingface_model_and_version_details(api: HuggingFaceAPI, model_url_or_id: str, req_version_id: Optional[int]) -> Dict[str, Any]: """ Helper to fetch HuggingFace details. Prioritizes fetching model info based on resolved Model ID. Fetches specific version info if version ID is provided/resolved, otherwise latest. Returns a dict with 'model_info', 'version_info', 'primary_file', and resolved IDs. Raises HTTP exceptions on critical failures. """ target_model_id = None target_version_id = None potential_version_id_from_input = None model_info = {} version_info_to_use = {} # The version (specific or latest) whose file we'll use primary_file = None # --- 1. Parse Input to get potential IDs --- parsed_model_id, parsed_version_id = parse_huggingface_input(model_url_or_id) # Determine the initial target model ID (input URL/ID takes precedence) target_model_id = parsed_model_id # Determine the specific version requested (explicit param > URL param) if req_version_id and str(req_version_id).isdigit(): try: potential_version_id_from_input = int(req_version_id) except (ValueError, TypeError): print(f"[API Helper] Warning: Invalid req_version_id: {req_version_id}. Ignoring.") elif parsed_version_id: potential_version_id_from_input = parsed_version_id # --- 2. Ensure we have a Model ID --- # If we only got a version ID from the input (e.g., huggingface.com/model-versions/456), # we need to fetch that version *first* just to find the model ID. if not target_model_id and potential_version_id_from_input: print(f"[API Helper] Input requires fetching version {potential_version_id_from_input} first to find model ID.") temp_version_info = api.get_model_version_info(potential_version_id_from_input) if temp_version_info and "error" not in temp_version_info and temp_version_info.get('modelId'): target_model_id = temp_version_info['modelId'] print(f"[API Helper] Found Model ID {target_model_id} from Version ID {potential_version_id_from_input}.") # We might reuse temp_version_info later if this was the specifically requested version else: err = temp_version_info.get('details', 'Could not find model ID from version') if isinstance(temp_version_info, dict) else 'API error' raise web.HTTPNotFound(reason=f"Could not determine Model ID from Version ID {potential_version_id_from_input}", body=json.dumps({"error": f"Version {potential_version_id_from_input} not found or missing modelId", "details": err})) # If still no model ID after potential lookup, fail if not target_model_id: raise web.HTTPBadRequest(reason="Could not determine a valid Model ID from the input.") # --- 3. Fetch Core Model Information (Always based on target_model_id) --- print(f"[API Helper] Fetching core model info for Model ID: {target_model_id}") model_info_result = api.get_model_info(target_model_id) if not model_info_result or "error" in model_info_result: err_details = model_info_result.get('details', 'Unknown API error') if isinstance(model_info_result, dict) else 'Unknown API error' raise web.HTTPNotFound(reason=f"Model {target_model_id} not found or API error", body=json.dumps({"error": f"Model {target_model_id} not found or API error", "details": err_details})) model_info = model_info_result # Store the successfully fetched model info # --- 4. Determine and Fetch Version Info for File Details --- if potential_version_id_from_input: # User specified a version explicitly, fetch its details print(f"[API Helper] Fetching specific version info for Version ID: {potential_version_id_from_input}") target_version_id = potential_version_id_from_input # This is the version we need info for # Check if we already fetched this during Model ID lookup if 'temp_version_info' in locals() and temp_version_info.get('id') == target_version_id: print("[API Helper] Reusing version info fetched earlier.") version_info_to_use = temp_version_info else: version_info_result = api.get_model_version_info(target_version_id) if not version_info_result or "error" in version_info_result: err_details = version_info_result.get('details', 'Unknown API error') if isinstance(version_info_result, dict) else 'Unknown API error' raise web.HTTPNotFound(reason=f"Specified Version {target_version_id} not found or API error", body=json.dumps({"error": f"Version {target_version_id} not found or API error", "details": err_details})) version_info_to_use = version_info_result else: # No specific version requested, find latest/default from model_info print(f"[API Helper] Finding latest/default version for Model ID: {target_model_id}") versions = model_info.get("modelVersions") if not versions or not isinstance(versions, list) or len(versions) == 0: raise web.HTTPNotFound(reason=f"Model {target_model_id} has no listed model versions.") # Find the 'best' default version (usually first published) default_version_summary = next((v for v in versions if v.get('status') == 'Published'), versions[0]) target_version_id = default_version_summary.get('id') if not target_version_id: raise web.HTTPNotFound(reason=f"Model {target_model_id}'s latest version has no ID.") print(f"[API Helper] Using latest/default Version ID: {target_version_id}. Fetching its full details.") # Fetch full details for this latest version version_info_result = api.get_model_version_info(target_version_id) if not version_info_result or "error" in version_info_result: # Log error, but maybe try to proceed with summary data if desperate? Risky. err_details = version_info_result.get('details', 'Unknown error getting full version') if isinstance(version_info_result, dict) else 'Error' print(f"[API Helper] Warning: Could not fetch full details for latest version {target_version_id}. Details: {err_details}. Falling back to summary.") # Use summary data from model_info as fallback - file info might be missing! version_info_to_use = default_version_summary # Ensure minimal structure for file finding later version_info_to_use['files'] = version_info_to_use.get('files', []) version_info_to_use['images'] = version_info_to_use.get('images', []) version_info_to_use['modelId'] = version_info_to_use.get('modelId', target_model_id) # Ensure modelId is present version_info_to_use['model'] = version_info_to_use.get('model', {'name': model_info.get('name', 'Unknown')}) # Add fallback model name else: version_info_to_use = version_info_result # --- 5. Find Primary File from the Determined Version (version_info_to_use) --- print(f"[API Helper] Finding primary file for Version ID: {target_version_id}") files = version_info_to_use.get("files", []) if not isinstance(files, list): files = [] # Handle fallback downloadUrl at version level if 'files' is empty/missing if not files and 'downloadUrl' in version_info_to_use and version_info_to_use['downloadUrl']: print("[API Helper] Warning: No 'files' array found, using version-level 'downloadUrl'.") files = [{ "id": None, "name": version_info_to_use.get('name', f"version_{target_version_id}_file"), "primary": True, "type": "Model", "sizeKB": version_info_to_use.get('fileSizeKB'), "downloadUrl": version_info_to_use['downloadUrl'], "hashes": {}, "metadata": {} }] if not files: raise web.HTTPNotFound(reason=f"Version {target_version_id} (Name: {version_info_to_use.get('name', 'N/A')}) has no files listed.") # For HuggingFace, just pick the first file or largest .safetensors primary_file = None if files and isinstance(files, list): # Try to find .safetensors first for file_info in files: if (file_info.get("type") == "file" and file_info.get("path", "").endswith(".safetensors")): primary_file = file_info break # If no .safetensors, pick the largest file if not primary_file: primary_file = max(files, key=lambda x: x.get("size", 0), default=None) if not primary_file: raise web.HTTPNotFound(reason=f"Could not find any usable file with a download URL for version {target_version_id}.") print(f"[API Helper] Selected file: Name='{primary_file.get('name', 'N/A')}', SizeKB={primary_file.get('sizeKB')}") # --- 6. Return Results --- return { "model_info": model_info, # Always the full model info "version_info": version_info_to_use, # Info for the specific/latest version "primary_file": primary_file, # The file from that version "target_model_id": target_model_id, # Resolved model ID "target_version_id": target_version_id, # Resolved version ID (specific or latest) }