hafnia 0.1.25__py3-none-any.whl → 0.1.27__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.
cli/__main__.py CHANGED
@@ -10,6 +10,7 @@ from cli.config import Config, ConfigSchema
10
10
  def main(ctx: click.Context) -> None:
11
11
  """Hafnia CLI."""
12
12
  ctx.obj = Config()
13
+ ctx.max_content_width = 120
13
14
 
14
15
 
15
16
  @main.command("configure")
@@ -17,9 +18,7 @@ def main(ctx: click.Context) -> None:
17
18
  def configure(cfg: Config) -> None:
18
19
  """Configure Hafnia CLI settings."""
19
20
 
20
- from hafnia.platform.api import get_organization_id
21
-
22
- profile_name = click.prompt("Profile Name", type=str, default="default")
21
+ profile_name = click.prompt("Profile Name", type=str, default=consts.DEFAULT_PROFILE_NAME)
23
22
  profile_name = profile_name.strip()
24
23
  try:
25
24
  cfg.add_profile(profile_name, ConfigSchema(), set_active=True)
@@ -32,12 +31,8 @@ def configure(cfg: Config) -> None:
32
31
  except ValueError as e:
33
32
  click.echo(f"Error: {str(e)}", err=True)
34
33
  return
35
- platform_url = click.prompt("Hafnia Platform URL", type=str, default="https://api.mdi.milestonesys.com")
34
+ platform_url = click.prompt("Hafnia Platform URL", type=str, default=consts.DEFAULT_API_URL)
36
35
  cfg.platform_url = platform_url.strip()
37
- try:
38
- cfg.organization_id = get_organization_id(cfg.get_platform_endpoint("organizations"), cfg.api_key)
39
- except Exception:
40
- raise click.ClickException(consts.ERROR_ORG_ID)
41
36
  cfg.save_config()
42
37
  profile_cmds.profile_show(cfg)
43
38
 
@@ -57,4 +52,4 @@ main.add_command(experiment_cmds.experiment)
57
52
  main.add_command(recipe_cmds.recipe)
58
53
 
59
54
  if __name__ == "__main__":
60
- main()
55
+ main(max_content_width=120)
cli/config.py CHANGED
@@ -6,14 +6,21 @@ from typing import Dict, List, Optional
6
6
  from pydantic import BaseModel, field_validator
7
7
 
8
8
  import cli.consts as consts
9
- from hafnia.log import logger
9
+ from hafnia.log import user_logger
10
+
11
+ PLATFORM_API_MAPPING = {
12
+ "recipes": "/api/v1/recipes",
13
+ "experiments": "/api/v1/experiments",
14
+ "experiment_environments": "/api/v1/experiment-environments",
15
+ "experiment_runs": "/api/v1/experiment-runs",
16
+ "runs": "/api/v1/experiments-runs",
17
+ "datasets": "/api/v1/datasets",
18
+ }
10
19
 
11
20
 
12
21
  class ConfigSchema(BaseModel):
13
- organization_id: str = ""
14
22
  platform_url: str = ""
15
23
  api_key: Optional[str] = None
16
- api_mapping: Optional[Dict[str, str]] = None
17
24
 
18
25
  @field_validator("api_key")
19
26
  def validate_api_key(cls, value: str) -> str:
@@ -61,14 +68,6 @@ class Config:
61
68
  def api_key(self, value: str) -> None:
62
69
  self.config.api_key = value
63
70
 
64
- @property
65
- def organization_id(self) -> str:
66
- return self.config.organization_id
67
-
68
- @organization_id.setter
69
- def organization_id(self, value: str) -> None:
70
- self.config.organization_id = value
71
-
72
71
  @property
73
72
  def platform_url(self) -> str:
74
73
  return self.config.platform_url
@@ -77,7 +76,6 @@ class Config:
77
76
  def platform_url(self, value: str) -> None:
78
77
  base_url = value.rstrip("/")
79
78
  self.config.platform_url = base_url
80
- self.config.api_mapping = self.get_api_mapping(base_url)
81
79
 
82
80
  def __init__(self, config_path: Optional[Path] = None) -> None:
83
81
  self.config_path = self.resolve_config_path(config_path)
@@ -96,27 +94,22 @@ class Config:
96
94
 
97
95
  def add_profile(self, profile_name: str, profile: ConfigSchema, set_active: bool = False) -> None:
98
96
  profile_name = profile_name.strip()
97
+ if profile_name in self.config_data.profiles:
98
+ user_logger.warning(
99
+ f"Profile with name '{profile_name}' already exists, it will be overwritten by the new one."
100
+ )
101
+
99
102
  self.config_data.profiles[profile_name] = profile
100
103
  if set_active:
101
104
  self.config_data.active_profile = profile_name
102
105
  self.save_config()
103
106
 
104
- def get_api_mapping(self, base_url: str) -> Dict:
105
- return {
106
- "organizations": f"{base_url}/api/v1/organizations",
107
- "recipes": f"{base_url}/api/v1/recipes",
108
- "experiments": f"{base_url}/api/v1/experiments",
109
- "experiment_environments": f"{base_url}/api/v1/experiment-environments",
110
- "experiment_runs": f"{base_url}/api/v1/experiment-runs",
111
- "runs": f"{base_url}/api/v1/experiments-runs",
112
- "datasets": f"{base_url}/api/v1/datasets",
113
- }
114
-
115
107
  def get_platform_endpoint(self, method: str) -> str:
116
108
  """Get specific API endpoint"""
117
- if not self.config.api_mapping or method not in self.config.api_mapping:
118
- raise ValueError(f"{method} is not supported.")
119
- return self.config.api_mapping[method]
109
+ if method not in PLATFORM_API_MAPPING:
110
+ raise ValueError(f"'{method}' is not supported.")
111
+ endpoint = self.config.platform_url + PLATFORM_API_MAPPING[method]
112
+ return endpoint
120
113
 
121
114
  def load_config(self) -> ConfigFileSchema:
122
115
  """Load configuration from file."""
@@ -127,7 +120,7 @@ class Config:
127
120
  data = json.load(f)
128
121
  return ConfigFileSchema(**data)
129
122
  except json.JSONDecodeError:
130
- logger.error("Error decoding JSON file.")
123
+ user_logger.error("Error decoding JSON file.")
131
124
  raise ValueError("Failed to parse configuration file")
132
125
 
133
126
  def save_config(self) -> None:
cli/consts.py CHANGED
@@ -1,8 +1,10 @@
1
+ DEFAULT_API_URL = "https://api.mdi.milestonesys.com"
2
+ DEFAULT_PROFILE_NAME = "default"
3
+
1
4
  ERROR_CONFIGURE: str = "Please configure the CLI with `hafnia configure`"
2
5
  ERROR_PROFILE_NOT_EXIST: str = "No active profile configured. Please configure the CLI with `hafnia configure`"
3
6
  ERROR_PROFILE_REMOVE_ACTIVE: str = "Cannot remove active profile. Please switch to another profile first."
4
7
  ERROR_API_KEY_NOT_SET: str = "API key not set. Please configure the CLI with `hafnia configure`."
5
- ERROR_ORG_ID: str = "Failed to fetch organization ID. Verify platform URL and API key."
6
8
  ERROR_CREATE_PROFILE: str = "Failed to create profile. Profile name must be unique and not empty."
7
9
 
8
10
  ERROR_GET_RESOURCE: str = "Failed to get the data from platform. Verify url or api key."
cli/experiment_cmds.py CHANGED
@@ -22,25 +22,18 @@ def experiment() -> None:
22
22
  @click.pass_obj
23
23
  def create(cfg: Config, name: str, source_dir: Path, exec_cmd: str, dataset_name: str, env_name: str) -> None:
24
24
  """Create a new experiment run"""
25
- from hafnia.platform import (
26
- create_experiment,
27
- create_recipe,
28
- get_dataset_id,
29
- get_exp_environment_id,
30
- )
25
+ from hafnia.platform import create_experiment, create_recipe, get_dataset_id, get_exp_environment_id
31
26
 
32
27
  if not source_dir.exists():
33
28
  raise click.ClickException(consts.ERROR_EXPERIMENT_DIR)
34
29
 
35
30
  try:
36
31
  dataset_id = get_dataset_id(dataset_name, cfg.get_platform_endpoint("datasets"), cfg.api_key)
37
- except (IndexError, KeyError):
38
- raise click.ClickException(f"Dataset '{dataset_name}' not found.")
39
32
  except Exception:
40
33
  raise click.ClickException(f"Error retrieving dataset '{dataset_name}'.")
41
34
 
42
35
  try:
43
- recipe_id = create_recipe(source_dir, cfg.get_platform_endpoint("recipes"), cfg.api_key, cfg.organization_id)
36
+ recipe_id = create_recipe(source_dir, cfg.get_platform_endpoint("recipes"), cfg.api_key)
44
37
  except Exception:
45
38
  raise click.ClickException(f"Failed to create recipe from '{source_dir}'")
46
39
 
@@ -51,14 +44,7 @@ def create(cfg: Config, name: str, source_dir: Path, exec_cmd: str, dataset_name
51
44
 
52
45
  try:
53
46
  experiment_id = create_experiment(
54
- name,
55
- dataset_id,
56
- recipe_id,
57
- exec_cmd,
58
- env_id,
59
- cfg.get_platform_endpoint("experiments"),
60
- cfg.api_key,
61
- cfg.organization_id,
47
+ name, dataset_id, recipe_id, exec_cmd, env_id, cfg.get_platform_endpoint("experiments"), cfg.api_key
62
48
  )
63
49
  except Exception:
64
50
  raise click.ClickException(f"Failed to create experiment '{name}'")
cli/profile_cmds.py CHANGED
@@ -3,7 +3,7 @@ from rich.console import Console
3
3
  from rich.table import Table
4
4
 
5
5
  import cli.consts as consts
6
- from cli.config import Config
6
+ from cli.config import Config, ConfigSchema
7
7
 
8
8
 
9
9
  @click.group()
@@ -43,6 +43,21 @@ def profile_use(cfg: Config, profile_name: str) -> None:
43
43
  click.echo(f"{consts.PROFILE_SWITCHED_SUCCESS} {profile_name}")
44
44
 
45
45
 
46
+ @profile.command("create")
47
+ @click.argument("api-key", required=True)
48
+ @click.option("--name", help="Specify profile name", default=consts.DEFAULT_PROFILE_NAME, show_default=True)
49
+ @click.option("--api-url", help="API URL", default=consts.DEFAULT_API_URL, show_default=True)
50
+ @click.option(
51
+ "--activate/--no-activate", help="Activate the created profile after creation", default=True, show_default=True
52
+ )
53
+ @click.pass_obj
54
+ def profile_create(cfg: Config, name: str, api_url: str, api_key: str, activate: bool) -> None:
55
+ """Create a new profile."""
56
+ cfg_profile = ConfigSchema(platform_url=api_url, api_key=api_key)
57
+
58
+ cfg.add_profile(profile_name=name, profile=cfg_profile, set_active=activate)
59
+
60
+
46
61
  @profile.command("rm")
47
62
  @click.argument("profile_name", required=True)
48
63
  @click.pass_obj
@@ -80,7 +95,6 @@ def profile_show(cfg: Config) -> None:
80
95
  table.add_column("Value")
81
96
 
82
97
  table.add_row("API Key", masked_key)
83
- table.add_row("Organization", cfg.organization_id)
84
98
  table.add_row("Platform URL", cfg.platform_url)
85
99
  table.add_row("Config File", cfg.config_path.as_posix())
86
100
  console.print(table)
cli/recipe_cmds.py CHANGED
@@ -19,7 +19,6 @@ def recipe() -> None:
19
19
  def create(source: str, output: str) -> None:
20
20
  """Create HRF from local path"""
21
21
 
22
- from hafnia.platform.builder import validate_recipe
23
22
  from hafnia.utils import archive_dir
24
23
 
25
24
  path_output_zip = Path(output)
@@ -28,7 +27,6 @@ def create(source: str, output: str) -> None:
28
27
 
29
28
  path_source = Path(source)
30
29
  path_output_zip = archive_dir(path_source, path_output_zip)
31
- validate_recipe(path_output_zip)
32
30
 
33
31
 
34
32
  @recipe.command(name="view")
@@ -36,7 +34,7 @@ def create(source: str, output: str) -> None:
36
34
  @click.option("--depth-limit", type=int, default=3, help="Limit the depth of the tree view.", show_default=True)
37
35
  def view(path: str, depth_limit: int) -> None:
38
36
  """View the content of a recipe zip file."""
39
- from hafnia.utils import view_recipe_content
37
+ from hafnia.utils import show_recipe_content
40
38
 
41
39
  path_recipe = Path(path)
42
40
  if not path_recipe.exists():
@@ -44,6 +42,4 @@ def view(path: str, depth_limit: int) -> None:
44
42
  f"Recipe file '{path_recipe}' does not exist. Please provide a valid path. "
45
43
  f"To create a recipe, use the 'hafnia recipe create' command."
46
44
  )
47
-
48
- tree_str = view_recipe_content(path_recipe, depth_limit=depth_limit)
49
- click.echo(tree_str)
45
+ show_recipe_content(path_recipe, depth_limit=depth_limit)
cli/runc_cmds.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import json
2
2
  import subprocess
3
3
  import zipfile
4
- from hashlib import sha256
5
4
  from pathlib import Path
6
5
  from tempfile import TemporaryDirectory
7
6
  from typing import Optional
@@ -9,6 +8,7 @@ from typing import Optional
9
8
  import click
10
9
 
11
10
  from cli.config import Config
11
+ from hafnia.log import sys_logger, user_logger
12
12
 
13
13
 
14
14
  @click.group(name="runc")
@@ -17,15 +17,6 @@ def runc():
17
17
  pass
18
18
 
19
19
 
20
- @runc.command(name="launch")
21
- @click.argument("task", required=True)
22
- def launch(task: str) -> None:
23
- """Launch a job within the image."""
24
- from hafnia.platform.executor import handle_launch
25
-
26
- handle_launch(task)
27
-
28
-
29
20
  @runc.command(name="launch-local")
30
21
  @click.argument("exec_cmd", type=str)
31
22
  @click.option(
@@ -65,9 +56,9 @@ def launch_local(cfg: Config, exec_cmd: str, dataset: str, image_name: str) -> N
65
56
  if not path_state_file.exists():
66
57
  raise click.ClickException("State file does not exist. Please build the image first.")
67
58
  state_dict = json.loads(path_state_file.read_text())
68
- if "mdi_tag" not in state_dict:
69
- raise click.ClickException("mdi_tag not found in state file. Please build the image first.")
70
- image_name = state_dict["mdi_tag"]
59
+ if "image_tag" not in state_dict:
60
+ raise click.ClickException("'image_tag' not found in state file. Please build the image first.")
61
+ image_name = state_dict["image_tag"]
71
62
 
72
63
  docker_cmds = [
73
64
  "docker",
@@ -94,50 +85,60 @@ def launch_local(cfg: Config, exec_cmd: str, dataset: str, image_name: str) -> N
94
85
 
95
86
  @runc.command(name="build")
96
87
  @click.argument("recipe_url")
97
- @click.argument("state_file", default="state.json")
98
- @click.argument("ecr_repository", default="localhost")
99
- @click.argument("image_name", default="recipe")
88
+ @click.option("--state_file", "--st", type=str, default="state.json")
89
+ @click.option("--repo", type=str, default="localhost", help="Docker repository")
100
90
  @click.pass_obj
101
- def build(cfg: Config, recipe_url: str, state_file: str, ecr_repository: str, image_name: str) -> None:
91
+ def build(cfg: Config, recipe_url: str, state_file: str, repo: str) -> None:
102
92
  """Build docker image with a given recipe."""
103
93
  from hafnia.platform.builder import build_image, prepare_recipe
104
94
 
105
95
  with TemporaryDirectory() as temp_dir:
106
- image_info = prepare_recipe(recipe_url, Path(temp_dir), cfg.api_key)
107
- image_info["name"] = image_name
108
- build_image(image_info, ecr_repository, state_file)
96
+ metadata = prepare_recipe(recipe_url, Path(temp_dir), cfg.api_key)
97
+ build_image(metadata, repo, state_file=state_file)
109
98
 
110
99
 
111
100
  @runc.command(name="build-local")
112
101
  @click.argument("recipe")
113
- @click.argument("state_file", default="state.json")
114
- @click.argument("image_name", default="recipe")
115
- def build_local(recipe: str, state_file: str, image_name: str) -> None:
102
+ @click.option("--state_file", "--st", type=str, default="state.json")
103
+ @click.option("--repo", type=str, default="localhost", help="Docker repository")
104
+ def build_local(recipe: Path, state_file: str, repo: str) -> None:
116
105
  """Build recipe from local path as image with prefix - localhost"""
117
-
118
- from hafnia.platform.builder import build_image, validate_recipe
119
- from hafnia.utils import archive_dir
120
-
121
- recipe_zip = Path(recipe)
122
- recipe_created = False
123
- if not recipe_zip.suffix == ".zip" and recipe_zip.is_dir():
124
- recipe_zip = archive_dir(recipe_zip)
125
- recipe_created = True
126
-
127
- validate_recipe(recipe_zip)
128
- click.echo("Recipe successfully validated")
129
- with TemporaryDirectory() as temp_dir:
130
- temp_dir_path = Path(temp_dir)
131
- with zipfile.ZipFile(recipe_zip, "r") as zip_ref:
132
- zip_ref.extractall(temp_dir_path)
133
-
134
- image_info = {
135
- "name": image_name,
136
- "dockerfile": (temp_dir_path / "Dockerfile").as_posix(),
137
- "docker_context": temp_dir_path.as_posix(),
138
- "hash": sha256(recipe_zip.read_bytes()).hexdigest()[:8],
106
+ import shutil
107
+ import uuid
108
+
109
+ import seedir
110
+
111
+ from hafnia.platform.builder import build_image
112
+ from hafnia.utils import filter_recipe_files
113
+
114
+ recipe = Path(recipe)
115
+
116
+ with TemporaryDirectory() as d:
117
+ tmp_dir = Path(d)
118
+ recipe_dir = tmp_dir / "recipe"
119
+ recipe_dir.mkdir(parents=True, exist_ok=True)
120
+
121
+ if recipe.suffix == ".zip":
122
+ user_logger.info("Extracting recipe for processing.")
123
+ with zipfile.ZipFile(recipe.as_posix(), "r") as zip_ref:
124
+ zip_ref.extractall(recipe_dir)
125
+ elif recipe.is_dir():
126
+ for rf in filter_recipe_files(recipe):
127
+ src_path = (recipe / rf).absolute()
128
+ target_path = recipe_dir / rf
129
+ target_path.parent.mkdir(parents=True, exist_ok=True)
130
+ shutil.copyfile(src_path, target_path)
131
+
132
+ user_logger.info(
133
+ seedir.seedir(recipe_dir, sort=True, first="folders", style="emoji", printout=False, depthlimit=2)
134
+ )
135
+
136
+ metadata = {
137
+ "dockerfile": (recipe_dir / "Dockerfile").as_posix(),
138
+ "docker_context": recipe_dir.as_posix(),
139
+ "digest": uuid.uuid4().hex[:8],
139
140
  }
140
- click.echo("Start building image")
141
- build_image(image_info, "localhost", state_file=state_file)
142
- if recipe_created:
143
- recipe_zip.unlink()
141
+
142
+ user_logger.info("Start building image.")
143
+ sys_logger.debug(metadata)
144
+ build_image(metadata, repo, state_file=state_file)
hafnia/data/factory.py CHANGED
@@ -7,7 +7,7 @@ from datasets import Dataset, DatasetDict, load_from_disk
7
7
 
8
8
  from cli.config import Config
9
9
  from hafnia import utils
10
- from hafnia.log import logger
10
+ from hafnia.log import user_logger
11
11
  from hafnia.platform import download_resource, get_dataset_id
12
12
 
13
13
 
@@ -15,7 +15,7 @@ def load_local(dataset_path: Path) -> Union[Dataset, DatasetDict]:
15
15
  """Load a Hugging Face dataset from a local directory path."""
16
16
  if not dataset_path.exists():
17
17
  raise ValueError(f"Can not load dataset, directory does not exist -- {dataset_path}")
18
- logger.info(f"Loading data from {dataset_path.as_posix()}")
18
+ user_logger.info(f"Loading data from {dataset_path.as_posix()}")
19
19
  return load_from_disk(dataset_path.as_posix())
20
20
 
21
21
 
@@ -37,7 +37,7 @@ def download_or_get_dataset_path(
37
37
  dataset_path_sample = dataset_path_base / "sample"
38
38
 
39
39
  if dataset_path_sample.exists() and not force_redownload:
40
- logger.info("Dataset found locally. Set 'force=True' or add `--force` flag with cli to re-download")
40
+ user_logger.info("Dataset found locally. Set 'force=True' or add `--force` flag with cli to re-download")
41
41
  return dataset_path_sample
42
42
 
43
43
  dataset_id = get_dataset_id(dataset_name, endpoint_dataset, api_key)
@@ -13,7 +13,7 @@ from datasets import DatasetDict
13
13
  from pydantic import BaseModel, field_validator
14
14
 
15
15
  from hafnia.data.factory import load_dataset
16
- from hafnia.log import logger
16
+ from hafnia.log import sys_logger, user_logger
17
17
  from hafnia.utils import is_remote_job, now_as_str
18
18
 
19
19
 
@@ -49,7 +49,7 @@ class Entity(BaseModel):
49
49
  try:
50
50
  return float(v)
51
51
  except (ValueError, TypeError) as e:
52
- logger.warning(f"Invalid value '{v}' provided, defaulting to -1.0: {e}")
52
+ user_logger.warning(f"Invalid value '{v}' provided, defaulting to -1.0: {e}")
53
53
  return -1.0
54
54
 
55
55
  @field_validator("ent_type", mode="before")
@@ -165,9 +165,9 @@ class HafniaLogger:
165
165
  existing_params = {}
166
166
  existing_params.update(params)
167
167
  file_path.write_text(json.dumps(existing_params, indent=2))
168
- logger.info(f"Saved parameters to {file_path}")
168
+ user_logger.info(f"Saved parameters to {file_path}")
169
169
  except Exception as e:
170
- logger.error(f"Failed to save parameters to {file_path}: {e}")
170
+ user_logger.error(f"Failed to save parameters to {file_path}: {e}")
171
171
 
172
172
  def log_environment(self):
173
173
  environment_info = {
@@ -201,4 +201,4 @@ class HafniaLogger:
201
201
  next_table = pa.concat_tables([prev, log_batch])
202
202
  pq.write_table(next_table, self.log_file)
203
203
  except Exception as e:
204
- logger.error(f"Failed to flush logs: {e}")
204
+ sys_logger.error(f"Failed to flush logs: {e}")
hafnia/http.py CHANGED
@@ -20,7 +20,7 @@ def fetch(endpoint: str, headers: Dict, params: Optional[Dict] = None) -> Dict:
20
20
  json.JSONDecodeError: On invalid JSON response
21
21
  """
22
22
  params = {} if params is None else params
23
- http = urllib3.PoolManager(timeout=5.0, retries=urllib3.Retry(3))
23
+ http = urllib3.PoolManager(retries=urllib3.Retry(3))
24
24
  try:
25
25
  response = http.request("GET", endpoint, fields=params, headers=headers)
26
26
  if response.status != 200:
@@ -48,7 +48,7 @@ def post(endpoint: str, headers: Dict, data: Union[Path, Dict, bytes], multipart
48
48
  json.JSONDecodeError: If response isn't valid JSON
49
49
  ValueError: If data type is unsupported
50
50
  """
51
- http = urllib3.PoolManager(timeout=5.0, retries=urllib3.Retry(3))
51
+ http = urllib3.PoolManager(retries=urllib3.Retry(3))
52
52
  try:
53
53
  if multipart:
54
54
  # Remove content-type header if present as urllib3 will set it
hafnia/log.py CHANGED
@@ -1,32 +1,23 @@
1
1
  import logging
2
+ import os
2
3
 
3
- from hafnia import __package_name__
4
-
5
-
6
- class CustomFormatter(logging.Formatter):
7
- log_format = "%(asctime)s - %(name)s:%(filename)s @ %(lineno)d - %(levelname)s - %(message)s"
4
+ from rich.logging import RichHandler
8
5
 
9
- def format(self, record):
10
- formatter = logging.Formatter(self.log_format)
11
- return formatter.format(record)
12
-
13
-
14
- def create_logger() -> logging.Logger:
15
- root_logger = logging.getLogger(__package_name__)
16
- if root_logger.hasHandlers():
17
- return root_logger
6
+ from hafnia import __package_name__
18
7
 
19
- ch = logging.StreamHandler()
20
- ch.setLevel(logging.INFO)
21
- ch.setFormatter(CustomFormatter())
8
+ system_handler = RichHandler(rich_tracebacks=True, show_path=True, show_level=True)
9
+ user_handler = RichHandler(rich_tracebacks=False, show_path=False, show_level=False, log_time_format="[%X]")
22
10
 
23
- root_logger.propagate = False
24
- for handler in root_logger.handlers:
25
- root_logger.removeHandler(handler)
26
11
 
27
- root_logger.addHandler(ch)
28
- root_logger.setLevel(logging.INFO)
29
- return root_logger
12
+ def create_logger(handler: RichHandler, name: str, log_level: str) -> logging.Logger:
13
+ logger = logging.getLogger(name)
14
+ if logger.hasHandlers():
15
+ logger.handlers.clear()
16
+ logger.addHandler(handler)
17
+ logger.setLevel(log_level)
18
+ logger.propagate = False
19
+ return logger
30
20
 
31
21
 
32
- logger = create_logger()
22
+ sys_logger = create_logger(system_handler, f"{__package_name__}.system", os.getenv("HAFNIA_LOG", "INFO").upper())
23
+ user_logger = create_logger(user_handler, f"{__package_name__}.user", "DEBUG")
@@ -1,4 +1,3 @@
1
- from hafnia.platform.api import get_organization_id
2
1
  from hafnia.platform.download import (
3
2
  download_resource,
4
3
  download_single_object,
@@ -12,7 +11,6 @@ from hafnia.platform.experiment import (
12
11
  )
13
12
 
14
13
  __all__ = [
15
- "get_organization_id",
16
14
  "get_dataset_id",
17
15
  "create_recipe",
18
16
  "get_exp_environment_id",