Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/source/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,19 @@ This prevents partial copies from leaving broken state.

See also the [Python API](utilities.md#copying-experiments) for programmatic use.

## Workspaces Management

You can list the workspaces configured in your `settings.yaml`:

```bash
experimaestro workspaces list [OPTIONS]
```

```
Options:
`-v`, `--verbose` Output all workspace configuration parameters (e.g. ssh, env, folders) |
```

## Cleaning Up Orphans

Check for tasks that are not part of any experimental plan in the given
Expand Down
5 changes: 5 additions & 0 deletions src/experimaestro/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,11 @@ def get_command(self, ctx, name):

cli.add_command(install_skill_cli)

# Import and add workspaces commands
from .workspaces import workspaces as workspaces_cli # noqa: E402

cli.add_command(workspaces_cli)


@cli.group()
def migrate():
Expand Down
52 changes: 52 additions & 0 deletions src/experimaestro/cli/workspaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import click
from termcolor import cprint
from experimaestro.settings import get_settings
from dataclasses import asdict
import yaml

@click.group()
def workspaces():
"""Manage workspaces defined in settings.yaml"""
pass

@workspaces.command(name="list")
@click.option("-v", "--verbose", is_flag=True, help="Display all parameters for each workspace")
def list_workspaces(verbose):
"""List workspaces configured in settings.yaml"""
settings = get_settings()

if not settings.workspaces:
cprint("No workspaces configured in settings.yaml", "yellow")
return

cprint("Experimaestro workspaces:", "white", attrs=["bold"])
for ws in settings.workspaces:
tags = []
if ws.is_remote:
tags.append(f"remote: {ws.ssh.host}")

tags_str = f" [{', '.join(tags)}]" if tags else ""
cprint(f"- {ws.id}: {ws.path}{tags_str}", "cyan")

if verbose:
# Print a structured representation of all workspace parameters
ws_dict = asdict(ws)
# Remove redundant ID and path from the verbose dump as they are already printed
ws_dict.pop("id", None)
ws_dict.pop("path", None)
# path is a Path object, which might not serialize cleanly with simple yaml dump in older PyYAML, but asdict() might keep it as Path.
# We convert Path to str just to be safe.
def sanitize(d):
for k, v in list(d.items()):
if hasattr(v, '__fspath__'):
d[k] = str(v)
elif isinstance(v, dict):
sanitize(v)
elif isinstance(v, list):
for i, item in enumerate(v):
if hasattr(item, '__fspath__'):
v[i] = str(item)
elif isinstance(item, dict):
sanitize(item)
sanitize(ws_dict)
cprint(yaml.dump(ws_dict, sort_keys=False, default_flow_style=False), "white")
5 changes: 3 additions & 2 deletions src/experimaestro/experiments/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,9 @@ def converter(value: Any) -> Any:
for combination in grid_combinations:
cfg_tags = {}
new_cfg = copy.deepcopy(base_cfg)
for path, value in zip(param_paths, combination):
cfg_tags[path] = value
for i, (path, value) in enumerate(zip(param_paths, combination)):
if len(value_options[i]) > 1:
cfg_tags[path] = value
set_nested_attr(new_cfg, path, value)

# Convert any remaining single-value GenericParams to scalars
Expand Down
14 changes: 12 additions & 2 deletions src/experimaestro/tests/test_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,17 @@ def test_unique_value_in_tags():

configs, tags = generate_grid(cfg)
assert len(configs) == 1
assert tags[0]["lr"] == 0.1
assert tags[0]["batch_size"] == 32
assert tags[0] == {}

# Test that multi-value params are tagged while single-value params in the same grid are not
cfg_multi = MyConfig(id="test", lr=[0.1, 0.01], batch_size=32)
cfg_multi.lr = GenericParams.from_any(cfg_multi.lr)
cfg_multi.batch_size = GenericParams.from_any(cfg_multi.batch_size)

configs_multi, tags_multi = generate_grid(cfg_multi)
assert len(configs_multi) == 2
assert tags_multi[0] == {"lr": 0.1}
assert tags_multi[1] == {"lr": 0.01}



17 changes: 16 additions & 1 deletion src/experimaestro/tests/test_grid_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,22 @@ def test_unique_value_in_tags_from_validation():
configs, tags = generate_grid(cfg)

assert len(configs) == 1
assert tags[0] == {"lr": 0.05, "sub.value": 10}
assert tags[0] == {}

# Test with multi-value param
data_multi = {
"id": "test",
"lr": [0.05, 0.1],
"sub": {"value": 10}
}

cfg_multi = validate_attrs(MainConfig, data_multi)
configs_multi, tags_multi = generate_grid(cfg_multi)

assert len(configs_multi) == 2
assert tags_multi[0] == {"lr": 0.05}
assert tags_multi[1] == {"lr": 0.1}



def test_unrecognized_key_in_validation():
Expand Down
Loading