Skip to content

Commit c698782

Browse files
committed
refactor(core): centralize optional-dependency
1 parent 3524082 commit c698782

3 files changed

Lines changed: 101 additions & 38 deletions

File tree

deeptab/core/observability.py

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -300,12 +300,9 @@ def build_structlog_logger(config: ObservabilityConfig, run_dir: str | None = No
300300
ImportError
301301
If ``structlog`` is not installed, with an actionable install hint.
302302
"""
303-
try:
304-
import structlog # type: ignore[import-untyped]
305-
except ImportError as exc:
306-
raise ImportError(
307-
"structlog is required when structured_logging=True. Install it with: pip install 'deeptab[logs]'"
308-
) from exc
303+
from deeptab.core.optional_deps import require_structlog
304+
305+
structlog = require_structlog()
309306

310307
import json
311308
import os
@@ -458,16 +455,15 @@ def build_lightning_loggers(
458455
"""
459456
import os
460457

458+
from deeptab.core.optional_deps import require_mlflow, require_tensorboard
459+
461460
loggers: list[Any] = []
462461

463462
for tracker in config.experiment_trackers:
464463
if tracker == "mlflow":
465-
try:
466-
from lightning.pytorch.loggers import MLFlowLogger
467-
except ImportError as exc:
468-
raise ImportError(
469-
"MLflow logging requires the mlflow package. Install it with: pip install 'deeptab[mlflow]'"
470-
) from exc
464+
require_mlflow()
465+
from lightning.pytorch.loggers import MLFlowLogger
466+
471467
# Ensure the artifact location directory exists
472468
if config.mlflow_artifact_location:
473469
os.makedirs(config.mlflow_artifact_location, exist_ok=True)
@@ -482,13 +478,9 @@ def build_lightning_loggers(
482478
)
483479

484480
elif tracker == "tensorboard":
485-
try:
486-
from lightning.pytorch.loggers import TensorBoardLogger
487-
except ImportError as exc:
488-
raise ImportError(
489-
"TensorBoard logging requires the tensorboard package. "
490-
"Install it with: pip install 'deeptab[tensorboard]'"
491-
) from exc
481+
require_tensorboard()
482+
from lightning.pytorch.loggers import TensorBoardLogger
483+
492484
loggers.append(
493485
TensorBoardLogger(
494486
save_dir=config.tensorboard_save_dir,

deeptab/core/optional_deps.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"""Guards for optional third-party dependencies.
2+
3+
DeepTab keeps its default install lightweight: structured logging and the
4+
experiment-tracking backends are shipped as optional extras. Each helper here
5+
imports one optional dependency on demand and raises a clear, actionable
6+
:class:`ImportError` — pointing at the matching ``pip install 'deeptab[...]'``
7+
command — when the package is not installed.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
from typing import TYPE_CHECKING, Any
13+
14+
if TYPE_CHECKING:
15+
from types import ModuleType
16+
17+
18+
def require_structlog() -> ModuleType:
19+
"""Return the :mod:`structlog` module.
20+
21+
Returns
22+
-------
23+
module
24+
The imported ``structlog`` module.
25+
26+
Raises
27+
------
28+
ImportError
29+
If ``structlog`` is not installed, with an actionable install hint.
30+
"""
31+
try:
32+
import structlog # type: ignore[import-untyped]
33+
except ImportError as exc:
34+
raise ImportError(
35+
"Structured logging requires the optional 'structlog' dependency. "
36+
"Install it with: pip install 'deeptab[logs]'"
37+
) from exc
38+
39+
return structlog
40+
41+
42+
def require_mlflow() -> ModuleType:
43+
"""Return the :mod:`mlflow` module.
44+
45+
Returns
46+
-------
47+
module
48+
The imported ``mlflow`` module.
49+
50+
Raises
51+
------
52+
ImportError
53+
If ``mlflow`` is not installed, with an actionable install hint.
54+
"""
55+
try:
56+
import mlflow # type: ignore[import-untyped]
57+
except ImportError as exc:
58+
raise ImportError(
59+
"MLflow tracking requires the optional 'mlflow' dependency. Install it with: pip install 'deeptab[mlflow]'"
60+
) from exc
61+
62+
return mlflow
63+
64+
65+
def require_tensorboard() -> Any:
66+
"""Return ``torch.utils.tensorboard.SummaryWriter``.
67+
68+
Returns
69+
-------
70+
type
71+
The ``SummaryWriter`` class from ``torch.utils.tensorboard``.
72+
73+
Raises
74+
------
75+
ImportError
76+
If ``tensorboard`` is not installed, with an actionable install hint.
77+
"""
78+
try:
79+
from torch.utils.tensorboard import SummaryWriter
80+
except ImportError as exc:
81+
raise ImportError(
82+
"TensorBoard logging requires the optional 'tensorboard' dependency. "
83+
"Install it with: pip install 'deeptab[tensorboard]'"
84+
) from exc
85+
86+
return SummaryWriter

tests/test_observability.py

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -158,32 +158,17 @@ def test_build_lightning_loggers_unknown_tracker_raises():
158158

159159
def test_build_lightning_loggers_mlflow_absent(monkeypatch):
160160
"""ImportError with install hint when mlflow is not installed."""
161-
# Simulate mlflow being absent by blocking its import inside Lightning
162-
real_import = __builtins__.__import__ if hasattr(__builtins__, "__import__") else __import__ # type: ignore[attr-defined]
163-
164-
def _block_mlflow(name, *args, **kwargs):
165-
if "MLFlowLogger" in name or (len(args) >= 3 and "MLFlowLogger" in str(args[2])):
166-
raise ImportError("No module named 'mlflow'")
167-
return real_import(name, *args, **kwargs)
168-
169-
# Use monkeypatch on the lightning loggers module directly
170-
mock_module = MagicMock()
171-
mock_module.MLFlowLogger.side_effect = ImportError("No module named 'mlflow'")
172-
173-
import lightning.pytorch.loggers as lpl
174-
175-
original_MLFlowLogger = getattr(lpl, "MLFlowLogger", None)
176-
177-
# Patch lightning.pytorch.loggers so that importing MLFlowLogger raises
178-
monkeypatch.setitem(sys.modules, "lightning.pytorch.loggers", None) # type: ignore[arg-type]
161+
# The guard checks the actual ``mlflow`` package, so simulate its absence.
162+
monkeypatch.setitem(sys.modules, "mlflow", None) # type: ignore[arg-type]
179163
cfg = ObservabilityConfig(experiment_trackers=["mlflow"])
180164
with pytest.raises(ImportError, match="pip install 'deeptab\\[mlflow\\]'"):
181165
build_lightning_loggers(cfg)
182166

183167

184168
def test_build_lightning_loggers_tensorboard_absent(monkeypatch):
185169
"""ImportError with install hint when tensorboard is not installed."""
186-
monkeypatch.setitem(sys.modules, "lightning.pytorch.loggers", None) # type: ignore[arg-type]
170+
# The guard checks ``torch.utils.tensorboard``, so simulate its absence.
171+
monkeypatch.setitem(sys.modules, "torch.utils.tensorboard", None) # type: ignore[arg-type]
187172
cfg = ObservabilityConfig(experiment_trackers=["tensorboard"])
188173
with pytest.raises(ImportError, match="pip install 'deeptab\\[tensorboard\\]'"):
189174
build_lightning_loggers(cfg)

0 commit comments

Comments
 (0)