diff --git a/src/spatialdata_plot/__init__.py b/src/spatialdata_plot/__init__.py index fd8c82c0..f336f11c 100644 --- a/src/spatialdata_plot/__init__.py +++ b/src/spatialdata_plot/__init__.py @@ -1,7 +1,8 @@ from importlib.metadata import version from . import pl +from ._logging import set_verbosity -__all__ = ["pl"] +__all__ = ["pl", "set_verbosity"] __version__ = version("spatialdata-plot") diff --git a/src/spatialdata_plot/_logging.py b/src/spatialdata_plot/_logging.py index 364cba27..e73fd949 100644 --- a/src/spatialdata_plot/_logging.py +++ b/src/spatialdata_plot/_logging.py @@ -6,6 +6,8 @@ from contextlib import contextmanager from typing import TYPE_CHECKING +from ._settings import settings + if TYPE_CHECKING: # pragma: no cover from _pytest.logging import LogCaptureFixture @@ -15,10 +17,14 @@ def _setup_logger() -> "logging.Logger": from rich.logging import RichHandler logger = logging.getLogger(__name__) - logger.setLevel(logging.INFO) + + level = logging.INFO if settings.verbose else logging.WARNING + logger.setLevel(level) + console = Console(force_terminal=True) if console.is_jupyter is True: console.is_jupyter = False + ch = RichHandler(show_path=False, console=console, show_time=False) logger.addHandler(ch) @@ -69,3 +75,8 @@ def logger_warns( if not any(pattern.search(r.getMessage()) for r in records): msgs = [r.getMessage() for r in records] raise AssertionError(f"Did not find log matching {match!r} in records: {msgs!r}") + + +def set_verbosity(verbose: bool = True) -> None: + level = logging.INFO if verbose else logging.WARNING + logger.setLevel(level) diff --git a/src/spatialdata_plot/_settings.py b/src/spatialdata_plot/_settings.py new file mode 100644 index 00000000..ff6555f0 --- /dev/null +++ b/src/spatialdata_plot/_settings.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass + + +@dataclass +class Settings: + verbose: bool = False + + +settings = Settings() diff --git a/tests/pl/test_logging.py b/tests/pl/test_logging.py new file mode 100644 index 00000000..d07a717e --- /dev/null +++ b/tests/pl/test_logging.py @@ -0,0 +1,57 @@ +import io +import logging + +from spatialdata import SpatialData + +import spatialdata_plot +from tests.conftest import PlotTester, PlotTesterMeta + + +class TestLogging(PlotTester, metaclass=PlotTesterMeta): + def test_default_verbosity_hides_info(self, sdata_blobs: SpatialData): + """INFO logs should be hidden by default.""" + spatialdata_plot.set_verbosity(False) # ensure default verbosity + + # Replace all handlers temporarily + logger = spatialdata_plot._logging.logger + original_handlers = logger.handlers[:] + logger.handlers = [] + + log_stream = io.StringIO() + handler = logging.StreamHandler(log_stream) + handler.setLevel(logging.INFO) + logger.addHandler(handler) + + # Run the function + sdata_blobs.pl.render_shapes("blobs_circles", method="datashader").pl.show() + + # Check captured logs — should NOT contain the datashader info message + logs = log_stream.getvalue() + assert "Using 'datashader' backend" not in logs + + # Restore original handlers + logger.handlers = original_handlers + + def test_verbose_verbosity_shows_info(self, sdata_blobs): + spatialdata_plot.set_verbosity(True) + + # Replace all handlers temporarily + logger = spatialdata_plot._logging.logger + original_handlers = logger.handlers[:] + logger.handlers = [] + + log_stream = io.StringIO() + handler = logging.StreamHandler(log_stream) + handler.setLevel(logging.INFO) + logger.addHandler(handler) + + # Run the function + sdata_blobs.pl.render_shapes("blobs_circles", method="datashader").pl.show() + + # Check captured logs + logs = log_stream.getvalue() + assert "Using 'datashader' backend" in logs + + # Restore original handlers + logger.handlers = original_handlers + spatialdata_plot.set_verbosity(False)