diff --git a/pygmab/python/gmab/__init__.py b/pygmab/python/gmab/__init__.py index c2dbb39..6519e7f 100644 --- a/pygmab/python/gmab/__init__.py +++ b/pygmab/python/gmab/__init__.py @@ -1,3 +1,4 @@ +from gmab import logging from gmab.gmab import Gmab from gmab.search import GmabSearchCV from gmab.study import Study, create_study @@ -5,6 +6,7 @@ __all__ = [ "Gmab", "GmabSearchCV", + "logging", "Study", "create_study", ] diff --git a/pygmab/python/gmab/logging.py b/pygmab/python/gmab/logging.py new file mode 100644 index 0000000..f63f35c --- /dev/null +++ b/pygmab/python/gmab/logging.py @@ -0,0 +1,65 @@ +import logging +import sys +from logging import CRITICAL, DEBUG, ERROR, INFO, WARNING + +__all__ = [ + "CRITICAL", + "DEBUG", + "ERROR", + "INFO", + "WARNING", +] + +_default_handler: logging.Handler | None = None +_default_fmt: str = "[%(asctime)s] %(levelname)-8s - %(name)s - %(message)s" + + +def _get_library_root_logger() -> logging.Logger: + return logging.getLogger(__name__.split(".")[0]) + + +def _configure_library_root_logger() -> None: + global _default_handler + + if _default_handler: + return # This library has already configured the library root logger. + + _default_handler = logging.StreamHandler(sys.stderr) + _default_handler.setFormatter(logging.Formatter(_default_fmt)) + library_root_logger: logging.Logger = _get_library_root_logger() + library_root_logger.addHandler(_default_handler) + library_root_logger.setLevel(logging.INFO) + # library_root_logger.propagate = False + + +def get_logger(name: str) -> logging.Logger: + """Return a logger with the specified name.""" + _configure_library_root_logger() + return logging.getLogger(name) + + +def set_level(level: int) -> None: + """Set the level for gmab's root logger. + + Args: + verbosity: + Logging level, e.g., ``gmab.logging.DEBUG`` or ``gmab.logging.INFO``. + + """ + _configure_library_root_logger() + _get_library_root_logger().setLevel(level) + + +def disable() -> None: + """Disable the default handler of gmab's root logger""" + global _default_handler + if _default_handler: + library_root_logger: logging.Logger = _get_library_root_logger() + library_root_logger.removeHandler(_default_handler) + _default_handler = None + + +def enable() -> None: + """Enable the default handler of gmab's root logger""" + _configure_library_root_logger() + _get_library_root_logger() diff --git a/pygmab/python/gmab/study/study.py b/pygmab/python/gmab/study/study.py index cda35e2..2534935 100644 --- a/pygmab/python/gmab/study/study.py +++ b/pygmab/python/gmab/study/study.py @@ -1,7 +1,10 @@ from collections.abc import Callable +from gmab import logging from gmab.gmab import Gmab +_logger = logging.get_logger(__name__) + class Study: """A Study corresponds to an optimization task, i.e. a set of trials. @@ -54,6 +57,7 @@ def optimize( """ gmab = Gmab(func, bounds) self._best_trial = gmab.optimize(n_simulations) + _logger.info("completed") def create_study() -> Study: diff --git a/pygmab/tests/study_tests/test_study.py b/pygmab/tests/study_tests/test_study.py index a9d1f97..266c6f9 100644 --- a/pygmab/tests/study_tests/test_study.py +++ b/pygmab/tests/study_tests/test_study.py @@ -1,6 +1,6 @@ -import pytest - import gmab +import pytest +from pytest import LogCaptureFixture def rosenbrock_function(number: list): @@ -12,7 +12,7 @@ def rosenbrock_function(number: list): ) -def test_best_trial(): +def test_best_trial(caplog: LogCaptureFixture): study = gmab.create_study() # best_trial requires running study.optimize() @@ -22,5 +22,7 @@ def test_best_trial(): bounds = [(-5, 10), (-5, 10)] n_simulations = 10_000 study.optimize(rosenbrock_function, bounds, n_simulations) + assert "completed" in caplog.text # integrates logging + result = study.best_trial - assert result == [1, 1] + assert result == [1, 1] # returns expected result diff --git a/pygmab/tests/test_logging.py b/pygmab/tests/test_logging.py new file mode 100644 index 0000000..ca3e2f5 --- /dev/null +++ b/pygmab/tests/test_logging.py @@ -0,0 +1,36 @@ +from gmab import logging +from pytest import CaptureFixture, LogCaptureFixture + + +def test_get_logger(caplog: LogCaptureFixture) -> None: + logger = logging.get_logger("gmab.foo") + + logger.info("hello") + assert "hello" in caplog.text # Checks logging with a simple example + + logger.debug("bye") + assert "bye" not in caplog.text # DEBUG is not displayed per default + + +def test_set_level(caplog: LogCaptureFixture) -> None: + logger = logging.get_logger("gmab.foo") + + logging.set_level(logging.DEBUG) + logger.debug("debug_msg") + assert "debug_msg" in caplog.text # level is set to DEBUG + + logging.set_level(logging.CRITICAL) + logger.error("error_msg") + assert "error_msg" not in caplog.text # level is set to CRITICAL + + +def test_disable(capsys: CaptureFixture) -> None: + logger = logging.get_logger("gmab.foo") + + logging.disable() + logger.info("hello") + assert "hello" not in capsys.readouterr().err # Logging is disabled + + logging.enable() + logger.info("bye") + assert "bye" in capsys.readouterr().err # Logging is enabled