Skip to content

Commit 6bdb934

Browse files
committed
update fallback when no logging_dir found
1 parent 6b369e5 commit 6bdb934

File tree

3 files changed

+103
-50
lines changed

3 files changed

+103
-50
lines changed

fancylog/fancylog.py

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -599,22 +599,43 @@ def log_image(
599599
subfolder: str | None = None,
600600
metadata: dict | None = None,
601601
):
602-
"""Save an image to the logging dir and record its path in the log."""
602+
"""Save an image to the logging dir and record its path in the log.
603+
604+
Parameters
605+
----------
606+
image : np.ndarray
607+
Image data to save.
608+
name : str
609+
Filename (without extension).
610+
logging_dir : str or Path, optional
611+
Root logging directory. If None, the default is inferred.
612+
subfolder : str, optional
613+
Optional subfolder within 'logging_dir/images'.
614+
metadata : dict, optional
615+
Optional metadata to save as a JSON file in same dir as image.
616+
617+
Returns
618+
-------
619+
Path
620+
Path to the saved image file.
621+
622+
"""
623+
logger = logging.getLogger(__name__)
624+
603625
if logging_dir is None:
604626
logging_dir = get_default_logging_dir()
605627
if logging_dir is None:
606-
raise ValueError(
607-
"Could not infer logging directory from active logger. "
608-
"Please provide logging_dir explicitly."
628+
logging_dir = Path.cwd() / "logs"
629+
logger.info(
630+
f"[fancylog] No default logging directory found. "
631+
f"Falling back to '{logging_dir}'"
609632
)
610633

611634
output_dir = Path(logging_dir)
612635
if subfolder:
613-
image_dir = (
614-
output_dir / "media" / "images" / _RUN_TIMESTAMP / subfolder
615-
)
636+
image_dir = output_dir / "logged_images" / _RUN_TIMESTAMP / subfolder
616637
else:
617-
image_dir = output_dir / "media" / "images" / _RUN_TIMESTAMP
638+
image_dir = output_dir / "logged_images" / _RUN_TIMESTAMP
618639
image_dir.mkdir(parents=True, exist_ok=True)
619640

620641
filepath = image_dir / f"{name}.tiff"
@@ -625,7 +646,7 @@ def log_image(
625646
with open(meta_path, "w") as f:
626647
json.dump(metadata, f, indent=2)
627648

628-
logging.getLogger(__name__).info(f"[fancylog] Saved image: {filepath}")
649+
logger.info(f"[fancylog] Saved image: {filepath}")
629650
return filepath
630651

631652

@@ -634,35 +655,54 @@ def log_data_object(
634655
name: str,
635656
logging_dir: str | Path | None = None,
636657
subfolder: str | None = None,
637-
ext: str = "json",
638658
):
639-
"""Save structured data (e.g., dict, list, numpy array) to disk."""
659+
"""Save structured data (e.g., dict, list, numpy array) to disk.
660+
661+
Parameters
662+
----------
663+
data : dict, list, or np.ndarray
664+
The data object to save. Dictionaries and lists are saved as JSON,
665+
while NumPy arrays are saved as `.npy` files.
666+
name : str
667+
Filename (without extension).
668+
logging_dir : str or Path, optional
669+
Root logging directory. If None, the default is inferred.
670+
subfolder : str, optional
671+
Optional subfolder within 'logging_dir/data'.
672+
673+
Returns
674+
-------
675+
Path
676+
Path to the saved data file.
677+
678+
"""
679+
logger = logging.getLogger(__name__)
680+
640681
if logging_dir is None:
641682
logging_dir = get_default_logging_dir()
642683
if logging_dir is None:
643-
raise ValueError(
644-
"Could not infer logging directory from active logger. "
645-
"Please provide logging_dir explicitly."
684+
logging_dir = Path.cwd() / "logs"
685+
logger.info(
686+
f"[fancylog] No default logging directory found. "
687+
f"Falling back to '{logging_dir}'"
646688
)
647689

648690
output_dir = Path(logging_dir)
649691
if subfolder:
650-
data_dir = output_dir / "media" / "data" / _RUN_TIMESTAMP / subfolder
692+
data_dir = output_dir / "logged_data" / _RUN_TIMESTAMP / subfolder
651693
else:
652-
data_dir = output_dir / "media" / "data" / _RUN_TIMESTAMP
694+
data_dir = output_dir / "logged_data" / _RUN_TIMESTAMP
653695
data_dir.mkdir(parents=True, exist_ok=True)
654696

655-
filepath = data_dir / f"{name}.{ext}"
656-
657697
if isinstance(data, (dict | list)):
698+
filepath = data_dir / f"{name}.json"
658699
with open(filepath, "w") as f:
659700
json.dump(data, f, indent=2)
660701
elif isinstance(data, np.ndarray):
702+
filepath = data_dir / f"{name}.npy"
661703
np.save(filepath, data)
662704
else:
663705
raise ValueError("Unsupported data type for logging")
664706

665-
logging.getLogger(__name__).info(
666-
f"[fancylog] Saved data object: {filepath}"
667-
)
707+
logger.info(f"[fancylog] Saved data object: {filepath}")
668708
return filepath

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ image = [
4545
"tifffile",
4646
]
4747

48+
array = [
49+
"numpy",
50+
]
51+
4852
multiprocessing = ["multiprocessing-logging"]
4953

5054
[build-system]
@@ -124,6 +128,7 @@ python =
124128
extras =
125129
dev
126130
image
131+
array
127132
commands =
128133
pytest -v --color=yes --cov=fancylog --cov-report=xml
129134
"""

tests/tests/test_general.py

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import subprocess
66
import sys
77
from importlib.metadata import distributions
8+
from pathlib import Path
89
from unittest.mock import MagicMock, patch
910

1011
import numpy as np
@@ -472,6 +473,10 @@ def test_log_image_without_metadata(tmp_path, caplog):
472473

473474
assert filepath.exists()
474475

476+
assert filepath.suffix == ".tiff"
477+
loaded_img = tifffile.imread(filepath)
478+
np.testing.assert_array_equal(img, loaded_img)
479+
475480
# Ensure no metadata file
476481
meta_file = filepath.with_name("no_meta_meta.json")
477482
assert not meta_file.exists()
@@ -481,27 +486,24 @@ def test_log_image_without_metadata(tmp_path, caplog):
481486
)
482487

483488

484-
def test_log_data_object_dict_and_list(tmp_path, caplog):
489+
@pytest.mark.parametrize(
490+
"data, name",
491+
[
492+
({"a": 1, "b": 2, "c": 3}, "mydict"),
493+
([1, 2, 3], "mylist"),
494+
],
495+
)
496+
def test_log_data_object_dict_and_list(tmp_path, caplog, data, name):
485497
"""Test logging of dict and list objects."""
486498

487-
data_dict = {"a": 1}
488499
with caplog.at_level(logging.INFO):
489-
filepath_dict = fancylog.log_data_object(data_dict, "mydict", tmp_path)
490-
491-
assert filepath_dict.exists()
492-
493-
with open(filepath_dict) as f:
494-
loaded = json.load(f)
495-
assert loaded == data_dict
496-
497-
data_list = [1, 2, 3]
500+
filepath = fancylog.log_data_object(data, name, tmp_path)
498501

499-
with caplog.at_level(logging.INFO):
500-
filepath_list = fancylog.log_data_object(data_list, "mylist", tmp_path)
502+
assert filepath.exists()
501503

502-
with open(filepath_list) as f:
504+
with open(filepath) as f:
503505
loaded = json.load(f)
504-
assert loaded == data_list
506+
assert loaded == data
505507

506508
assert any(
507509
"[fancylog] Saved data object:" in message
@@ -513,7 +515,7 @@ def test_log_data_object_numpy_array(tmp_path):
513515
"""Test logging of numpy array."""
514516

515517
arr = np.array([[1, 2], [3, 4]])
516-
filepath = fancylog.log_data_object(arr, "array", tmp_path, ext="npy")
518+
filepath = fancylog.log_data_object(arr, "array", tmp_path)
517519

518520
assert filepath.exists()
519521
loaded = np.load(filepath)
@@ -540,19 +542,25 @@ def test_get_default_logging_dir_returns_none(monkeypatch):
540542
logger.handlers = old_handlers
541543

542544

543-
def test_log_image_raises_without_inferable_dir(monkeypatch):
544-
"""Covers ValueError when logging_dir=None and no FileHandler."""
545-
logger = logging.getLogger()
546-
old_handlers = logger.handlers[:]
547-
logger.handlers = []
545+
def test_log_image_no_logging_dir(monkeypatch, tmp_path, caplog):
546+
"""Test that log_image logs fallback message
547+
when get_default_logging_dir() returns None.
548+
"""
549+
monkeypatch.setattr(fancylog, "get_default_logging_dir", lambda: None)
550+
monkeypatch.setattr(type(Path()), "cwd", classmethod(lambda cls: tmp_path))
548551

549-
try:
550-
with pytest.raises(
551-
ValueError, match="Could not infer logging directory"
552-
):
553-
fancylog.log_image(np.zeros((2, 2)), "fail")
554-
finally:
555-
logger.handlers = old_handlers
552+
img = np.zeros((3, 3))
553+
with caplog.at_level(logging.INFO):
554+
filepath = fancylog.log_image(img, "no_logging_dir")
555+
556+
assert filepath.exists()
557+
558+
caplog.set_level(logging.INFO)
559+
assert (
560+
"[fancylog] No default logging directory found. Falling back to"
561+
in message
562+
for message in caplog.text
563+
)
556564

557565

558566
def test_log_image_with_subfolder(tmp_path):
@@ -569,7 +577,7 @@ def test_log_data_object_with_subfolder(tmp_path):
569577
"""Covers subfolder branch in log_data_object."""
570578
arr = np.array([1, 2, 3])
571579
filepath = fancylog.log_data_object(
572-
arr, "with_sub", tmp_path, subfolder="nested", ext="npy"
580+
arr, "with_sub", tmp_path, subfolder="nested"
573581
)
574582
assert "nested" in str(filepath)
575583
assert filepath.exists()

0 commit comments

Comments
 (0)