Skip to content

Commit 5e51de0

Browse files
committed
Clean up error handling in sync client
1 parent a6964d8 commit 5e51de0

File tree

1 file changed

+69
-35
lines changed

1 file changed

+69
-35
lines changed

src/humanloop/sync/sync_client.py

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
from pathlib import Path
3-
from typing import List, TYPE_CHECKING, Optional
3+
from typing import List, Tuple, TYPE_CHECKING
44
from functools import lru_cache
55
from humanloop.types import FileType
66
import time
@@ -189,24 +189,49 @@ def _save_serialized_file(self, serialized_content: str, file_path: str, file_ty
189189
logger.error(f"Failed to sync {file_type} {file_path}: {str(e)}")
190190
raise
191191

192-
def _pull_file(self, path: str, environment: str | None = None) -> None:
193-
"""Pull a specific file from Humanloop to local filesystem."""
194-
file = self.client.files.retrieve_by_path(
195-
path=path,
196-
environment=environment,
197-
include_raw_file_content=True
198-
)
192+
def _pull_file(self, path: str, environment: str | None = None) -> bool:
193+
"""Pull a specific file from Humanloop to local filesystem.
194+
195+
Returns:
196+
True if the file was successfully pulled, False otherwise
197+
198+
Raises:
199+
HumanloopRuntimeError: If there's an error communicating with the API
200+
"""
201+
try:
202+
file = self.client.files.retrieve_by_path(
203+
path=path,
204+
environment=environment,
205+
include_raw_file_content=True
206+
)
207+
except Exception as e:
208+
logger.error(f"Failed to pull file {path}: {format_api_error(e)}")
209+
return False
199210

200211
if file.type not in ["prompt", "agent"]:
201212
raise ValueError(f"Unsupported file type: {file.type}")
202213

203-
self._save_serialized_file(file.raw_file_content, file.path, file.type)
214+
try:
215+
self._save_serialized_file(file.raw_file_content, file.path, file.type)
216+
return True
217+
except Exception as e:
218+
logger.error(f"Failed to save file {path}: {str(e)}")
219+
return False
204220

205221
def _pull_directory(self,
206222
path: str | None = None,
207223
environment: str | None = None,
208-
) -> List[str]:
209-
"""Sync Prompt and Agent files from Humanloop to local filesystem."""
224+
) -> Tuple[List[str], List[str]]:
225+
"""Sync Prompt and Agent files from Humanloop to local filesystem.
226+
227+
Returns:
228+
Tuple of two lists:
229+
- First list contains paths of successfully synced files
230+
- Second list contains paths of files that failed to sync
231+
232+
Raises:
233+
HumanloopRuntimeError: If there's an error communicating with the API
234+
"""
210235
successful_files = []
211236
failed_files = []
212237
page = 1
@@ -248,21 +273,21 @@ def _pull_directory(self,
248273
successful_files.append(file.path)
249274
except Exception as e:
250275
failed_files.append(file.path)
251-
logger.error(f"Task failed for {file.path}: {str(e)}")
276+
logger.error(f"Failed to save {file.path}: {str(e)}")
252277

253278
page += 1
254279
except Exception as e:
255280
formatted_error = format_api_error(e)
256-
raise HumanloopRuntimeError(f"Failed to pull files: {formatted_error}")
281+
raise HumanloopRuntimeError(f"Failed to fetch page {page}: {formatted_error}")
257282

258283
if successful_files:
259284
logger.info(f"Successfully pulled {len(successful_files)} files")
260285
if failed_files:
261286
logger.warning(f"Failed to pull {len(failed_files)} files")
262287

263-
return successful_files
288+
return successful_files, failed_files
264289

265-
def pull(self, path: str | None = None, environment: str | None = None) -> List[str]:
290+
def pull(self, path: str | None = None, environment: str | None = None) -> Tuple[List[str], List[str]]:
266291
"""Pull files from Humanloop to local filesystem.
267292
268293
If the path ends with .prompt or .agent, pulls that specific file.
@@ -274,31 +299,40 @@ def pull(self, path: str | None = None, environment: str | None = None) -> List[
274299
environment: The environment to pull from
275300
276301
Returns:
277-
List of successfully processed file paths
302+
Tuple of two lists:
303+
- First list contains paths of successfully synced files
304+
- Second list contains paths of files that failed to sync
305+
306+
Raises:
307+
HumanloopRuntimeError: If there's an error communicating with the API
278308
"""
279309
start_time = time.time()
280310
normalized_path = self._normalize_path(path) if path else None
281311

282312
logger.info(f"Starting pull operation: path={normalized_path or '(root)'}, environment={environment or '(default)'}")
283313

284-
if path is None:
285-
# Pull all files from the root
286-
logger.debug("Pulling all files from root")
287-
successful_files = self._pull_directory(None, environment)
288-
failed_files = [] # Failed files are already logged in _pull_directory
289-
else:
290-
if self.is_file(path.strip()):
291-
logger.debug(f"Pulling specific file: {normalized_path}")
292-
self._pull_file(normalized_path, environment)
293-
successful_files = [path]
294-
failed_files = []
314+
try:
315+
if path is None:
316+
# Pull all files from the root
317+
logger.debug("Pulling all files from root")
318+
successful_files, failed_files = self._pull_directory(None, environment)
295319
else:
296-
logger.debug(f"Pulling directory: {normalized_path}")
297-
successful_files = self._pull_directory(normalized_path, environment)
298-
failed_files = [] # Failed files are already logged in _pull_directory
299-
300-
duration_ms = int((time.time() - start_time) * 1000)
301-
logger.info(f"Pull completed in {duration_ms}ms: {len(successful_files)} files succeeded")
302-
303-
return successful_files
320+
if self.is_file(path.strip()):
321+
logger.debug(f"Pulling specific file: {normalized_path}")
322+
if self._pull_file(normalized_path, environment):
323+
successful_files = [path]
324+
failed_files = []
325+
else:
326+
successful_files = []
327+
failed_files = [path]
328+
else:
329+
logger.debug(f"Pulling directory: {normalized_path}")
330+
successful_files, failed_files = self._pull_directory(normalized_path, environment)
331+
332+
duration_ms = int((time.time() - start_time) * 1000)
333+
logger.info(f"Pull completed in {duration_ms}ms: {len(successful_files)} files succeeded")
334+
335+
return successful_files, failed_files
336+
except Exception as e:
337+
raise HumanloopRuntimeError(f"Pull operation failed: {str(e)}")
304338

0 commit comments

Comments
 (0)