liblaf-cherries 0.0.14__py3-none-any.whl → 0.1.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 (41) hide show
  1. liblaf/cherries/__init__.pyi +71 -44
  2. liblaf/cherries/_run.py +38 -26
  3. liblaf/cherries/_version.py +2 -2
  4. liblaf/cherries/config/__init__.pyi +23 -0
  5. liblaf/cherries/config/_asset.py +72 -0
  6. liblaf/cherries/config/_config.py +6 -0
  7. liblaf/cherries/integration/__init__.pyi +66 -0
  8. liblaf/cherries/integration/_abc.py +144 -0
  9. liblaf/cherries/integration/_exp.py +97 -0
  10. liblaf/cherries/integration/comet.py +142 -0
  11. liblaf/cherries/integration/dvc.py +51 -0
  12. liblaf/cherries/integration/git.py +44 -0
  13. liblaf/cherries/integration/logging.py +45 -0
  14. liblaf/cherries/meta/__init__.py +3 -0
  15. liblaf/cherries/{info → meta}/__init__.pyi +2 -2
  16. liblaf/cherries/meta/_name.py +25 -0
  17. liblaf/cherries/pathutils/__init__.pyi +2 -2
  18. liblaf/cherries/pathutils/_convert.py +0 -4
  19. liblaf/cherries/pathutils/_path.py +6 -6
  20. liblaf/cherries/pathutils/_special.py +2 -2
  21. liblaf/cherries/presets/__init__.pyi +3 -1
  22. liblaf/cherries/presets/_default.py +37 -23
  23. liblaf/cherries/presets/_playground.py +11 -0
  24. liblaf/cherries/presets/typed.py +5 -0
  25. {liblaf_cherries-0.0.14.dist-info → liblaf_cherries-0.1.0.dist-info}/METADATA +5 -5
  26. liblaf_cherries-0.1.0.dist-info/RECORD +40 -0
  27. liblaf/cherries/_config.py +0 -22
  28. liblaf/cherries/info/_name.py +0 -30
  29. liblaf/cherries/plugin/__init__.pyi +0 -77
  30. liblaf/cherries/plugin/_abc.py +0 -131
  31. liblaf/cherries/plugin/_dvc.py +0 -63
  32. liblaf/cherries/plugin/_git.py +0 -46
  33. liblaf/cherries/plugin/_logging.py +0 -31
  34. liblaf/cherries/plugin/_mlflow.py +0 -88
  35. liblaf/cherries/plugin/_run.py +0 -105
  36. liblaf_cherries-0.0.14.dist-info/RECORD +0 -35
  37. /liblaf/cherries/{info → config}/__init__.py +0 -0
  38. /liblaf/cherries/{plugin → integration}/__init__.py +0 -0
  39. /liblaf/cherries/{info → meta}/_git.py +0 -0
  40. {liblaf_cherries-0.0.14.dist-info → liblaf_cherries-0.1.0.dist-info}/WHEEL +0 -0
  41. {liblaf_cherries-0.0.14.dist-info → liblaf_cherries-0.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,89 +1,116 @@
1
- from . import info, pathutils, plugin, presets, utils
2
- from ._config import BaseConfig
1
+ from . import config, integration, meta, pathutils, presets, utils
3
2
  from ._run import end, run, start
3
+ from .config import (
4
+ AssetKind,
5
+ BaseConfig,
6
+ MetaAsset,
7
+ PathProvider,
8
+ get_assets,
9
+ get_inputs,
10
+ get_outputs,
11
+ input, # noqa: A004
12
+ output,
13
+ )
14
+ from .integration import (
15
+ AddTag,
16
+ AddTags,
17
+ End,
18
+ Experiment,
19
+ LogAsset,
20
+ LogCode,
21
+ LogMetric,
22
+ LogMetrics,
23
+ LogOther,
24
+ LogOthers,
25
+ LogParam,
26
+ LogParams,
27
+ Plugin,
28
+ Start,
29
+ add_tag,
30
+ add_tags,
31
+ log_asset,
32
+ log_code,
33
+ log_metric,
34
+ log_metrics,
35
+ log_other,
36
+ log_others,
37
+ log_param,
38
+ log_params,
39
+ )
4
40
  from .pathutils import (
5
41
  as_os_path,
6
42
  as_path,
7
43
  as_posix,
8
- config,
9
44
  data,
10
45
  entrypoint,
46
+ exp_dir,
11
47
  git_root,
12
48
  git_root_safe,
13
49
  inputs,
14
50
  outputs,
15
51
  params,
16
52
  path,
17
- run_dir,
18
53
  src,
19
54
  )
20
- from .plugin import (
21
- End,
22
- LogArtifact,
23
- LogArtifacts,
24
- LoggingEnd,
25
- LoggingStart,
26
- LogMetric,
27
- LogParam,
28
- Plugin,
29
- Run,
30
- RunStatus,
31
- Start,
32
- log_artifact,
33
- log_artifacts,
34
- log_input,
35
- log_inputs,
36
- log_metric,
37
- log_output,
38
- log_outputs,
39
- log_param,
40
- log_src,
41
- set_tag,
42
- )
43
55
 
44
56
  __all__ = [
57
+ "AddTag",
58
+ "AddTags",
59
+ "AssetKind",
45
60
  "BaseConfig",
46
61
  "End",
47
- "LogArtifact",
48
- "LogArtifacts",
62
+ "Experiment",
63
+ "LogAsset",
64
+ "LogCode",
49
65
  "LogMetric",
66
+ "LogMetrics",
67
+ "LogOther",
68
+ "LogOthers",
50
69
  "LogParam",
51
- "LoggingEnd",
52
- "LoggingStart",
70
+ "LogParams",
71
+ "MetaAsset",
72
+ "PathProvider",
53
73
  "Plugin",
54
- "Run",
55
- "RunStatus",
56
74
  "Start",
75
+ "add_tag",
76
+ "add_tags",
57
77
  "as_os_path",
58
78
  "as_path",
59
79
  "as_posix",
60
80
  "config",
61
81
  "data",
62
82
  "end",
83
+ "end",
84
+ "end",
63
85
  "entrypoint",
86
+ "exp_dir",
87
+ "get_assets",
88
+ "get_inputs",
89
+ "get_outputs",
64
90
  "git_root",
65
91
  "git_root_safe",
66
- "info",
92
+ "input",
67
93
  "inputs",
68
- "log_artifact",
69
- "log_artifacts",
70
- "log_input",
71
- "log_inputs",
94
+ "integration",
95
+ "log_asset",
96
+ "log_code",
72
97
  "log_metric",
73
- "log_output",
74
- "log_outputs",
98
+ "log_metrics",
99
+ "log_other",
100
+ "log_others",
75
101
  "log_param",
76
- "log_src",
102
+ "log_params",
103
+ "meta",
104
+ "output",
77
105
  "outputs",
78
106
  "params",
79
107
  "path",
80
108
  "pathutils",
81
- "plugin",
82
109
  "presets",
83
110
  "run",
84
- "run_dir",
85
- "set_tag",
111
+ "run",
86
112
  "src",
87
113
  "start",
114
+ "start",
88
115
  "utils",
89
116
  ]
liblaf/cherries/_run.py CHANGED
@@ -3,37 +3,49 @@ from typing import get_type_hints
3
3
 
4
4
  import pydantic
5
5
 
6
+ from liblaf.cherries import config, integration, presets
6
7
  from liblaf.cherries import pathutils as _path
7
- from liblaf.cherries import plugin, presets
8
8
 
9
9
 
10
- def run[C: pydantic.BaseModel, T](main: Callable[[C], T]) -> T:
11
- run: plugin.Run = start()
10
+ def run[C: pydantic.BaseModel, T](
11
+ main: Callable[[], T] | Callable[[C], T],
12
+ *,
13
+ play: bool = False,
14
+ preset: presets.Preset = presets.default,
15
+ ) -> T:
16
+ exp: integration.Experiment = start(preset=preset, play=play)
12
17
  type_hints: dict[str, type[C]] = get_type_hints(main)
13
- cls: type[C] = next(iter(type_hints.values()))
14
- cfg: C = cls()
15
- run.log_param("cherries.config", cfg.model_dump(mode="json"))
18
+ del type_hints["return"]
19
+ args: list[C] = []
20
+ if len(type_hints) == 1:
21
+ cls: type[C] = next(iter(type_hints.values()))
22
+ cfg: C = cls()
23
+ args.append(cfg)
24
+ for path in config.get_inputs(cfg):
25
+ exp.log_input(path)
16
26
  try:
17
- ret: T = main(cfg)
18
- except BaseException as e:
19
- if isinstance(e, KeyboardInterrupt):
20
- run.end(plugin.RunStatus.KILLED)
21
- raise
22
- run.end(plugin.RunStatus.FAILED)
23
- raise
24
- else:
25
- run.end()
26
- return ret
27
-
28
-
29
- def start() -> plugin.Run:
30
- run: plugin.Run = presets.default()
31
- run.start()
32
- run.log_src(_path.entrypoint(absolute=True))
33
- run.set_tag("cherries.entrypoint", _path.entrypoint(absolute=False))
34
- run.set_tag("cherries.run-dir", _path.run_dir(absolute=False))
35
- return plugin.run
27
+ result: T = main(*args)
28
+ finally:
29
+ if len(type_hints) == 1:
30
+ cfg: C = args[0]
31
+ for path in config.get_outputs(cfg):
32
+ exp.log_output(path)
33
+ exp.end()
34
+ return result
35
+
36
+
37
+ def start(
38
+ preset: presets.Preset = presets.default, *, play: bool = False
39
+ ) -> integration.Experiment:
40
+ if play:
41
+ preset = presets.playground
42
+ exp: integration.Experiment = preset(integration.exp)
43
+ exp.start()
44
+ exp.log_other("cherries.entrypoint", _path.entrypoint(absolute=False))
45
+ exp.log_other("cherries.exp-dir", _path.exp_dir(absolute=False))
46
+ exp.log_other("cherries.start-time", exp.start_time)
47
+ return exp
36
48
 
37
49
 
38
50
  def end() -> None:
39
- plugin.run.end()
51
+ integration.exp.end()
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.0.14'
21
- __version_tuple__ = version_tuple = (0, 0, 14)
20
+ __version__ = version = '0.1.0'
21
+ __version_tuple__ = version_tuple = (0, 1, 0)
@@ -0,0 +1,23 @@
1
+ from ._asset import (
2
+ AssetKind,
3
+ MetaAsset,
4
+ PathProvider,
5
+ get_assets,
6
+ get_inputs,
7
+ get_outputs,
8
+ input, # noqa: A004
9
+ output,
10
+ )
11
+ from ._config import BaseConfig
12
+
13
+ __all__ = [
14
+ "AssetKind",
15
+ "BaseConfig",
16
+ "MetaAsset",
17
+ "PathProvider",
18
+ "get_assets",
19
+ "get_inputs",
20
+ "get_outputs",
21
+ "input",
22
+ "output",
23
+ ]
@@ -0,0 +1,72 @@
1
+ import enum
2
+ from collections.abc import Callable, Iterable
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ import pydantic
7
+
8
+ from liblaf import grapes
9
+ from liblaf.cherries import pathutils as _path
10
+ from liblaf.cherries.typed import PathLike
11
+
12
+
13
+ class AssetKind(enum.StrEnum):
14
+ INPUT = enum.auto()
15
+ OUTPUT = enum.auto()
16
+
17
+
18
+ type PathProvider = (
19
+ PathLike | Iterable[PathLike] | Callable[[PathLike], PathLike | Iterable[PathLike]]
20
+ )
21
+
22
+
23
+ class MetaAsset:
24
+ kind: AssetKind
25
+ _extra: PathProvider | None = None
26
+
27
+ def __init__(self, kind: AssetKind, extra: PathProvider | None = None) -> None:
28
+ self.kind = kind
29
+ self._extra = extra
30
+
31
+ def get_extra(self, value: Path) -> list[Path]:
32
+ if self._extra is None:
33
+ return []
34
+ if callable(self._extra):
35
+ extra: Iterable[PathLike] = grapes.as_iterable(self._extra(value))
36
+ return [Path(p) for p in extra]
37
+ extra: Iterable[PathLike] = grapes.as_iterable(self._extra)
38
+ return [Path(p) for p in extra]
39
+
40
+
41
+ def get_assets(cfg: pydantic.BaseModel, kind: AssetKind) -> list[Path]:
42
+ assets: list[Path] = []
43
+ for name, info in type(cfg).model_fields.items():
44
+ value: Any = getattr(cfg, name)
45
+ if isinstance(value, pydantic.BaseModel):
46
+ assets.extend(get_assets(value, kind))
47
+ for meta in info.metadata:
48
+ if isinstance(meta, MetaAsset) and meta.kind == kind:
49
+ value: Path = Path(value)
50
+ assets.append(value)
51
+ assets.extend(meta.get_extra(value))
52
+ return assets
53
+
54
+
55
+ def get_inputs(cfg: pydantic.BaseModel) -> list[Path]:
56
+ return get_assets(cfg, AssetKind.INPUT)
57
+
58
+
59
+ def get_outputs(cfg: pydantic.BaseModel) -> list[Path]:
60
+ return get_assets(cfg, AssetKind.OUTPUT)
61
+
62
+
63
+ def input(path: PathLike, extra: PathProvider | None = None, **kwargs) -> Path: # noqa: A001
64
+ field_info: pydantic.fields.FieldInfo = pydantic.Field(_path.data(path), **kwargs) # pyright: ignore[reportAssignmentType]
65
+ field_info.metadata.append(MetaAsset(kind=AssetKind.INPUT, extra=extra))
66
+ return field_info # pyright: ignore[reportReturnType]
67
+
68
+
69
+ def output(path: PathLike, extra: PathProvider | None = None, **kwargs) -> Path:
70
+ field_info: pydantic.fields.FieldInfo = pydantic.Field(_path.data(path), **kwargs) # pyright: ignore[reportAssignmentType]
71
+ field_info.metadata.append(MetaAsset(kind=AssetKind.OUTPUT, extra=extra))
72
+ return field_info # pyright: ignore[reportReturnType]
@@ -0,0 +1,6 @@
1
+ import pydantic_settings as ps
2
+
3
+
4
+ class BaseConfig(ps.BaseSettings):
5
+ model_config = ps.SettingsConfigDict(cli_parse_args=True)
6
+ # TODO: add support for config files
@@ -0,0 +1,66 @@
1
+ from . import comet, dvc, git, logging
2
+ from ._abc import (
3
+ AddTag,
4
+ AddTags,
5
+ End,
6
+ LogAsset,
7
+ LogCode,
8
+ LogMetric,
9
+ LogMetrics,
10
+ LogOther,
11
+ LogOthers,
12
+ LogParam,
13
+ LogParams,
14
+ Plugin,
15
+ Start,
16
+ )
17
+ from ._exp import (
18
+ Experiment,
19
+ add_tag,
20
+ add_tags,
21
+ end,
22
+ exp,
23
+ log_asset,
24
+ log_code,
25
+ log_metric,
26
+ log_metrics,
27
+ log_other,
28
+ log_others,
29
+ log_param,
30
+ log_params,
31
+ start,
32
+ )
33
+
34
+ __all__ = [
35
+ "AddTag",
36
+ "AddTags",
37
+ "End",
38
+ "Experiment",
39
+ "LogAsset",
40
+ "LogCode",
41
+ "LogMetric",
42
+ "LogMetrics",
43
+ "LogOther",
44
+ "LogOthers",
45
+ "LogParam",
46
+ "LogParams",
47
+ "Plugin",
48
+ "Start",
49
+ "add_tag",
50
+ "add_tags",
51
+ "comet",
52
+ "dvc",
53
+ "end",
54
+ "exp",
55
+ "git",
56
+ "log_asset",
57
+ "log_code",
58
+ "log_metric",
59
+ "log_metrics",
60
+ "log_other",
61
+ "log_others",
62
+ "log_param",
63
+ "log_params",
64
+ "logging",
65
+ "start",
66
+ ]
@@ -0,0 +1,144 @@
1
+ import bisect
2
+ import functools
3
+ import operator
4
+ from collections.abc import Iterable, Mapping
5
+ from typing import Any
6
+
7
+ import attrs
8
+ from loguru import logger
9
+
10
+ from liblaf.cherries.typed import PathLike
11
+
12
+
13
+ @attrs.define
14
+ @functools.total_ordering
15
+ class Plugin[**P, T]:
16
+ priority: int = attrs.field(default=0, kw_only=True)
17
+ _children: list["Plugin"] = attrs.field(
18
+ factory=list, eq=False, order=False, alias="children"
19
+ )
20
+
21
+ def __attrs_post_init__(self) -> None:
22
+ self._children.sort(key=operator.attrgetter("priority"))
23
+
24
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
25
+ ret: T | None = None
26
+ for child in self._children:
27
+ try:
28
+ ret = child(*args, **kwargs)
29
+ except BaseException as err:
30
+ if isinstance(err, (KeyboardInterrupt, SystemExit)):
31
+ raise
32
+ logger.exception(child)
33
+ return ret # pyright: ignore[reportReturnType]
34
+
35
+ def __lt__(self, other: "Plugin") -> bool:
36
+ if not isinstance(other, Plugin):
37
+ return NotImplemented
38
+ return self.priority < other.priority
39
+
40
+ def __eq__(self, other: "Plugin") -> bool: # pyright: ignore[reportIncompatibleMethodOverride]
41
+ if not isinstance(other, Plugin):
42
+ return NotImplemented
43
+ return self.priority == other.priority
44
+
45
+ @property
46
+ def children(self) -> list["Plugin"]:
47
+ return self._children
48
+
49
+ def add(self, *children: "Plugin") -> None:
50
+ for c in children:
51
+ bisect.insort(self._children, c, key=operator.attrgetter("priority"))
52
+
53
+ def extend(self, children: Iterable["Plugin"]) -> None:
54
+ self.add(*children)
55
+
56
+ def remove(self, child: "Plugin") -> None:
57
+ self._children.remove(child)
58
+
59
+
60
+ @attrs.define
61
+ class AddTag(Plugin):
62
+ def __call__(self, tag: str, /, **kwargs) -> None:
63
+ super().__call__(tag, **kwargs)
64
+
65
+
66
+ @attrs.define
67
+ class AddTags(Plugin):
68
+ def __call__(self, tags: Iterable[str], /, **kwargs) -> None:
69
+ super().__call__(tags, **kwargs)
70
+
71
+
72
+ @attrs.define
73
+ class End(Plugin):
74
+ def __call__(self, **kwargs) -> None:
75
+ super().__call__(**kwargs)
76
+
77
+
78
+ @attrs.define
79
+ class LogAsset(Plugin):
80
+ def __call__(self, path: PathLike, /, prefix: str | None = None, **kwargs) -> None:
81
+ super().__call__(path, prefix=prefix, **kwargs)
82
+
83
+
84
+ @attrs.define
85
+ class LogCode(Plugin):
86
+ def __call__(self, path: PathLike, /, **kwargs) -> None:
87
+ super().__call__(path, **kwargs)
88
+
89
+
90
+ @attrs.define
91
+ class LogMetric(Plugin):
92
+ def __call__(
93
+ self,
94
+ key: str,
95
+ value: float,
96
+ /,
97
+ step: int | None = None,
98
+ epoch: int | None = None,
99
+ **kwargs,
100
+ ) -> None:
101
+ super().__call__(key, value, step=step, epoch=epoch, **kwargs)
102
+
103
+
104
+ @attrs.define
105
+ class LogMetrics(Plugin):
106
+ def __call__(
107
+ self,
108
+ metrics: Mapping[str, Any],
109
+ /,
110
+ step: int | None = None,
111
+ epoch: int | None = None,
112
+ **kwargs,
113
+ ) -> None:
114
+ super().__call__(metrics, step=step, epoch=epoch, **kwargs)
115
+
116
+
117
+ @attrs.define
118
+ class LogOther(Plugin):
119
+ def __call__(self, key: str, value: Any, /, **kwargs) -> None:
120
+ super().__call__(key, value, **kwargs)
121
+
122
+
123
+ @attrs.define
124
+ class LogOthers(Plugin):
125
+ def __call__(self, others: Mapping[str, Any], /, **kwargs) -> None:
126
+ super().__call__(others, **kwargs)
127
+
128
+
129
+ @attrs.define
130
+ class LogParam(Plugin):
131
+ def __call__(self, name: str, value: Any, /, **kwargs) -> None:
132
+ super().__call__(name, value, **kwargs)
133
+
134
+
135
+ @attrs.define
136
+ class LogParams(Plugin):
137
+ def __call__(self, params: Mapping[str, Any], /, **kwargs) -> None:
138
+ super().__call__(params, **kwargs)
139
+
140
+
141
+ @attrs.define
142
+ class Start(Plugin):
143
+ def __call__(self, **kwargs) -> None:
144
+ super().__call__(**kwargs)
@@ -0,0 +1,97 @@
1
+ import datetime
2
+ import functools
3
+ from os import PathLike
4
+ from pathlib import Path
5
+
6
+ import attrs
7
+ import comet_ml as comet
8
+
9
+ from liblaf.cherries import pathutils as _path
10
+
11
+ from ._abc import (
12
+ AddTag,
13
+ AddTags,
14
+ End,
15
+ LogAsset,
16
+ LogCode,
17
+ LogMetric,
18
+ LogMetrics,
19
+ LogOther,
20
+ LogOthers,
21
+ LogParam,
22
+ LogParams,
23
+ Start,
24
+ )
25
+
26
+
27
+ @attrs.define
28
+ class Experiment:
29
+ add_tag: AddTag = attrs.field(factory=AddTag, init=False)
30
+ add_tags: AddTags = attrs.field(factory=AddTags, init=False)
31
+ end: End = attrs.field(factory=End, init=False)
32
+ log_asset: LogAsset = attrs.field(factory=LogAsset, init=False)
33
+ log_code: LogCode = attrs.field(factory=LogCode, init=False)
34
+ log_metric: LogMetric = attrs.field(factory=LogMetric, init=False)
35
+ log_metrics: LogMetrics = attrs.field(factory=LogMetrics, init=False)
36
+ log_other: LogOther = attrs.field(factory=LogOther, init=False)
37
+ log_others: LogOthers = attrs.field(factory=LogOthers, init=False)
38
+ log_param: LogParam = attrs.field(factory=LogParam, init=False)
39
+ log_params: LogParams = attrs.field(factory=LogParams, init=False)
40
+ start: Start = attrs.field(factory=Start, init=False)
41
+
42
+ @property
43
+ def comet(self) -> comet.CometExperiment:
44
+ return comet.get_running_experiment() # pyright: ignore[reportReturnType]
45
+
46
+ @functools.cached_property
47
+ def exp_dir(self) -> Path:
48
+ return _path.exp_dir(absolute=True)
49
+
50
+ @property
51
+ def exp_key(self) -> str:
52
+ return self.comet.get_key()
53
+
54
+ @property
55
+ def exp_name(self) -> str:
56
+ return self.comet.get_name()
57
+
58
+ @exp_name.setter
59
+ def exp_name(self, value: str) -> None:
60
+ self.comet.set_name(value)
61
+
62
+ @property
63
+ def exp_url(self) -> str:
64
+ return self.comet.url # pyright: ignore[reportReturnType]
65
+
66
+ @property
67
+ def project_id(self) -> str:
68
+ return self.comet.project_id # pyright: ignore[reportReturnType]
69
+
70
+ @property
71
+ def project_name(self) -> str:
72
+ return self.comet.project_name
73
+
74
+ @functools.cached_property
75
+ def start_time(self) -> datetime.datetime:
76
+ return datetime.datetime.now().astimezone()
77
+
78
+ def log_input(self, path: PathLike, /, **kwargs) -> None:
79
+ self.log_asset(path, prefix="inputs/", **kwargs)
80
+
81
+ def log_output(self, path: PathLike, /, **kwargs) -> None:
82
+ self.log_asset(path, prefix="outputs/", **kwargs)
83
+
84
+
85
+ exp = Experiment()
86
+ add_tag: AddTag = exp.add_tag
87
+ add_tags: AddTags = exp.add_tags
88
+ end: End = exp.end
89
+ log_asset: LogAsset = exp.log_asset
90
+ log_code: LogCode = exp.log_code
91
+ log_metric: LogMetric = exp.log_metric
92
+ log_metrics: LogMetrics = exp.log_metrics
93
+ log_other: LogOther = exp.log_other
94
+ log_others: LogOthers = exp.log_others
95
+ log_param: LogParam = exp.log_param
96
+ log_params: LogParams = exp.log_params
97
+ start: Start = exp.start