"""Explicit configuration generation; import performs no I/O. Returns YAML on success, otherwise raises BuildConfigError with safe attribution and HTTP/platform status. Server messages and transport details are never exposed. """ import json import os import urllib.error import urllib.parse import urllib.request from src.task_add_client import resolve_token class BuildConfigError(RuntimeError): def __init__(self, reason, http_status=None, platform_code=None): self.error_attribution = reason self.http_status = http_status self.platform_code = platform_code super().__init__('build-config: ' + reason) def build_config(gpu_type, framework='vllm', task_type='text-generation'): try: token = resolve_token() except (ValueError, OSError): raise BuildConfigError('missing/invalid credential') from None if not isinstance(gpu_type, str) or not gpu_type.strip() or '<' in gpu_type: raise BuildConfigError('gpu_type required (platform enum)') query = urllib.parse.urlencode(dict(gpuType=gpu_type, framework=framework, taskType=task_type)) header = 'Authorization' if os.environ.get('BUILD_CONFIG_USE_AUTHORIZATION') == '1' else 'Xc-Token' try: request = urllib.request.Request( os.environ.get('MODELHUB_API_BASE', 'https://www.modelhub.org.cn').rstrip('/') + '/api/adapt/task/build-config?' + query, headers={header: token, 'Accept': 'application/json'}, method='POST') with urllib.request.urlopen(request, timeout=30) as response: status, raw = response.status, response.read() except urllib.error.HTTPError as error: status, raw = error.code, error.read() except (OSError, urllib.error.URLError, ValueError): raise BuildConfigError('transport/timeout') from None try: body = json.loads(raw) except (ValueError, UnicodeError): raise BuildConfigError('invalid JSON response', status) from None if not isinstance(body, dict): raise BuildConfigError('invalid response object', status) code = body.get('code') # Only numeric codes are retained; arbitrary server strings may echo secrets. code = code if type(code) is int else None if code in (40100, 40101): raise BuildConfigError({40100: '40100 NOT_LOGIN_ERROR', 40101: '40101 NO_AUTH_ERROR'}[code], status, code) if not 200 <= status < 300 or code != 0: raise BuildConfigError('other HTTP/platform code', status, code) data = body.get('data') if not isinstance(data, str) or not data.strip(): raise BuildConfigError('empty/non-str data', status, code) if token in data: raise BuildConfigError('credential echoed in data', status, code) return data