|
6 | 6 | from .metadata_handler import MetadataHandler |
7 | 7 | import time |
8 | 8 | from humanloop.error import HumanloopRuntimeError |
| 9 | +import json |
9 | 10 |
|
10 | 11 | if TYPE_CHECKING: |
11 | 12 | from humanloop.base_client import BaseHumanloop |
|
22 | 23 | # Default cache size for file content caching |
23 | 24 | DEFAULT_CACHE_SIZE = 100 |
24 | 25 |
|
| 26 | +def format_api_error(error: Exception) -> str: |
| 27 | + """Format API error messages to be more user-friendly.""" |
| 28 | + error_msg = str(error) |
| 29 | + if "status_code" not in error_msg or "body" not in error_msg: |
| 30 | + return error_msg |
| 31 | + |
| 32 | + try: |
| 33 | + # Extract the body part and parse as JSON |
| 34 | + body_str = error_msg.split("body: ")[1] |
| 35 | + # Convert Python dict string to valid JSON by replacing single quotes with double quotes |
| 36 | + body_str = body_str.replace("'", '"') |
| 37 | + body = json.loads(body_str) |
| 38 | + |
| 39 | + # Get the detail from the body |
| 40 | + detail = body.get("detail", {}) |
| 41 | + |
| 42 | + # Prefer description, fall back to msg |
| 43 | + return detail.get("description") or detail.get("msg") or error_msg |
| 44 | + except Exception as e: |
| 45 | + logger.debug(f"Failed to parse error message: {str(e)}") |
| 46 | + return error_msg |
| 47 | + |
25 | 48 | class SyncClient: |
26 | 49 | """Client for managing synchronization between local filesystem and Humanloop. |
27 | 50 | |
@@ -260,7 +283,8 @@ def _pull_directory(self, |
260 | 283 |
|
261 | 284 | page += 1 |
262 | 285 | except Exception as e: |
263 | | - raise HumanloopRuntimeError(f"Failed to fetch page {page}: {str(e)}") |
| 286 | + formatted_error = format_api_error(e) |
| 287 | + raise HumanloopRuntimeError(f"Failed to pull files: {formatted_error}") |
264 | 288 |
|
265 | 289 | # Log summary only if we have results |
266 | 290 | if successful_files or failed_files: |
|
0 commit comments