Skip to content

Commit d5e2de1

Browse files
committed
feat: improve local file handling in overload
- Make local file access more explicit by throwing errors instead of silent fallbacks - Add proper error handling for file not found and IO errors - Improve version/environment handling with clear warnings - Update docstrings to better document behavior This change makes the behavior more predictable when use_local_files=True: - Throws FileNotFoundError/IOError if local file can't be accessed - Warns and uses remote when version_id/environment is specified - No more silent fallbacks to remote API
1 parent de5c767 commit d5e2de1

File tree

2 files changed

+59
-23
lines changed

2 files changed

+59
-23
lines changed

src/humanloop/overload.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,19 @@ def overload_with_local_files(
143143
) -> Union[PromptsClient, AgentsClient]:
144144
"""Overload call and log methods to handle local files when use_local_files is True.
145145
146+
When use_local_files is True:
147+
- If only path is specified (no version_id or environment), attempts to use local file
148+
- If local file is not found or cannot be read, raises an error
149+
- If version_id or environment is specified, uses remote version with a warning
150+
146151
Args:
147152
client: The client to overload (PromptsClient or AgentsClient)
153+
sync_client: The sync client for handling local files
148154
use_local_files: Whether to use local files
155+
156+
Raises:
157+
FileNotFoundError: If use_local_files is True and local file is not found
158+
IOError: If use_local_files is True and local file cannot be read
149159
"""
150160
original_call = client._call if hasattr(client, '_call') else client.call
151161
original_log = client._log if hasattr(client, '_log') else client.log
@@ -154,11 +164,24 @@ def overload_with_local_files(
154164
def _overload(self, function_name: str, **kwargs) -> PromptCallResponse:
155165
# Handle local files if enabled
156166
if use_local_files and "path" in kwargs:
157-
# Normalize the path and get file content
158-
normalized_path = sync_client._normalize_path(kwargs["path"])
159-
file_content = sync_client.get_file_content(normalized_path, file_type)
160-
if file_content is not None:
167+
# Check if version_id or environment is specified
168+
has_version_info = "version_id" in kwargs or "environment" in kwargs
169+
170+
if has_version_info:
171+
logger.warning(
172+
"Ignoring local file for %s as version_id or environment was specified. "
173+
"Using remote version instead.",
174+
kwargs["path"]
175+
)
176+
else:
177+
# Only use local file if no version info is specified
178+
normalized_path = sync_client._normalize_path(kwargs["path"])
179+
try:
180+
file_content = sync_client.get_file_content(normalized_path, file_type)
161181
kwargs[file_type] = file_content
182+
except (FileNotFoundError, IOError) as e:
183+
# Re-raise with more context
184+
raise type(e)(f"Failed to use local file for {kwargs['path']}: {str(e)}")
162185

163186
try:
164187
if function_name == "call":

src/humanloop/sync/sync_client.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -54,31 +54,40 @@ def __init__(
5454
# Initialize metadata handler
5555
self.metadata = MetadataHandler(self.base_dir)
5656

57-
def _get_file_content_impl(self, path: str, file_type: FileType) -> Optional[str]:
57+
def _get_file_content_impl(self, path: str, file_type: FileType) -> str:
5858
"""Implementation of get_file_content without the cache decorator.
5959
6060
This is the actual implementation that gets wrapped by lru_cache.
61+
62+
Args:
63+
path: The normalized path to the file (without extension)
64+
file_type: The type of file (prompt or agent)
65+
66+
Returns:
67+
The file content
68+
69+
Raises:
70+
FileNotFoundError: If the file doesn't exist
71+
IOError: If there's an error reading the file
6172
"""
62-
try:
63-
# Construct path to local file
64-
local_path = self.base_dir / path
65-
# Add appropriate extension
66-
local_path = local_path.parent / f"{local_path.stem}.{file_type}"
73+
# Construct path to local file
74+
local_path = self.base_dir / path
75+
# Add appropriate extension
76+
local_path = local_path.parent / f"{local_path.stem}.{file_type}"
77+
78+
if not local_path.exists():
79+
raise FileNotFoundError(f"Local file not found: {local_path}")
6780

68-
if local_path.exists():
69-
# Read the file content
70-
with open(local_path) as f:
71-
file_content = f.read()
72-
logger.debug(f"Using local file content from {local_path}")
73-
return file_content
74-
else:
75-
logger.warning(f"Local file not found: {local_path}, falling back to API")
76-
return None
81+
try:
82+
# Read the file content
83+
with open(local_path) as f:
84+
file_content = f.read()
85+
logger.debug(f"Using local file content from {local_path}")
86+
return file_content
7787
except Exception as e:
78-
logger.error(f"Error reading local file: {e}, falling back to API")
79-
return None
88+
raise IOError(f"Error reading local file {local_path}: {str(e)}")
8089

81-
def get_file_content(self, path: str, file_type: FileType) -> Optional[str]:
90+
def get_file_content(self, path: str, file_type: FileType) -> str:
8291
"""Get the content of a file from cache or filesystem.
8392
8493
This method uses an LRU cache to store file contents. When the cache is full,
@@ -89,7 +98,11 @@ def get_file_content(self, path: str, file_type: FileType) -> Optional[str]:
8998
file_type: The type of file (prompt or agent)
9099
91100
Returns:
92-
The file content if found, None otherwise
101+
The file content
102+
103+
Raises:
104+
FileNotFoundError: If the file doesn't exist
105+
IOError: If there's an error reading the file
93106
"""
94107
return self._get_file_content_impl(path, file_type)
95108

0 commit comments

Comments
 (0)