11import logging
22from pathlib import Path
3- from typing import List , TYPE_CHECKING , Optional
3+ from typing import List , Tuple , TYPE_CHECKING
44from functools import lru_cache
55from humanloop .types import FileType
66import 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