hafnia 0.2.4__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. cli/__main__.py +13 -2
  2. cli/config.py +2 -1
  3. cli/consts.py +1 -1
  4. cli/dataset_cmds.py +6 -14
  5. cli/dataset_recipe_cmds.py +78 -0
  6. cli/experiment_cmds.py +226 -43
  7. cli/profile_cmds.py +6 -5
  8. cli/runc_cmds.py +5 -5
  9. cli/trainer_package_cmds.py +65 -0
  10. hafnia/__init__.py +2 -0
  11. hafnia/data/factory.py +1 -2
  12. hafnia/dataset/dataset_helpers.py +0 -12
  13. hafnia/dataset/dataset_names.py +8 -4
  14. hafnia/dataset/dataset_recipe/dataset_recipe.py +119 -33
  15. hafnia/dataset/dataset_recipe/recipe_transforms.py +32 -4
  16. hafnia/dataset/dataset_recipe/recipe_types.py +1 -1
  17. hafnia/dataset/dataset_upload_helper.py +206 -53
  18. hafnia/dataset/hafnia_dataset.py +432 -194
  19. hafnia/dataset/license_types.py +63 -0
  20. hafnia/dataset/operations/dataset_stats.py +260 -3
  21. hafnia/dataset/operations/dataset_transformations.py +325 -4
  22. hafnia/dataset/operations/table_transformations.py +39 -2
  23. hafnia/dataset/primitives/__init__.py +8 -0
  24. hafnia/dataset/primitives/classification.py +1 -1
  25. hafnia/experiment/hafnia_logger.py +112 -0
  26. hafnia/http.py +16 -2
  27. hafnia/platform/__init__.py +9 -3
  28. hafnia/platform/builder.py +12 -10
  29. hafnia/platform/dataset_recipe.py +99 -0
  30. hafnia/platform/datasets.py +44 -6
  31. hafnia/platform/download.py +2 -1
  32. hafnia/platform/experiment.py +51 -56
  33. hafnia/platform/trainer_package.py +57 -0
  34. hafnia/utils.py +64 -13
  35. hafnia/visualizations/image_visualizations.py +3 -3
  36. {hafnia-0.2.4.dist-info → hafnia-0.3.0.dist-info}/METADATA +34 -30
  37. hafnia-0.3.0.dist-info/RECORD +53 -0
  38. cli/recipe_cmds.py +0 -45
  39. hafnia-0.2.4.dist-info/RECORD +0 -49
  40. {hafnia-0.2.4.dist-info → hafnia-0.3.0.dist-info}/WHEEL +0 -0
  41. {hafnia-0.2.4.dist-info → hafnia-0.3.0.dist-info}/entry_points.txt +0 -0
  42. {hafnia-0.2.4.dist-info → hafnia-0.3.0.dist-info}/licenses/LICENSE +0 -0
cli/__main__.py CHANGED
@@ -1,11 +1,21 @@
1
1
  #!/usr/bin/env python
2
2
  import click
3
3
 
4
- from cli import consts, dataset_cmds, experiment_cmds, profile_cmds, recipe_cmds, runc_cmds
4
+ import hafnia
5
+ from cli import (
6
+ consts,
7
+ dataset_cmds,
8
+ dataset_recipe_cmds,
9
+ experiment_cmds,
10
+ profile_cmds,
11
+ runc_cmds,
12
+ trainer_package_cmds,
13
+ )
5
14
  from cli.config import Config, ConfigSchema
6
15
 
7
16
 
8
17
  @click.group()
18
+ @click.version_option(version=hafnia.__version__)
9
19
  @click.pass_context
10
20
  def main(ctx: click.Context) -> None:
11
21
  """Hafnia CLI."""
@@ -45,7 +55,8 @@ main.add_command(profile_cmds.profile)
45
55
  main.add_command(dataset_cmds.dataset)
46
56
  main.add_command(runc_cmds.runc)
47
57
  main.add_command(experiment_cmds.experiment)
48
- main.add_command(recipe_cmds.recipe)
58
+ main.add_command(trainer_package_cmds.trainer_package)
59
+ main.add_command(dataset_recipe_cmds.dataset_recipe)
49
60
 
50
61
  if __name__ == "__main__":
51
62
  main(max_content_width=120)
cli/config.py CHANGED
@@ -9,7 +9,8 @@ import cli.consts as consts
9
9
  from hafnia.log import sys_logger, user_logger
10
10
 
11
11
  PLATFORM_API_MAPPING = {
12
- "recipes": "/api/v1/recipes",
12
+ "trainers": "/api/v1/trainers",
13
+ "dataset_recipes": "/api/v1/dataset-recipes",
13
14
  "experiments": "/api/v1/experiments",
14
15
  "experiment_environments": "/api/v1/experiment-environments",
15
16
  "experiment_runs": "/api/v1/experiment-runs",
cli/consts.py CHANGED
@@ -10,7 +10,7 @@ ERROR_CREATE_PROFILE: str = "Failed to create profile. Profile name must be uniq
10
10
  ERROR_GET_RESOURCE: str = "Failed to get the data from platform. Verify url or api key."
11
11
 
12
12
  ERROR_EXPERIMENT_DIR: str = "Source directory does not exist"
13
- ERROR_RECIPE_FILE_FORMAT: str = "Recipe filename must be a '.zip' file"
13
+ ERROR_TRAINER_PACKAGE_FILE_FORMAT: str = "Trainer package must be a '.zip' file"
14
14
 
15
15
  PROFILE_SWITCHED_SUCCESS: str = "Switched to profile:"
16
16
  PROFILE_REMOVED_SUCCESS: str = "Removed profile:"
cli/dataset_cmds.py CHANGED
@@ -2,12 +2,10 @@ from pathlib import Path
2
2
  from typing import Optional
3
3
 
4
4
  import click
5
- from rich import print as rprint
6
5
 
7
- import cli.consts as consts
6
+ from cli import consts
8
7
  from cli.config import Config
9
8
  from hafnia import utils
10
- from hafnia.platform.datasets import create_rich_table_from_dataset
11
9
 
12
10
 
13
11
  @click.group()
@@ -18,18 +16,12 @@ def dataset():
18
16
 
19
17
  @dataset.command("ls")
20
18
  @click.pass_obj
21
- def dataset_list(cfg: Config) -> None:
19
+ def cmd_list_datasets(cfg: Config) -> None:
22
20
  """List available datasets on Hafnia platform"""
21
+ from hafnia.platform.datasets import get_datasets, pretty_print_datasets
23
22
 
24
- from hafnia.platform.datasets import dataset_list
25
-
26
- try:
27
- datasets = dataset_list(cfg=cfg)
28
- except Exception:
29
- raise click.ClickException(consts.ERROR_GET_RESOURCE)
30
-
31
- table = create_rich_table_from_dataset(datasets)
32
- rprint(table)
23
+ datasets = get_datasets(cfg=cfg)
24
+ pretty_print_datasets(datasets)
33
25
 
34
26
 
35
27
  @dataset.command("download")
@@ -43,7 +35,7 @@ def dataset_list(cfg: Config) -> None:
43
35
  )
44
36
  @click.option("--force", "-f", is_flag=True, default=False, help="Flag to enable force redownload")
45
37
  @click.pass_obj
46
- def data_download(cfg: Config, dataset_name: str, destination: Optional[click.Path], force: bool) -> Path:
38
+ def cmd_dataset_download(cfg: Config, dataset_name: str, destination: Optional[click.Path], force: bool) -> Path:
47
39
  """Download dataset from Hafnia platform"""
48
40
 
49
41
  from hafnia.platform import datasets
@@ -0,0 +1,78 @@
1
+ from pathlib import Path
2
+ from typing import Dict, Optional
3
+
4
+ import click
5
+ from rich import print as rprint
6
+
7
+ from cli.config import Config
8
+
9
+
10
+ @click.group(name="dataset-recipe")
11
+ def dataset_recipe() -> None:
12
+ """Dataset recipe commands"""
13
+ pass
14
+
15
+
16
+ @dataset_recipe.command(name="create")
17
+ @click.argument("path_json_recipe", required=True)
18
+ @click.option(
19
+ "-n",
20
+ "--name",
21
+ type=str,
22
+ default=None,
23
+ show_default=True,
24
+ help="Name of the dataset recipe.",
25
+ )
26
+ @click.pass_obj
27
+ def cmd_get_or_create_dataset_recipe(cfg: Config, path_json_recipe: Path, name: Optional[str]) -> None:
28
+ """Create Hafnia dataset recipe from dataset recipe JSON file"""
29
+ from hafnia.platform.dataset_recipe import get_or_create_dataset_recipe_from_path
30
+
31
+ endpoint = cfg.get_platform_endpoint("dataset_recipes")
32
+ recipe = get_or_create_dataset_recipe_from_path(path_json_recipe, endpoint=endpoint, api_key=cfg.api_key, name=name)
33
+
34
+ if recipe is None:
35
+ raise click.ClickException("Failed to create dataset recipe.")
36
+
37
+ rprint(recipe)
38
+
39
+
40
+ @dataset_recipe.command(name="ls")
41
+ @click.pass_obj
42
+ @click.option("-l", "--limit", type=int, default=None, help="Limit number of listed dataset recipes.")
43
+ def cmd_list_dataset_recipes(cfg: Config, limit: Optional[int]) -> None:
44
+ """List available dataset recipes"""
45
+ from hafnia.platform.dataset_recipe import get_dataset_recipes, pretty_print_dataset_recipes
46
+
47
+ endpoint = cfg.get_platform_endpoint("dataset_recipes")
48
+ recipes = get_dataset_recipes(endpoint=endpoint, api_key=cfg.api_key)
49
+ # Sort recipes to have the most recent first
50
+ recipes = sorted(recipes, key=lambda x: x["created_at"], reverse=True)
51
+ if limit is not None:
52
+ recipes = recipes[:limit]
53
+ pretty_print_dataset_recipes(recipes)
54
+
55
+
56
+ @dataset_recipe.command(name="rm")
57
+ @click.option("-i", "--id", type=str, help="Dataset recipe ID to delete.")
58
+ @click.option("-n", "--name", type=str, help="Dataset recipe name to delete.")
59
+ @click.pass_obj
60
+ def cmd_delete_dataset_recipe(cfg: Config, id: Optional[str], name: Optional[str]) -> Dict:
61
+ """Delete a dataset recipe by ID or name"""
62
+ from hafnia.platform.dataset_recipe import delete_dataset_recipe_by_id, delete_dataset_recipe_by_name
63
+
64
+ endpoint = cfg.get_platform_endpoint("dataset_recipes")
65
+
66
+ if id is not None:
67
+ return delete_dataset_recipe_by_id(id=id, endpoint=endpoint, api_key=cfg.api_key)
68
+ if name is not None:
69
+ dataset_recipe = delete_dataset_recipe_by_name(name=name, endpoint=endpoint, api_key=cfg.api_key)
70
+ if dataset_recipe is None:
71
+ raise click.ClickException(f"Dataset recipe with name '{name}' was not found.")
72
+
73
+ return dataset_recipe
74
+
75
+ raise click.MissingParameter(
76
+ "No dataset recipe identifier have been given. Provide either --id or --name. "
77
+ "Get available recipes with 'hafnia dataset-recipe ls'."
78
+ )
cli/experiment_cmds.py CHANGED
@@ -1,10 +1,16 @@
1
1
  from pathlib import Path
2
+ from typing import Dict, Optional
2
3
 
3
4
  import click
4
- from rich import print as rprint
5
5
 
6
- import cli.consts as consts
7
6
  from cli.config import Config
7
+ from hafnia import utils
8
+ from hafnia.platform.dataset_recipe import (
9
+ get_dataset_recipe_by_id,
10
+ get_dataset_recipe_by_name,
11
+ get_or_create_dataset_recipe_by_dataset_name,
12
+ )
13
+ from hafnia.platform.trainer_package import create_trainer_package
8
14
 
9
15
 
10
16
  @click.group(name="experiment")
@@ -13,48 +19,225 @@ def experiment() -> None:
13
19
  pass
14
20
 
15
21
 
22
+ @experiment.command(name="environments")
23
+ @click.pass_obj
24
+ def cmd_view_environments(cfg: Config):
25
+ """
26
+ View available experiment training environments.
27
+ """
28
+ from hafnia.platform import get_environments, pretty_print_training_environments
29
+
30
+ envs = get_environments(cfg.get_platform_endpoint("experiment_environments"), cfg.api_key)
31
+
32
+ pretty_print_training_environments(envs)
33
+
34
+
35
+ def default_experiment_run_name():
36
+ return f"run-{utils.now_as_str()}"
37
+
38
+
16
39
  @experiment.command(name="create")
17
- @click.argument("name")
18
- @click.argument("source_dir", type=Path)
19
- @click.argument("exec_cmd", type=str)
20
- @click.argument("dataset_name")
21
- @click.argument("env_name")
40
+ @click.option(
41
+ "-n",
42
+ "--name",
43
+ type=str,
44
+ default=default_experiment_run_name(),
45
+ required=False,
46
+ help=f"Name of the experiment. [default: run-[DATETIME] e.g. {default_experiment_run_name()}] ",
47
+ )
48
+ @click.option(
49
+ "-c",
50
+ "--cmd",
51
+ type=str,
52
+ default="python scripts/train.py",
53
+ show_default=True,
54
+ help="Command to run the experiment.",
55
+ )
56
+ @click.option(
57
+ "-p",
58
+ "--trainer-path",
59
+ type=Path,
60
+ default=None,
61
+ help="Path to the trainer package directory. ",
62
+ )
63
+ @click.option(
64
+ "-i",
65
+ "--trainer-id",
66
+ type=str,
67
+ default=None,
68
+ help="ID of the trainer package. View available trainers with 'hafnia trainer ls'",
69
+ )
70
+ @click.option(
71
+ "-d",
72
+ "--dataset",
73
+ type=str,
74
+ default=None,
75
+ required=False,
76
+ help="DatasetIdentifier: Name of the dataset. View Available datasets with 'hafnia dataset ls'",
77
+ )
78
+ @click.option(
79
+ "-r",
80
+ "--dataset-recipe",
81
+ type=str,
82
+ default=None,
83
+ required=False,
84
+ help="DatasetIdentifier: Name of the dataset recipe. View available dataset recipes with 'hafnia dataset-recipe ls'",
85
+ )
86
+ @click.option(
87
+ "--dataset-recipe-id",
88
+ type=str,
89
+ default=None,
90
+ required=False,
91
+ help="DatasetIdentifier: ID of the dataset recipe. View dataset recipes with 'hafnia dataset-recipe ls'",
92
+ )
93
+ @click.option(
94
+ "-e",
95
+ "--environment",
96
+ type=str,
97
+ default="Free Tier",
98
+ show_default=True,
99
+ help="Experiment environment name. View available environments with 'hafnia experiment environments'",
100
+ )
22
101
  @click.pass_obj
23
- def create(cfg: Config, name: str, source_dir: Path, exec_cmd: str, dataset_name: str, env_name: str) -> None:
24
- """Create a new experiment run"""
25
- from hafnia.platform import create_experiment, create_recipe, get_dataset_id, get_exp_environment_id
26
-
27
- if not source_dir.exists():
28
- raise click.ClickException(consts.ERROR_EXPERIMENT_DIR)
29
-
30
- try:
31
- dataset_id = get_dataset_id(dataset_name, cfg.get_platform_endpoint("datasets"), cfg.api_key)
32
- except Exception:
33
- raise click.ClickException(f"Error retrieving dataset '{dataset_name}'.")
34
-
35
- try:
36
- recipe_id = create_recipe(source_dir, cfg.get_platform_endpoint("recipes"), cfg.api_key)
37
- except Exception:
38
- raise click.ClickException(f"Failed to create recipe from '{source_dir}'")
39
-
40
- try:
41
- env_id = get_exp_environment_id(env_name, cfg.get_platform_endpoint("experiment_environments"), cfg.api_key)
42
- except Exception:
43
- raise click.ClickException(f"Environment '{env_name}' not found")
44
-
45
- try:
46
- experiment_id = create_experiment(
47
- name, dataset_id, recipe_id, exec_cmd, env_id, cfg.get_platform_endpoint("experiments"), cfg.api_key
102
+ def cmd_create_experiment(
103
+ cfg: Config,
104
+ name: str,
105
+ cmd: str,
106
+ trainer_path: Path,
107
+ trainer_id: Optional[str],
108
+ dataset: Optional[str],
109
+ dataset_recipe: Optional[str],
110
+ dataset_recipe_id: Optional[str],
111
+ environment: str,
112
+ ) -> None:
113
+ """
114
+ Create and launch a new experiment run
115
+
116
+ Requires one dataset recipe and one trainer package:.
117
+ - One dataset identifier is required either '--dataset', '--dataset-recipe' or '--dataset-recipe-id'.
118
+ - One trainer identifier is required either '--trainer-path' or '--trainer-id'.
119
+
120
+ \b
121
+ Examples:
122
+ # Launch an experiment with a dataset and a trainer package from local path
123
+ hafnia experiment create --dataset mnist --trainer-path ../trainer-classification
124
+
125
+ \b
126
+ # Launch experiment with dataset recipe by name and trainer package by id
127
+ hafnia experiment create --dataset-recipe mnist-recipe --trainer-id 5e454c0d-fdf1-4d1f-9732-771d7fecd28e
128
+
129
+ \b
130
+ # Show available options:
131
+ hafnia experiment create --name "My Experiment" -d mnist --cmd "python scripts/train.py" -e "Free Tier" -p ../trainer-classification
132
+ """
133
+ from hafnia.platform import create_experiment, get_exp_environment_id
134
+
135
+ dataset_recipe_response = get_dataset_recipe_by_dataset_identifies(
136
+ cfg=cfg,
137
+ dataset_name=dataset,
138
+ dataset_recipe_name=dataset_recipe,
139
+ dataset_recipe_id=dataset_recipe_id,
140
+ )
141
+ dataset_recipe_id = dataset_recipe_response["id"]
142
+
143
+ trainer_id = get_trainer_package_by_identifies(
144
+ cfg=cfg,
145
+ trainer_path=trainer_path,
146
+ trainer_id=trainer_id,
147
+ )
148
+
149
+ env_id = get_exp_environment_id(environment, cfg.get_platform_endpoint("experiment_environments"), cfg.api_key)
150
+
151
+ experiment = create_experiment(
152
+ experiment_name=name,
153
+ dataset_recipe_id=dataset_recipe_id,
154
+ trainer_id=trainer_id,
155
+ exec_cmd=cmd,
156
+ environment_id=env_id,
157
+ endpoint=cfg.get_platform_endpoint("experiments"),
158
+ api_key=cfg.api_key,
159
+ )
160
+
161
+ experiment_properties = {
162
+ "ID": experiment.get("id", "N/A"),
163
+ "Name": experiment.get("name", "N/A"),
164
+ "State": experiment.get("state", "N/A"),
165
+ "Trainer Package ID": experiment.get("trainer", "N/A"),
166
+ "Dataset Recipe ID": experiment.get("dataset_recipe", "N/A"),
167
+ "Dataset ID": experiment.get("dataset", "N/A"),
168
+ "Created At": experiment.get("created_at", "N/A"),
169
+ }
170
+ print("Successfully created experiment: ")
171
+ for key, value in experiment_properties.items():
172
+ print(f" {key}: {value}")
173
+
174
+
175
+ def get_dataset_recipe_by_dataset_identifies(
176
+ cfg: Config,
177
+ dataset_name: Optional[str],
178
+ dataset_recipe_name: Optional[str],
179
+ dataset_recipe_id: Optional[str],
180
+ ) -> Dict:
181
+ dataset_identifiers = [dataset_name, dataset_recipe_name, dataset_recipe_id]
182
+ n_dataset_identifies_defined = sum([bool(identifier) for identifier in dataset_identifiers])
183
+
184
+ if n_dataset_identifies_defined > 1:
185
+ raise click.ClickException(
186
+ "Multiple dataset identifiers have been provided. Define only one dataset identifier."
187
+ )
188
+
189
+ dataset_recipe_endpoint = cfg.get_platform_endpoint("dataset_recipes")
190
+ if dataset_name:
191
+ return get_or_create_dataset_recipe_by_dataset_name(dataset_name, dataset_recipe_endpoint, cfg.api_key)
192
+
193
+ if dataset_recipe_name:
194
+ recipe = get_dataset_recipe_by_name(dataset_recipe_name, dataset_recipe_endpoint, cfg.api_key)
195
+ if recipe is None:
196
+ raise click.ClickException(f"Dataset recipe '{dataset_recipe_name}' was not found in the dataset library.")
197
+ return recipe
198
+
199
+ if dataset_recipe_id:
200
+ return get_dataset_recipe_by_id(dataset_recipe_id, dataset_recipe_endpoint, cfg.api_key)
201
+
202
+ raise click.MissingParameter(
203
+ "At least one dataset identifier must be provided. Set one of the following:\n"
204
+ " --dataset <name> -- E.g. '--dataset mnist'\n"
205
+ " --dataset-recipe <name> -- E.g. '--dataset-recipe my-recipe'\n"
206
+ " --dataset-recipe-id <id> -- E.g. '--dataset-recipe-id 5e454c0d-fdf1-4d1f-9732-771d7fecd28e'\n"
207
+ )
208
+
209
+
210
+ def get_trainer_package_by_identifies(
211
+ cfg: Config,
212
+ trainer_path: Optional[Path],
213
+ trainer_id: Optional[str],
214
+ ) -> str:
215
+ from hafnia.platform import get_trainer_package_by_id
216
+
217
+ if trainer_path is not None and trainer_id is not None:
218
+ raise click.ClickException(
219
+ "Multiple trainer identifiers (--trainer-path, --trainer-id) have been provided. Define only one."
48
220
  )
49
- except Exception:
50
- raise click.ClickException(f"Failed to create experiment '{name}'")
51
-
52
- rprint(
53
- {
54
- "dataset_id": dataset_id,
55
- "recipe_id": recipe_id,
56
- "environment_id": env_id,
57
- "experiment_id": experiment_id,
58
- "status": "CREATED",
59
- }
221
+
222
+ if trainer_path is not None:
223
+ trainer_path = Path(trainer_path)
224
+ if not trainer_path.exists():
225
+ raise click.ClickException(f"Trainer package path '{trainer_path}' does not exist.")
226
+ trainer_id = create_trainer_package(
227
+ trainer_path,
228
+ cfg.get_platform_endpoint("trainers"),
229
+ cfg.api_key,
230
+ )
231
+ return trainer_id
232
+
233
+ if trainer_id:
234
+ trainer_response = get_trainer_package_by_id(
235
+ id=trainer_id, endpoint=cfg.get_platform_endpoint("trainers"), api_key=cfg.api_key
236
+ )
237
+ return trainer_response["id"]
238
+
239
+ raise click.MissingParameter(
240
+ "At least one trainer identifier must be provided. Set one of the following:\n"
241
+ " --trainer-path <path> -- E.g. '--trainer-path .'\n"
242
+ " --trainer-id <id> -- E.g. '--trainer-id 5e454c0d-fdf1-4d1f-9732-771d7fecd28e'\n"
60
243
  )
cli/profile_cmds.py CHANGED
@@ -14,7 +14,7 @@ def profile():
14
14
 
15
15
  @profile.command("ls")
16
16
  @click.pass_obj
17
- def profile_ls(cfg: Config) -> None:
17
+ def cmd_profile_ls(cfg: Config) -> None:
18
18
  """List all available profiles."""
19
19
  profiles = cfg.available_profiles
20
20
  if not profiles:
@@ -31,7 +31,7 @@ def profile_ls(cfg: Config) -> None:
31
31
  @profile.command("use")
32
32
  @click.argument("profile_name", required=True)
33
33
  @click.pass_obj
34
- def profile_use(cfg: Config, profile_name: str) -> None:
34
+ def cmd_profile_use(cfg: Config, profile_name: str) -> None:
35
35
  """Switch to a different profile."""
36
36
  if len(cfg.available_profiles) == 0:
37
37
  raise click.ClickException(consts.ERROR_CONFIGURE)
@@ -51,7 +51,7 @@ def profile_use(cfg: Config, profile_name: str) -> None:
51
51
  "--activate/--no-activate", help="Activate the created profile after creation", default=True, show_default=True
52
52
  )
53
53
  @click.pass_obj
54
- def profile_create(cfg: Config, name: str, api_url: str, api_key: str, activate: bool) -> None:
54
+ def cmd_profile_create(cfg: Config, name: str, api_url: str, api_key: str, activate: bool) -> None:
55
55
  """Create a new profile."""
56
56
  cfg_profile = ConfigSchema(platform_url=api_url, api_key=api_key)
57
57
 
@@ -62,7 +62,7 @@ def profile_create(cfg: Config, name: str, api_url: str, api_key: str, activate:
62
62
  @profile.command("rm")
63
63
  @click.argument("profile_name", required=True)
64
64
  @click.pass_obj
65
- def profile_rm(cfg: Config, profile_name: str) -> None:
65
+ def cmd_profile_rm(cfg: Config, profile_name: str) -> None:
66
66
  """Remove a profile."""
67
67
  if len(cfg.available_profiles) == 0:
68
68
  raise click.ClickException(consts.ERROR_CONFIGURE)
@@ -80,7 +80,8 @@ def profile_rm(cfg: Config, profile_name: str) -> None:
80
80
 
81
81
  @profile.command("active")
82
82
  @click.pass_obj
83
- def profile_active(cfg: Config) -> None:
83
+ def cmd_profile_active(cfg: Config) -> None:
84
+ """Show the currently active profile."""
84
85
  try:
85
86
  profile_show(cfg)
86
87
  except Exception as e:
cli/runc_cmds.py CHANGED
@@ -13,7 +13,7 @@ from hafnia.log import sys_logger, user_logger
13
13
 
14
14
  @click.group(name="runc")
15
15
  def runc():
16
- """Experiment management commands"""
16
+ """Creating and running trainer packages locally"""
17
17
  pass
18
18
 
19
19
 
@@ -90,10 +90,10 @@ def launch_local(cfg: Config, exec_cmd: str, dataset: str, image_name: str) -> N
90
90
  @click.pass_obj
91
91
  def build(cfg: Config, recipe_url: str, state_file: str, repo: str) -> None:
92
92
  """Build docker image with a given recipe."""
93
- from hafnia.platform.builder import build_image, prepare_recipe
93
+ from hafnia.platform.builder import build_image, prepare_trainer_package
94
94
 
95
95
  with TemporaryDirectory() as temp_dir:
96
- metadata = prepare_recipe(recipe_url, Path(temp_dir), cfg.api_key)
96
+ metadata = prepare_trainer_package(recipe_url, Path(temp_dir), cfg.api_key)
97
97
  build_image(metadata, repo, state_file=state_file)
98
98
 
99
99
 
@@ -109,7 +109,7 @@ def build_local(recipe: Path, state_file: str, repo: str) -> None:
109
109
  import seedir
110
110
 
111
111
  from hafnia.platform.builder import build_image
112
- from hafnia.utils import filter_recipe_files
112
+ from hafnia.utils import filter_trainer_package_files
113
113
 
114
114
  recipe = Path(recipe)
115
115
 
@@ -123,7 +123,7 @@ def build_local(recipe: Path, state_file: str, repo: str) -> None:
123
123
  with zipfile.ZipFile(recipe.as_posix(), "r") as zip_ref:
124
124
  zip_ref.extractall(recipe_dir)
125
125
  elif recipe.is_dir():
126
- for rf in filter_recipe_files(recipe):
126
+ for rf in filter_trainer_package_files(recipe):
127
127
  src_path = (recipe / rf).absolute()
128
128
  target_path = recipe_dir / rf
129
129
  target_path.parent.mkdir(parents=True, exist_ok=True)
@@ -0,0 +1,65 @@
1
+ from pathlib import Path
2
+ from typing import Optional
3
+
4
+ import click
5
+
6
+ import cli.consts as consts
7
+ from cli.config import Config
8
+
9
+
10
+ @click.group(name="trainer")
11
+ def trainer_package() -> None:
12
+ """Trainer package commands"""
13
+ pass
14
+
15
+
16
+ @trainer_package.command(name="ls")
17
+ @click.pass_obj
18
+ @click.option("-l", "--limit", type=int, default=None, help="Limit number of listed trainer packages.")
19
+ def cmd_list_trainer_packages(cfg: Config, limit: Optional[int]) -> None:
20
+ """List available trainer packages on the platform"""
21
+
22
+ from hafnia.platform.trainer_package import get_trainer_packages, pretty_print_trainer_packages
23
+
24
+ endpoint = cfg.get_platform_endpoint("trainers")
25
+ trainers = get_trainer_packages(endpoint, cfg.api_key)
26
+
27
+ pretty_print_trainer_packages(trainers, limit=limit)
28
+
29
+
30
+ @trainer_package.command(name="create-zip")
31
+ @click.argument("source")
32
+ @click.option(
33
+ "--output",
34
+ type=click.Path(writable=True),
35
+ default="./trainer.zip",
36
+ show_default=True,
37
+ help="Output trainer package path.",
38
+ )
39
+ def cmd_create_trainer_package_zip(source: str, output: str) -> None:
40
+ """Create Hafnia trainer package as zip-file from local path"""
41
+
42
+ from hafnia.utils import archive_dir
43
+
44
+ path_output_zip = Path(output)
45
+ if path_output_zip.suffix != ".zip":
46
+ raise click.ClickException(consts.ERROR_TRAINER_PACKAGE_FILE_FORMAT)
47
+
48
+ path_source = Path(source)
49
+ path_output_zip = archive_dir(path_source, path_output_zip)
50
+
51
+
52
+ @trainer_package.command(name="view-zip")
53
+ @click.option("--path", type=str, default="./trainer.zip", show_default=True, help="Path of trainer.zip.")
54
+ @click.option("--depth-limit", type=int, default=3, help="Limit the depth of the tree view.", show_default=True)
55
+ def cmd_view_trainer_package_zip(path: str, depth_limit: int) -> None:
56
+ """View the content of a trainer package zip file."""
57
+ from hafnia.utils import show_trainer_package_content
58
+
59
+ path_trainer_package = Path(path)
60
+ if not path_trainer_package.exists():
61
+ raise click.ClickException(
62
+ f"Trainer package file '{path_trainer_package}' does not exist. Please provide a valid path. "
63
+ f"To create a trainer package, use the 'hafnia trainer create-zip' command."
64
+ )
65
+ show_trainer_package_content(path_trainer_package, depth_limit=depth_limit)
hafnia/__init__.py CHANGED
@@ -2,3 +2,5 @@ from importlib.metadata import version
2
2
 
3
3
  __package_name__ = "hafnia"
4
4
  __version__ = version(__package_name__)
5
+
6
+ __dataset_format_version__ = "0.0.2" # Hafnia dataset format version
hafnia/data/factory.py CHANGED
@@ -1,4 +1,3 @@
1
- import os
2
1
  from pathlib import Path
3
2
  from typing import Any
4
3
 
@@ -16,7 +15,7 @@ def load_dataset(recipe: Any, force_redownload: bool = False) -> HafniaDataset:
16
15
 
17
16
  def get_dataset_path(recipe: Any, force_redownload: bool = False) -> Path:
18
17
  if utils.is_hafnia_cloud_job():
19
- return Path(os.getenv("MDI_DATASET_DIR", "/opt/ml/input/data/training"))
18
+ return utils.get_dataset_path_in_hafnia_cloud()
20
19
 
21
20
  path_dataset = get_or_create_dataset_path_from_recipe(recipe, force_redownload=force_redownload)
22
21
 
@@ -110,15 +110,3 @@ def split_sizes_from_ratios(n_items: int, split_ratios: Dict[str, float]) -> Dic
110
110
  raise ValueError("Something is wrong. The split sizes do not match the number of items.")
111
111
 
112
112
  return split_sizes
113
-
114
-
115
- def select_evenly_across_list(lst: list, num_samples: int):
116
- if num_samples >= len(lst):
117
- return lst # No need to sample
118
- step = (len(lst) - 1) / (num_samples - 1)
119
- indices = [int(round(step * i)) for i in range(num_samples)] # noqa: RUF046
120
- return [lst[index] for index in indices]
121
-
122
-
123
- def prefix_dict(d: dict, prefix: str) -> dict:
124
- return {f"{prefix}.{k}": v for k, v in d.items()}