laco-logging 1.0.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.
@@ -0,0 +1,58 @@
1
+ """Unified experiment logging for laco.
2
+
3
+ Supports Weights & Biases, MLflow, and TensorBoard through a common API.
4
+ Each backend is an optional extra — install only what you need::
5
+
6
+ pip install laco-logging[wandb]
7
+ pip install laco-logging[mlflow]
8
+ pip install laco-logging[tensorboard]
9
+ pip install laco-logging[all]
10
+
11
+ Top-level API
12
+ -------------
13
+ :func:`log_config`
14
+ Log a resolved DictConfig to one or more backends in a single call.
15
+
16
+ :func:`run`
17
+ Unified context manager — delegates to the backend of your choice.
18
+
19
+ Backend submodules
20
+ ------------------
21
+ :mod:`laco.integrations.logging.wandb`
22
+ ``run(cfg, *, project, name, tags, ...)``
23
+ ``log_config(cfg, run=None)``
24
+
25
+ :mod:`laco.integrations.logging.mlflow`
26
+ ``run(cfg, *, experiment, run_name, tags, ...)``
27
+ ``log_config(cfg, run_id=None)``
28
+ ``log_artifact(cfg)``
29
+
30
+ :mod:`laco.integrations.logging.tensorboard`
31
+ ``run(cfg, *, log_dir, comment, ...)``
32
+ ``log_config(cfg, writer)``
33
+
34
+ Examples
35
+ --------
36
+ ::
37
+
38
+ import laco
39
+ import laco.integrations.logging as laco_logging
40
+
41
+ cfg = laco.load("configs/train.py")
42
+
43
+ # Single backend:
44
+ with laco_logging.run(cfg, backend="wandb", project="my-project") as run:
45
+ train(cfg)
46
+
47
+ # Multiple backends simultaneously:
48
+ laco_logging.log_config(cfg, backends=["wandb", "mlflow"])
49
+
50
+ # Backend submodule directly:
51
+ with laco_logging.tensorboard.run(cfg, log_dir="tb/run1") as writer:
52
+ writer.add_scalar("loss", 0.1, 0)
53
+ """
54
+
55
+ from laco.integrations.logging._core import log_config as log_config
56
+ from laco.integrations.logging._core import run as run
57
+
58
+ __all__ = ["log_config", "mlflow", "run", "tensorboard", "wandb"]
@@ -0,0 +1 @@
1
+ """Backend implementations — imported lazily by the top-level API."""
@@ -0,0 +1,69 @@
1
+ """MLflow backend for laco-logging."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import pathlib
7
+ import typing
8
+
9
+ if typing.TYPE_CHECKING:
10
+ import mlflow
11
+ from omegaconf import DictConfig
12
+
13
+
14
+ def _flatten(d: dict, prefix: str = "") -> dict[str, str]:
15
+ out: dict[str, str] = {}
16
+ for k, v in d.items():
17
+ key = f"{prefix}.{k}" if prefix else k
18
+ if isinstance(v, dict):
19
+ out.update(_flatten(v, key))
20
+ else:
21
+ out[key] = str(v)
22
+ return out
23
+
24
+
25
+ def log_config(cfg: DictConfig, run_id: str | None = None) -> None:
26
+ """Log *cfg* as MLflow params (flattened with dot-separated keys)."""
27
+ import mlflow as _mlflow
28
+ from omegaconf import OmegaConf
29
+
30
+ flat = OmegaConf.to_container(cfg, resolve=True, throw_on_missing=False)
31
+ params = _flatten(flat) # type: ignore[arg-type]
32
+ if run_id:
33
+ with _mlflow.start_run(run_id=run_id):
34
+ _mlflow.log_params(params)
35
+ else:
36
+ _mlflow.log_params(params)
37
+
38
+
39
+ def log_artifact(cfg: DictConfig) -> None:
40
+ """Save *cfg* as a ``config.yaml`` artifact in the active MLflow run."""
41
+ import tempfile
42
+
43
+ import mlflow as _mlflow
44
+ from omegaconf import OmegaConf
45
+
46
+ yaml_text = OmegaConf.to_yaml(cfg, resolve=True)
47
+ with tempfile.TemporaryDirectory() as tmp:
48
+ p = pathlib.Path(tmp) / "config.yaml"
49
+ p.write_text(yaml_text)
50
+ _mlflow.log_artifact(str(p))
51
+
52
+
53
+ @contextlib.contextmanager
54
+ def run(
55
+ cfg: DictConfig,
56
+ *,
57
+ experiment: str | None = None,
58
+ run_name: str | None = None,
59
+ tags: dict[str, str] | None = None,
60
+ **kwargs: typing.Any,
61
+ ) -> typing.Generator[mlflow.ActiveRun]:
62
+ """Start an MLflow run, log *cfg* as params, and end on exit."""
63
+ import mlflow as _mlflow
64
+
65
+ if experiment:
66
+ _mlflow.set_experiment(experiment)
67
+ with _mlflow.start_run(run_name=run_name, tags=tags, **kwargs) as active_run:
68
+ log_config(cfg, run_id=active_run.info.run_id)
69
+ yield active_run
@@ -0,0 +1,89 @@
1
+ """TensorBoard backend for laco-logging."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import typing
7
+
8
+ if typing.TYPE_CHECKING:
9
+ from omegaconf import DictConfig
10
+ from torch.utils.tensorboard import SummaryWriter
11
+
12
+
13
+ def log_config(cfg: DictConfig, writer: SummaryWriter) -> None:
14
+ """Log *cfg* as TensorBoard hparams with no associated metrics.
15
+
16
+ Writes a ``hparams`` entry to the TensorBoard event file so the
17
+ HParams dashboard shows the config alongside any scalars logged later.
18
+
19
+ Parameters
20
+ ----------
21
+ cfg : DictConfig
22
+ Resolved laco DictConfig.
23
+ writer : SummaryWriter
24
+ An open ``SummaryWriter``.
25
+ """
26
+ from omegaconf import OmegaConf
27
+
28
+ flat = OmegaConf.to_container(cfg, resolve=True, throw_on_missing=False)
29
+
30
+ def _flatten(d: dict, prefix: str = "") -> dict[str, typing.Any]:
31
+ out: dict[str, typing.Any] = {}
32
+ for k, v in d.items():
33
+ key = f"{prefix}.{k}" if prefix else k
34
+ if isinstance(v, dict):
35
+ out.update(_flatten(v, key))
36
+ elif isinstance(v, int | float | str | bool):
37
+ out[key] = v
38
+ else:
39
+ out[key] = str(v)
40
+ return out
41
+
42
+ hparams = _flatten(flat) # type: ignore[arg-type]
43
+ # TensorBoard's add_hparams requires a metric_dict; pass an empty one
44
+ # so the hparams are visible without artificial metric entries.
45
+ writer.add_hparams(hparams, metric_dict={})
46
+
47
+
48
+ @contextlib.contextmanager
49
+ def run(
50
+ cfg: DictConfig,
51
+ *,
52
+ log_dir: str | None = None,
53
+ comment: str = "",
54
+ **kwargs: typing.Any,
55
+ ) -> typing.Generator[SummaryWriter]:
56
+ """Open a ``SummaryWriter``, log *cfg* as hparams, and close on exit.
57
+
58
+ Parameters
59
+ ----------
60
+ cfg : DictConfig
61
+ Resolved laco DictConfig.
62
+ log_dir : str | None
63
+ TensorBoard log directory. Defaults to ``runs/<timestamp>``.
64
+ comment : str
65
+ Appended to the auto-generated run name when *log_dir* is ``None``.
66
+ **kwargs
67
+ Extra kwargs forwarded to ``SummaryWriter.__init__``.
68
+
69
+ Yields
70
+ ------
71
+ SummaryWriter
72
+ An open ``torch.utils.tensorboard.SummaryWriter``.
73
+
74
+ Examples
75
+ --------
76
+ ::
77
+
78
+ with laco.integrations.logging.tensorboard.run(cfg, log_dir="tb_logs/run1") as writer:
79
+ for step, loss in enumerate(train(cfg)):
80
+ writer.add_scalar("loss/train", loss, step)
81
+ """
82
+ from torch.utils.tensorboard import SummaryWriter as _SW
83
+
84
+ writer = _SW(log_dir=log_dir, comment=comment, **kwargs)
85
+ log_config(cfg, writer)
86
+ try:
87
+ yield writer
88
+ finally:
89
+ writer.close()
@@ -0,0 +1,46 @@
1
+ """W&B backend for laco-logging."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import typing
7
+
8
+ if typing.TYPE_CHECKING:
9
+ import wandb
10
+ from omegaconf import DictConfig
11
+
12
+
13
+ def log_config(cfg: DictConfig, run: wandb.Run | None = None) -> None:
14
+ """Log *cfg* as W&B hyperparameters on *run* (or the global run)."""
15
+ import wandb as _wandb
16
+ from omegaconf import OmegaConf
17
+
18
+ target = run or _wandb.run
19
+ if target is None:
20
+ msg = "No active W&B run. Call wandb.init() before log_config()."
21
+ raise RuntimeError(msg)
22
+ flat = OmegaConf.to_container(cfg, resolve=True, throw_on_missing=False)
23
+ target.config.update(flat, allow_val_change=True)
24
+
25
+
26
+ @contextlib.contextmanager
27
+ def run(
28
+ cfg: DictConfig,
29
+ *,
30
+ project: str | None = None,
31
+ name: str | None = None,
32
+ tags: list[str] | None = None,
33
+ **kwargs: typing.Any,
34
+ ) -> typing.Generator[wandb.Run]:
35
+ """Start a W&B run, log *cfg*, and finish on exit."""
36
+ import wandb as _wandb
37
+ from omegaconf import OmegaConf
38
+
39
+ flat = OmegaConf.to_container(cfg, resolve=True, throw_on_missing=False)
40
+ wandb_run = _wandb.init(
41
+ project=project, name=name, tags=tags, config=flat, **kwargs
42
+ )
43
+ try:
44
+ yield wandb_run
45
+ finally:
46
+ wandb_run.finish()
@@ -0,0 +1,94 @@
1
+ """Implementation for laco-logging."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import typing
7
+
8
+ if typing.TYPE_CHECKING:
9
+ from omegaconf import DictConfig
10
+
11
+ _BACKEND_MODULES = {
12
+ "wandb": "laco.integrations.logging._backends.wandb",
13
+ "mlflow": "laco.integrations.logging._backends.mlflow",
14
+ "tensorboard": "laco.integrations.logging._backends.tensorboard",
15
+ }
16
+
17
+ _Backend = typing.Literal["wandb", "mlflow", "tensorboard"]
18
+
19
+
20
+ def log_config(
21
+ cfg: DictConfig,
22
+ backends: list[_Backend] | _Backend,
23
+ **backend_kwargs: typing.Any,
24
+ ) -> None:
25
+ """Log a resolved laco DictConfig to one or more backends.
26
+
27
+ Parameters
28
+ ----------
29
+ cfg : DictConfig
30
+ Resolved OmegaConf DictConfig from ``laco.load()``.
31
+ backends : str | list[str]
32
+ One backend name or a list of backend names.
33
+ Valid values: ``"wandb"``, ``"mlflow"``, ``"tensorboard"``.
34
+ **backend_kwargs
35
+ Passed through to each backend's ``log_config()``. For TensorBoard
36
+ a ``writer`` kwarg is required.
37
+
38
+ Raises
39
+ ------
40
+ ValueError
41
+ If an unknown backend name is provided.
42
+ """
43
+ import importlib
44
+
45
+ targets: list[_Backend] = [backends] if isinstance(backends, str) else backends
46
+ for backend in targets:
47
+ if backend not in _BACKEND_MODULES:
48
+ msg = f"Unknown backend {backend!r}. Choose from: {list(_BACKEND_MODULES)}"
49
+ raise ValueError(msg)
50
+ mod = importlib.import_module(_BACKEND_MODULES[backend])
51
+ mod.log_config(cfg, **backend_kwargs)
52
+
53
+
54
+ @contextlib.contextmanager
55
+ def run(
56
+ cfg: DictConfig,
57
+ *,
58
+ backend: _Backend,
59
+ **backend_kwargs: typing.Any,
60
+ ) -> typing.Generator[typing.Any]:
61
+ """Unified context manager — starts a run on the chosen backend.
62
+
63
+ Delegates entirely to the backend's own ``run()`` context manager,
64
+ forwarding all backend-specific kwargs unchanged.
65
+
66
+ Parameters
67
+ ----------
68
+ cfg : DictConfig
69
+ Resolved laco DictConfig.
70
+ backend : str
71
+ One of ``"wandb"``, ``"mlflow"``, ``"tensorboard"``.
72
+ **backend_kwargs
73
+ Forwarded to the backend's ``run()`` (e.g. ``project=`` for W&B,
74
+ ``experiment=`` for MLflow, ``log_dir=`` for TensorBoard).
75
+
76
+ Yields
77
+ ------
78
+ Any
79
+ The backend's run handle (``wandb.Run``, ``mlflow.ActiveRun``, or
80
+ ``torch.utils.tensorboard.SummaryWriter``).
81
+
82
+ Raises
83
+ ------
84
+ ValueError
85
+ If an unknown backend name is provided.
86
+ """
87
+ import importlib
88
+
89
+ if backend not in _BACKEND_MODULES:
90
+ msg = f"Unknown backend {backend!r}. Choose from: {list(_BACKEND_MODULES)}"
91
+ raise ValueError(msg)
92
+ mod = importlib.import_module(_BACKEND_MODULES[backend])
93
+ with mod.run(cfg, **backend_kwargs) as handle:
94
+ yield handle
@@ -0,0 +1,7 @@
1
+ """MLflow backend — re-exported from laco.integrations.logging._backends.mlflow."""
2
+
3
+ from laco.integrations.logging._backends.mlflow import log_artifact as log_artifact
4
+ from laco.integrations.logging._backends.mlflow import log_config as log_config
5
+ from laco.integrations.logging._backends.mlflow import run as run
6
+
7
+ __all__ = ["log_artifact", "log_config", "run"]
@@ -0,0 +1,6 @@
1
+ """TensorBoard backend — re-exported from laco.integrations.logging._backends.tensorboard."""
2
+
3
+ from laco.integrations.logging._backends.tensorboard import log_config as log_config
4
+ from laco.integrations.logging._backends.tensorboard import run as run
5
+
6
+ __all__ = ["log_config", "run"]
@@ -0,0 +1,6 @@
1
+ """W&B backend — re-exported from laco.integrations.logging._backends.wandb."""
2
+
3
+ from laco.integrations.logging._backends.wandb import log_config as log_config
4
+ from laco.integrations.logging._backends.wandb import run as run
5
+
6
+ __all__ = ["log_config", "run"]
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: laco-logging
3
+ Version: 1.0.0
4
+ Summary: Unified experiment logging (W&B, MLflow, TensorBoard) for laco.
5
+ Author-email: Kurt Stolle <kurt@khws.io>
6
+ Requires-Python: >=3.13
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: laco>=1.0.0
9
+ Provides-Extra: wandb
10
+ Requires-Dist: wandb>=0.19; extra == "wandb"
11
+ Provides-Extra: mlflow
12
+ Requires-Dist: mlflow>=2.0; extra == "mlflow"
13
+ Provides-Extra: tensorboard
14
+ Requires-Dist: tensorboard>=2.0; extra == "tensorboard"
15
+ Provides-Extra: all
16
+ Requires-Dist: wandb>=0.19; extra == "all"
17
+ Requires-Dist: mlflow>=2.0; extra == "all"
18
+ Requires-Dist: tensorboard>=2.0; extra == "all"
19
+
20
+ # Laco-Logging
21
+
22
+ Unified experiment logging (W&B, MLflow, TensorBoard) for laco.
23
+
24
+ Part of the [laco](https://github.com/khwstolle/laco) project — see the [root README](../../README.md) for an overview.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install laco-logging[all]
30
+ ```
31
+
32
+ ## Extras
33
+
34
+ | Extra | Installs |
35
+ |---|---|
36
+ | `laco-logging[wandb]` | W&B backend |
37
+ | `laco-logging[mlflow]` | MLflow backend |
38
+ | `laco-logging[tensorboard]` | TensorBoard backend |
39
+ | `laco-logging[all]` | All backends |
40
+
41
+ ## Features
42
+
43
+ `log_config()`, `run()`, submodules `wandb`, `mlflow`, `tensorboard`
44
+
45
+ ## Usage
46
+
47
+ See [`docs/index.md`](docs/index.md) for the full guide.
@@ -0,0 +1,13 @@
1
+ laco/integrations/logging/__init__.py,sha256=lV4doaFr6cY1nootc69mJGQLPfk5izLcODF9hdQo__I,1676
2
+ laco/integrations/logging/_core.py,sha256=EtbCaH9dZ9bViEPXHNyN8PHn-VQEAtX4Z9YqQZFaBec,2778
3
+ laco/integrations/logging/mlflow.py,sha256=eJKBx55MfXWmpjmy51-PgPJIHiBFB06XEzrWspxv5z0,366
4
+ laco/integrations/logging/tensorboard.py,sha256=PZXs3a3oNh-AMXOXDuQGfHYQWCZkQarqiER5v3O4JeQ,286
5
+ laco/integrations/logging/wandb.py,sha256=Q1ihqBWpFvfXVKKnY0RYGCzzU4BvzgXbKmpZU8qEzA8,260
6
+ laco/integrations/logging/_backends/__init__.py,sha256=iVb3zdnqAhRQPm4y5FVFaeR6Y5sBUIHeHb1lwYAJHaw,72
7
+ laco/integrations/logging/_backends/mlflow.py,sha256=Z0tviDNGJmAMrEh5FOI5fPGn1q1sbgbZeQ8MaGJGIMA,1975
8
+ laco/integrations/logging/_backends/tensorboard.py,sha256=CV-U7sYN1By3n3wGImk7pn5P54H3IbY9qZ7aV6Mk4uQ,2632
9
+ laco/integrations/logging/_backends/wandb.py,sha256=LWUu0a7OJcIOoPTDTWZ_rWSY0cHA00k8YqbkarE7jPM,1284
10
+ laco_logging-1.0.0.dist-info/METADATA,sha256=9QS-tC2bBVJ9duI-SxODlOqiCzQgGYEINYaqB4sg4Vc,1270
11
+ laco_logging-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
12
+ laco_logging-1.0.0.dist-info/top_level.txt,sha256=G2kLu09Aje44OkSqu-Tae3mjmTYhyRc2VrTyh3OmxFw,5
13
+ laco_logging-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ laco