Skip to content

Commit f6a9e63

Browse files
committed
Rename base_dir to local_files_directory in CLI for consistency + docs cleanup
1 parent 73966f5 commit f6a9e63

File tree

3 files changed

+49
-27
lines changed

3 files changed

+49
-27
lines changed

src/humanloop/cli/__main__.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,9 @@ def common_options(f: Callable) -> Callable:
9999
show_default=False,
100100
)
101101
@click.option(
102-
"--base-dir",
103-
help="Base directory for pulled files (default: humanloop/)",
102+
"--local-files-directory",
103+
"--local-dir",
104+
help="Directory (relative to the current working directory) where Humanloop files are stored locally (default: humanloop/).",
104105
default="humanloop",
105106
type=click.Path(),
106107
)
@@ -151,7 +152,7 @@ def cli(): # Does nothing because used as a group for other subcommands (pull,
151152
"-p",
152153
help="Path in the Humanloop workspace to pull from (file or directory). You can pull an entire directory (e.g. 'my/directory') "
153154
"or a specific file (e.g. 'my/directory/my_prompt.prompt'). When pulling a directory, all files within that directory and its subdirectories will be included. "
154-
"If not specified, pulls from the root of the workspace.",
155+
"If not specified, pulls from the root of the remote workspace.",
155156
default=None,
156157
)
157158
@click.option(
@@ -179,7 +180,7 @@ def pull(
179180
environment: Optional[str],
180181
api_key: Optional[str],
181182
env_file: Optional[str],
182-
base_dir: str,
183+
local_files_directory: str,
183184
base_url: Optional[str],
184185
verbose: bool,
185186
quiet: bool,
@@ -189,13 +190,13 @@ def pull(
189190
\b
190191
This command will:
191192
1. Fetch Prompt and Agent files from your Humanloop workspace
192-
2. Save them to your local filesystem (default directory: humanloop/)
193+
2. Save them to your local filesystem (directory specified by --local-files-directory, default: humanloop/)
193194
3. Maintain the same directory structure as in Humanloop
194195
4. Add appropriate file extensions (.prompt or .agent)
195196
196197
\b
197-
The files will be saved with the following structure:
198-
humanloop/
198+
For example, with the default --local-files-directory=humanloop, files will be saved as:
199+
./humanloop/
199200
├── my_project/
200201
│ ├── prompts/
201202
│ │ ├── my_prompt.prompt
@@ -207,12 +208,17 @@ def pull(
207208
└── prompts/
208209
└── other_prompt.prompt
209210
211+
\b
212+
If you specify --local-files-directory=data/humanloop, files will be saved in ./data/humanloop/ instead.
213+
210214
If a file exists both locally and in the Humanloop workspace, the local file will be overwritten
211215
with the version from Humanloop. Files that only exist locally will not be affected.
212216
213217
Currently only supports syncing Prompt and Agent files. Other file types will be skipped."""
214218
client = get_client(api_key, env_file, base_url)
215-
sync_client = SyncClient(client, base_dir=base_dir, log_level=logging.DEBUG if verbose else logging.WARNING)
219+
sync_client = SyncClient(
220+
client, base_dir=local_files_directory, log_level=logging.DEBUG if verbose else logging.WARNING
221+
)
216222

217223
click.echo(click.style("Pulling files from Humanloop...", fg=INFO_COLOR))
218224
click.echo(click.style(f"Path: {path or '(root)'}", fg=INFO_COLOR))

src/humanloop/client.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,13 @@ def __init__(
131131
opentelemetry_tracer_provider: Optional tracer provider for telemetry
132132
opentelemetry_tracer: Optional tracer for telemetry
133133
use_local_files: Whether to use local files for prompts and agents
134-
local_files_directory: Directory for local files (default: "humanloop")
134+
local_files_directory: Base directory where local prompt and agent files are stored (default: "humanloop").
135+
This is relative to the current working directory. For example:
136+
- "humanloop" will look for files in "./humanloop/"
137+
- "data/humanloop" will look for files in "./data/humanloop/"
138+
When using paths in the API, they must be relative to this directory. For example,
139+
if local_files_directory="humanloop" and you have a file at "humanloop/samples/test.prompt",
140+
you would reference it as "samples/test" in your code.
135141
cache_size: Maximum number of files to cache when use_local_files is True (default: DEFAULT_CACHE_SIZE).
136142
This parameter has no effect if use_local_files is False.
137143
"""
@@ -389,40 +395,50 @@ def agent():
389395
attributes=attributes,
390396
)
391397

392-
def pull(self, environment: str | None = None, path: str | None = None) -> Tuple[List[str], List[str]]:
398+
def pull(self, path: str | None = None, environment: str | None = None) -> Tuple[List[str], List[str]]:
393399
"""Pull Prompt and Agent files from Humanloop to local filesystem.
394400
395401
This method will:
396402
1. Fetch Prompt and Agent files from your Humanloop workspace
397-
2. Save them to the local filesystem using the client's files_directory (set during initialization)
403+
2. Save them to your local filesystem (directory specified by `local_files_directory`, default: "humanloop")
398404
3. Maintain the same directory structure as in Humanloop
399-
4. Add appropriate file extensions (.prompt or .agent)
405+
4. Add appropriate file extensions (`.prompt` or `.agent`)
400406
401407
The path parameter can be used in two ways:
402408
- If it points to a specific file (e.g. "path/to/file.prompt" or "path/to/file.agent"), only that file will be pulled
403-
- If it points to a directory (e.g. "path/to/directory"), all Prompt and Agent files in that directory will be pulled
409+
- If it points to a directory (e.g. "path/to/directory"), all Prompt and Agent files in that directory and its subdirectories will be pulled
404410
- If no path is provided, all Prompt and Agent files will be pulled
405411
406412
The operation will overwrite existing files with the latest version from Humanloop
407413
but will not delete local files that don't exist in the remote workspace.
408414
409-
Currently only supports syncing prompt and agent files. Other file types will be skipped.
415+
Currently only supports syncing Prompt and Agent files. Other file types will be skipped.
410416
411-
The files will be saved with the following structure:
417+
For example, with the default `local_files_directory="humanloop"`, files will be saved as:
412418
```
413-
{files_directory}/
414-
├── prompts/
415-
│ ├── my_prompt.prompt
416-
│ └── nested/
417-
│ └── another_prompt.prompt
418-
└── agents/
419-
└── my_agent.agent
419+
./humanloop/
420+
├── my_project/
421+
│ ├── prompts/
422+
│ │ ├── my_prompt.prompt
423+
│ │ └── nested/
424+
│ │ └── another_prompt.prompt
425+
│ └── agents/
426+
│ └── my_agent.agent
427+
└── another_project/
428+
└── prompts/
429+
└── other_prompt.prompt
420430
```
421431
422-
:param environment: The environment to pull the files from.
432+
If you specify `local_files_directory="data/humanloop"`, files will be saved in ./data/humanloop/ instead.
433+
423434
:param path: Optional path to either a specific file (e.g. "path/to/file.prompt") or a directory (e.g. "path/to/directory").
424435
If not provided, all Prompt and Agent files will be pulled.
425-
:return: List of successfully processed file paths.
436+
:param environment: The environment to pull the files from.
437+
:return: Tuple of two lists:
438+
- First list contains paths of successfully synced files
439+
- Second list contains paths of files that failed to sync (due to API errors, missing content,
440+
or filesystem issues)
441+
:raises HumanloopRuntimeError: If there's an error communicating with the API
426442
"""
427443
return self._sync_client.pull(environment=environment, path=path)
428444

src/humanloop/sync/sync_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ def _normalize_path(self, path: str) -> str:
163163
# Convert to Path object to handle platform-specific separators
164164
path_obj = Path(path)
165165

166-
# Paths are considered absolute on unix-like systems if they start with a forward slash.
167-
# This is because we want to ensure seamless toggling between the local and remote filesystems.
166+
# Reject absolute paths to ensure all paths are relative to base_dir.
167+
# This maintains consistency with the remote filesystem where paths are relative to project root.
168168
if path_obj.is_absolute():
169169
raise HumanloopRuntimeError(
170170
f"Absolute paths are not supported: `{path}`. "
@@ -329,7 +329,7 @@ def pull(self, path: str | None = None, environment: str | None = None) -> Tuple
329329
Returns:
330330
Tuple of two lists:
331331
- First list contains paths of successfully synced files
332-
- Second list contains paths of files that failed to sync
332+
- Second list contains paths of files that failed to sync (e.g. failed to write to disk or missing raw content)
333333
334334
Raises:
335335
HumanloopRuntimeError: If there's an error communicating with the API

0 commit comments

Comments
 (0)