Skip to content

Commit

Permalink
Add level option to setup_logging (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
PicoCentauri authored Apr 3, 2024
1 parent 98adcf9 commit 4a1e53f
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 27 deletions.
2 changes: 2 additions & 0 deletions docs/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Changelog
=========

* Replace Boolean ``debug`` option in ``setup_logging`` by more flexible integer
``level`` parameter.

v0.1.29 (2024-03-21)
------------------------------------------
Expand Down
15 changes: 11 additions & 4 deletions src/mdacli/cli.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*-
#
# Copyright (c) 2021 Authors and contributors
# Copyright (c) 2024 Authors and contributors
#
# Released under the GNU Public Licence, v2 or any higher version
# SPDX-License-Identifier: GPL-2.0-or-later
Expand Down Expand Up @@ -104,13 +104,20 @@ def cli(name,

args = ap.parse_args()

# Set the logging level based on the verbose argument
# If verbose is not an argument, default to WARNING
if not hasattr(args, "verbose") or not args.verbose:
level = logging.WARNING
else:
level = logging.INFO

if args.debug:
args.verbose = True
level = logging.DEBUG
else:
# Ignore all warnings if not in debug mode
# Ignore all warnings if not in debug mode, because MDA is noisy
warnings.filterwarnings("ignore")

with setup_logging(logger, logfile=args.logfile, debug=args.debug):
with setup_logging(logger, logfile=args.logfile, level=level):
# Execute the main client interface.
try:
analysis_callable = args.analysis_callable
Expand Down
23 changes: 12 additions & 11 deletions src/mdacli/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@


@contextlib.contextmanager
def setup_logging(logobj, logfile=None, debug=False):
def setup_logging(logobj, logfile=None, level=logging.WARNING):
"""
Create a logging environment for a given logobj.
Expand All @@ -25,19 +25,20 @@ def setup_logging(logobj, logfile=None, debug=False):
A logging instance
logfile : str
Name of the log file
debug : bool
If ``True`` detailed debug logs inludcing filename and function name
are displayed. If ``False`` only the message logged from
errors, warnings and infos will be displayed.
level : int
Set the root logger level to the specified level. If for example set
to :py:obj:`logging.DEBUG` detailed debug logs inludcing filename and
function name are displayed. For :py:obj:`logging.INFO only the message
logged from errors, warnings and infos will be displayed.
"""
try:
format = '{message}'
if debug:
format = "[{levelname}] {filename}:{name}:{funcName}:{lineno}: " \
+ format
level = logging.DEBUG
if level == logging.DEBUG:
format = (
"[{levelname}] {filename}:{name}:{funcName}:{lineno}: "
"{message}"
)
else:
level = logging.INFO
format = "{message}"

logging.basicConfig(format=format,
handlers=[logging.StreamHandler(sys.stdout)],
Expand Down
16 changes: 16 additions & 0 deletions tests/run_tester
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env python3
# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*-
#
# Copyright (c) 2024 Authors and contributors
#
# Released under the GNU Public Licence, v2 or any higher version
# SPDX-License-Identifier: GPL-2.0-or-later
import re
import sys

from tester.__main__ import main


if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())
71 changes: 63 additions & 8 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,38 @@
"""Test mdacli cli."""

import subprocess
import sys
from pathlib import Path

import pytest
from MDAnalysisTests.datafiles import TPR, XTC


tester_class = (Path('.').absolute() / 'tests/run_tester').as_posix()


def test_required_args():
"""Test that there is a module given."""
with pytest.raises(subprocess.CalledProcessError):
subprocess.check_call(['mda'])
subprocess.check_call(["mda"])


def test_wrong_module():
"""Test for a non existent module."""
with pytest.raises(subprocess.CalledProcessError):
subprocess.check_call(['mda', 'foo'])
subprocess.check_call(["mda", "foo"])


@pytest.mark.parametrize('args', ("version", "debug", "help"))
@pytest.mark.parametrize("args", ("version", "debug", "help"))
def test_extra_options(args):
"""Test for a ab extra option."""
subprocess.check_call(['mda', '--' + args])
subprocess.check_call(["mda", "--" + args])


@pytest.mark.parametrize('args', ("RMSF", "rmsf"))
@pytest.mark.parametrize("args", ("RMSF", "rmsf"))
def test_case_insensitive(args):
"""Test for beeing case insensitive."""
subprocess.check_call(['mda', args, "-h"])
"""Test for being case insensitive."""
subprocess.check_call(["mda", args, "-h"])


@pytest.mark.parametrize('args', ("RMSF", "rmsf"))
Expand All @@ -48,4 +53,54 @@ def test_running_analysis(tmpdir):
"""Test running a complete analysis."""
with tmpdir.as_cwd():
subprocess.check_call(
['mda', "rmsf", "-s", TPR, "-f", XTC, "-atomgroup", "all"])
["mda", "rmsf", "-s", TPR, "-f", XTC, "-atomgroup", "all"]
)


def test_verbosity_level_warning(caplog):
"""Test the log level warning."""
# This should only print warning messages
output = subprocess.check_output(
[sys.executable, tester_class,
"tester", "-s", TPR, "-f", XTC, "-atomgroup", "all"],
text=True,
)
assert "This is a warning" in output
# Cross-check that info and debug messages are not printed
assert "This is a debug message" not in output
assert "This is an info message" not in output


def test_verbosity_level_info(caplog):
"""Test the log level info."""
# This should only print warning and info messages
output = subprocess.check_output(
[
sys.executable, tester_class,
"tester", "-s", TPR, "-f", XTC,
"-atomgroup", "all",
"-v",
],
text=True,
)
assert "This is an info message" in output
assert "This is a warning" in output
# Cross-check that debug messages are not printed
assert "This is a debug message" not in output


def test_verbosity_level_debug(caplog):
"""Test the log level debug."""
# This should print all messages
output = subprocess.check_output(
[
sys.executable, tester_class, "--debug",
"tester", "-s", TPR, "-f", XTC,
"-atomgroup", "all",
"-v",
],
text=True,
)
assert "This is an info message" in output
assert "This is a warning" in output
assert "This is a debug message" in output
8 changes: 4 additions & 4 deletions tests/test_logger.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*-
#
# Copyright (c) 2021 Authors and contributors
# Copyright (c) 2024 Authors and contributors
#
# Released under the GNU Public Licence, v2 or any higher version
# SPDX-License-Identifier: GPL-2.0-or-later
Expand All @@ -20,7 +20,7 @@ def test_default_log(self, caplog):
logger = logging.getLogger("test")
with mdacli.logger.setup_logging(logger,
logfile=None,
debug=False):
level=logging.INFO):
logger.info("foo")
assert "foo" in caplog.text

Expand All @@ -33,7 +33,7 @@ def test_info_log(self, tmpdir, caplog):
# is created by the function.
with mdacli.logger.setup_logging(logger,
logfile="logfile",
debug=False):
level=logging.INFO):
logger.info("foo")
assert "foo" in caplog.text
with open("logfile.log", "r") as f:
Expand All @@ -48,7 +48,7 @@ def test_debug_log(self, tmpdir, caplog):
with tmpdir.as_cwd():
with mdacli.logger.setup_logging(logger,
logfile="logfile",
debug=True):
level=logging.DEBUG):
logger.info("foo")
assert "test:test_logger.py:52 foo\n" in caplog.text

Expand Down
10 changes: 10 additions & 0 deletions tests/tester/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env python3
# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*-
#
# Copyright (c) 2024 Authors and contributors
#
# Released under the GNU Public Licence, v2 or any higher version
# SPDX-License-Identifier: GPL-2.0-or-later
"""Make the tester module available to mdacli."""

from .tester_class import Tester # noqa
29 changes: 29 additions & 0 deletions tests/tester/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env python
# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*-
#
# Copyright (c) 2024 Authors and contributors
# (see the AUTHORS.rst file for the full list of names)
#
# Released under the GNU Public Licence, v3 or any higher version
# SPDX-License-Identifier: GPL-3.0-or-later
"""Test module for mdacli."""

from MDAnalysis.analysis.base import AnalysisBase

from mdacli import cli


def main():
"""Execute main CLI entry point."""
cli(
name="Tester",
module_list=["tester"],
base_class=AnalysisBase,
version=0.0,
description="test",
ignore_warnings=True,
)


if __name__ == "__main__":
main()
46 changes: 46 additions & 0 deletions tests/tester/tester_class.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*-
#
# Copyright (c) 2024 Authors and contributors
#
# Released under the GNU Public Licence, v2 or any higher version
# SPDX-License-Identifier: GPL-2.0-or-later
"""Mock module for mdacli to test logging."""

import logging

from MDAnalysis.analysis.base import AnalysisBase


logger = logging.getLogger(__name__)


class Tester(AnalysisBase):
"""Mock class for mdacli. Implements only the minimum requirements.
Currently only logs messages at different levels to check the verbosity and
debug flags in the CLI.
Parameters
----------
atomgroup : AtomGroup or Universe
"""

def __init__(self, atomgroup, **kwargs):
"""Initialise the Tester class."""
super(Tester, self).__init__(atomgroup.universe.trajectory, **kwargs)
logger.info("This is an info message")
logger.warn("This is a warning")
logger.debug("This is a debug message")

def _prepare(self):
"""Prepare the analysis."""
pass

def _single_frame(self):
"""Analyse a single frame."""
pass

def _conclude(self):
"""Conclude the analysis."""
pass

0 comments on commit 4a1e53f

Please sign in to comment.