22import logging
33from pathlib import Path
44import concurrent .futures
5- from typing import List , TYPE_CHECKING , Union
5+ from typing import List , TYPE_CHECKING , Union , cast
66
7- from humanloop .types import FileType , PromptResponse , AgentResponse
7+ from humanloop .types import FileType , PromptResponse , AgentResponse , ToolResponse , DatasetResponse , EvaluatorResponse , FlowResponse
88from humanloop .core .api_error import ApiError
99
1010if TYPE_CHECKING :
1919if not logger .hasHandlers ():
2020 logger .addHandler (console_handler )
2121
22+
2223def _save_serialized_file (serialized_content : str , file_path : str , file_type : FileType ) -> None :
2324 """Save serialized file to local filesystem.
24-
25+
2526 :param serialized_content: The content to save
2627 :param file_path: The path where to save the file
2728 :param file_type: The type of file (prompt or agent)
@@ -31,10 +32,10 @@ def _save_serialized_file(serialized_content: str, file_path: str, file_type: Fi
3132 full_path = Path ("humanloop" ) / file_path
3233 # Create directory if it doesn't exist
3334 full_path .parent .mkdir (parents = True , exist_ok = True )
34-
35+
3536 # Add file type extension
3637 new_path = full_path .parent / f"{ full_path .stem } .{ file_type } "
37-
38+
3839 # Write content to file
3940 with open (new_path , "w" ) as f :
4041 f .write (serialized_content )
@@ -43,15 +44,27 @@ def _save_serialized_file(serialized_content: str, file_path: str, file_type: Fi
4344 logger .error (f"Failed to sync { file_type } { file_path } : { str (e )} " )
4445 raise
4546
46- def _process_file (client : "BaseHumanloop" , file : Union [PromptResponse , AgentResponse ]) -> None :
47+
48+ def _process_file (client : "BaseHumanloop" , file : Union [PromptResponse , AgentResponse , ToolResponse , DatasetResponse , EvaluatorResponse , FlowResponse ]) -> None :
4749 """Process a single file by serializing and saving it.
48-
50+
4951 Currently only supports prompt and agent files. Other file types will be skipped.
50-
52+
5153 :param client: Humanloop client instance
5254 :param file: The file to process (must be a PromptResponse or AgentResponse)
5355 """
5456 try :
57+ # Skip if not a prompt or agent
58+ if file .type not in ["prompt" , "agent" ]:
59+ logger .warning (f"Skipping unsupported file type: { file .type } " )
60+ return
61+
62+ # Cast to the correct type for type checking
63+ if file .type == "prompt" :
64+ file = cast (PromptResponse , file )
65+ elif file .type == "agent" :
66+ file = cast (AgentResponse , file )
67+
5568 # Serialize the file based on its type
5669 try :
5770 if file .type == "prompt" :
@@ -70,48 +83,46 @@ def _process_file(client: "BaseHumanloop", file: Union[PromptResponse, AgentResp
7083 except Exception as e :
7184 logger .error (f"Failed to serialize { file .type } { file .id } : { str (e )} " )
7285 raise
73-
86+
7487 # Save to local filesystem
7588 _save_serialized_file (serialized , file .path , file .type )
76-
89+
7790 except Exception as e :
7891 logger .error (f"Error processing file { file .path } : { str (e )} " )
7992 raise
8093
94+
8195def sync (client : "BaseHumanloop" ) -> List [str ]:
8296 """Sync prompt and agent files from Humanloop to local filesystem.
83-
97+
8498 :param client: Humanloop client instance
8599 :return: List of successfully processed file paths
86100 """
87101 successful_files = []
88102 failed_files = []
89-
103+
90104 # Create a thread pool for processing files
91105 with concurrent .futures .ThreadPoolExecutor (max_workers = 5 ) as executor :
92106 futures = []
93107 page = 1
94-
108+
95109 while True :
96110 try :
97- response = client .files .list_files (
98- type = ["prompt" , "agent" ],
99- page = page
100- )
101-
111+ response = client .files .list_files (type = ["prompt" , "agent" ], page = page )
112+
102113 if len (response .records ) == 0 :
103114 break
104-
115+
105116 # Submit each file for processing
106117 for file in response .records :
107118 future = executor .submit (_process_file , client , file )
108119 futures .append ((file .path , future ))
109-
120+
110121 page += 1
111122 except Exception as e :
112123 logger .error (f"Failed to fetch page { page } : { str (e )} " )
113124 break
114-
125+
115126 # Wait for all tasks to complete
116127 for file_path , future in futures :
117128 try :
@@ -120,11 +131,11 @@ def sync(client: "BaseHumanloop") -> List[str]:
120131 except Exception as e :
121132 failed_files .append (file_path )
122133 logger .error (f"Task failed for { file_path } : { str (e )} " )
123-
134+
124135 # Log summary
125136 if successful_files :
126137 logger .info (f"\n Synced { len (successful_files )} files" )
127138 if failed_files :
128139 logger .error (f"Failed to sync { len (failed_files )} files" )
129-
130- return successful_files
140+
141+ return successful_files
0 commit comments