Coverage for src/instawell/utils/logging_util.py: 97%
31 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-07 15:47 -0600
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-07 15:47 -0600
1# instawell/logging_util.py
2import logging
3from logging import FileHandler, Formatter
4from pathlib import Path
7def setup_experiment_logging(
8 experiment_dir: Path,
9 filename: str = "experiment.log",
10 level: int = logging.INFO,
11) -> Path:
12 """
13 Attach a file handler for this experiment to the `instawell` logger.
14 Safe to call multiple times; it won't duplicate handlers for the same file.
15 """
16 experiment_dir.mkdir(parents=True, exist_ok=True)
17 log_path = experiment_dir / filename
19 pkg_logger = logging.getLogger("instawell")
20 pkg_logger.setLevel(level)
22 # Don't add duplicate handlers for the same file
23 for h in pkg_logger.handlers:
24 if isinstance(h, FileHandler) and getattr(h, "baseFilename", None) == str(log_path):
25 return log_path
27 fh = FileHandler(log_path, encoding="utf-8")
28 fh.setLevel(level)
29 fh.setFormatter(Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s"))
30 pkg_logger.addHandler(fh)
32 pkg_logger.propagate = True
33 return log_path
36def ensure_experiment_context(
37 experiment_name: str,
38 *,
39 experiments_root: str | Path = "experiments",
40 log_to_file: bool = True,
41 log_level: int = logging.INFO,
42) -> Path:
43 """
44 Common setup used by all step entrypoints.
46 - Creates a top-level experiments_root directory (default: ./experiments)
47 - Creates this experiment's directory under it
48 - Ensures a .gitignore in the experiment directory
49 - Optionally wires all `instawell` loggers to experiment.log
51 Returns
52 -------
53 experiment_dir : Path
54 The path to this experiment's directory.
55 """
56 # Resolve the base experiments directory
57 experiments_root_path = Path(experiments_root)
59 # If relative, anchor to current working directory
60 if not experiments_root_path.is_absolute():
61 experiments_root_path = Path.cwd() / experiments_root_path
63 experiments_root_path.mkdir(parents=True, exist_ok=True)
65 # Now the specific experiment directory, e.g. ./experiments/exp_001
66 experiment_dir = experiments_root_path / experiment_name
67 experiment_dir.mkdir(parents=True, exist_ok=True)
69 # Keep experiment artifacts out of git by default
70 gitignore = experiment_dir / ".gitignore"
71 if not gitignore.exists():
72 gitignore.write_text("*\n", encoding="utf-8")
74 if log_to_file:
75 setup_experiment_logging(experiment_dir=experiment_dir, level=log_level)
77 # check if any .csv files exist in the experiment directory
78 csv_files = list(experiment_dir.glob("*.csv"))
80 return experiment_dir