Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion mhctools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .binding_prediction import BindingPrediction
from .binding_prediction_collection import BindingPredictionCollection
from .logging import set_log_level
from .pred import (
FIELD_BEST_DIRECTIONS,
Kind,
Expand Down Expand Up @@ -87,7 +88,7 @@ def __getattr__(name):
raise AttributeError(
"module %r has no attribute %r" % (__name__, name))

__version__ = "3.31.3"
__version__ = "3.31.4"

__all__ = [
"Prediction",
Expand All @@ -108,6 +109,7 @@ def __getattr__(name):
"parse_annotation_spec",
"BindingPrediction",
"BindingPredictionCollection",
"set_log_level",
"IedbNetMHCcons",
"IedbNetMHCpan",
"IedbSMM",
Expand Down
44 changes: 39 additions & 5 deletions mhctools/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,46 @@
# limitations under the License.

import logging
import logging.config
from importlib.resources import as_file, files


_PACKAGE_LOGGER_NAME = "mhctools"
_PACKAGE_LOGGER = logging.getLogger(_PACKAGE_LOGGER_NAME)
if not any(isinstance(handler, logging.NullHandler)
for handler in _PACKAGE_LOGGER.handlers):
_PACKAGE_LOGGER.addHandler(logging.NullHandler())

_LOG_LEVELS = {
"CRITICAL": logging.CRITICAL,
"ERROR": logging.ERROR,
"WARNING": logging.WARNING,
"WARN": logging.WARNING,
"INFO": logging.INFO,
"DEBUG": logging.DEBUG,
"NOTSET": logging.NOTSET,
}


def get_logger(name):
config_resource = files("mhctools").joinpath("logging.conf")
with as_file(config_resource) as config_path:
logging.config.fileConfig(str(config_path))
return logging.getLogger(name)


def set_log_level(level):
"""Set the log level for mhctools loggers.

Parameters
----------
level : str or int
Logging level such as ``"WARNING"``, ``"INFO"``, or ``logging.DEBUG``.

Returns
-------
logging.Logger
The package logger whose level was set.
"""
if isinstance(level, str):
level_name = level.upper()
if level_name not in _LOG_LEVELS:
raise ValueError("Unknown logging level %r" % level)
level = _LOG_LEVELS[level_name]
_PACKAGE_LOGGER.setLevel(level)
return _PACKAGE_LOGGER
76 changes: 76 additions & 0 deletions tests/test_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import logging
import logging.config

import pytest

import mhctools
from mhctools import logging as mhctools_logging


@pytest.fixture(autouse=True)
def restore_mhctools_logger():
logger = logging.getLogger("mhctools")
old_level = logger.level
old_handlers = list(logger.handlers)
old_propagate = logger.propagate
yield
logger.setLevel(old_level)
logger.handlers[:] = old_handlers
logger.propagate = old_propagate


def test_get_logger_does_not_call_fileconfig(monkeypatch):
def fail_fileconfig(*args, **kwargs):
raise AssertionError("fileConfig must not be called by mhctools")

monkeypatch.setattr(logging.config, "fileConfig", fail_fileconfig)

logger = mhctools_logging.get_logger("mhctools.test")

assert logger.name == "mhctools.test"


def test_get_logger_does_not_reconfigure_root_logger():
root = logging.getLogger()
old_level = root.level
old_handlers = list(root.handlers)

mhctools_logging.get_logger("mhctools.test")

assert root.level == old_level
assert list(root.handlers) == old_handlers


def test_package_logger_has_null_handler_by_default():
handlers = logging.getLogger("mhctools").handlers

assert any(isinstance(handler, logging.NullHandler)
for handler in handlers)


def test_info_logs_are_quiet_by_default(capsys):
logger = mhctools_logging.get_logger("mhctools.test.quiet")

logger.info("should not print")

captured = capsys.readouterr()
assert captured.out == ""
assert captured.err == ""


def test_set_log_level_accepts_string_levels():
logger = mhctools.set_log_level("WARNING")

assert logger is logging.getLogger("mhctools")
assert logger.level == logging.WARNING


def test_set_log_level_accepts_numeric_levels():
logger = mhctools_logging.set_log_level(logging.ERROR)

assert logger.level == logging.ERROR


def test_set_log_level_rejects_unknown_level():
with pytest.raises(ValueError, match="NOTALEVEL"):
mhctools.set_log_level("NOTALEVEL")
Loading