Skip to content

Commit addaf36

Browse files
Adds warning filtering to stderr (#387)
* Adds a new context manager that prints QIIME2Warnings to stderr instead of to the log * Chaining context managers cleanly * QIIME2Warning -> RachisWarning, don't show if --quiet * Switching to RachisWarning * RachisWarning visibility test * merge conflicts * move import --------- Co-authored-by: Colin Wood <woodcolin38@gmail.com>
1 parent 1840b29 commit addaf36

3 files changed

Lines changed: 91 additions & 9 deletions

File tree

q2cli/commands.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -405,13 +405,16 @@ def __call__(self, **kwargs):
405405
"""Called when user hits return, **kwargs are Dict[click_names, Obj]"""
406406
import os
407407
import click
408+
from contextlib import nullcontext
408409

409410
import qiime2.util
410411
from qiime2.core.cache import Cache
411412
from qiime2.sdk import ResultCollection
412413

413-
from q2cli.util import (output_in_cache, _get_cache_path_and_key,
414-
get_default_recycle_pool)
414+
from q2cli.util import (
415+
output_in_cache, _get_cache_path_and_key, get_default_recycle_pool,
416+
capture_rachis_warnings
417+
)
415418
from q2cli.core.artifact_cache_global import (
416419
get_used_artifact_cache, unset_used_artifact_cache)
417420

@@ -547,7 +550,10 @@ def __call__(self, **kwargs):
547550

548551
cleanup_logfile = False
549552
try:
550-
with qiime2.util.redirected_stdio(stdout=log, stderr=log):
553+
with (
554+
capture_rachis_warnings() if not quiet else nullcontext(),
555+
qiime2.util.redirected_stdio(stdout=log, stderr=log)
556+
):
551557
if parallel:
552558
from qiime2.sdk.parallel_config import \
553559
(load_config_from_file, ParallelConfig)
@@ -557,7 +563,7 @@ def __call__(self, **kwargs):
557563
parallel_config = ParallelConfig()
558564
else:
559565
config, mapping = \
560-
load_config_from_file(parallel_config_fp)
566+
load_config_from_file(parallel_config_fp)
561567
parallel_config = ParallelConfig(config, mapping)
562568

563569
with parallel_config:

q2cli/tests/test_cli.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@
66
# The full license is in the file LICENSE, distributed with this software.
77
# ----------------------------------------------------------------------------
88

9+
import click
10+
import contextlib
11+
import errno
912
import os.path
13+
from pathlib import Path
14+
import shutil
15+
import subprocess
16+
import tempfile
1017
import unittest
11-
import contextlib
1218
import unittest.mock
13-
import tempfile
14-
import shutil
15-
import click
16-
import errno
1719

1820
from click.testing import CliRunner
1921
from qiime2.core.cache import get_cache
@@ -484,6 +486,36 @@ def test_get_citations(self):
484486
self.assertEqual(result.exit_code, 0)
485487
self.assertEqual(result.output, EXPECTED_CITATIONS)
486488

489+
def test_rachis_warning_is_always_visible(self):
490+
'''
491+
Tests that the `RachisWarning` warning type gets passed to the standard
492+
error stream even when --verbose is not set. Also ensures that such
493+
errors are still visible with --verbose, and not visible with --quiet.
494+
'''
495+
with tempfile.TemporaryDirectory() as tempdir:
496+
base_command = [
497+
'qiime', 'dummy-plugin', 'raises-rachis-warning', '--o-output',
498+
Path(tempdir) / 'output.qza'
499+
]
500+
501+
# no verbose, no quiet
502+
output = subprocess.run(
503+
base_command, capture_output=True, text=True
504+
)
505+
self.assertIn('This is an important warning', output.stderr)
506+
507+
# verbose
508+
output = subprocess.run(
509+
base_command + ['--verbose'], capture_output=True, text=True
510+
)
511+
self.assertIn('This is an important warning', output.stderr)
512+
513+
# quiet
514+
output = subprocess.run(
515+
base_command + ['--quiet'], capture_output=True, text=True
516+
)
517+
self.assertNotIn('This is an important warning', output.stderr)
518+
487519

488520
class TestMigrated(unittest.TestCase):
489521
def setUp(self):

q2cli/util.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
# The full license is in the file LICENSE, distributed with this software.
77
# ----------------------------------------------------------------------------
88

9+
import contextlib
10+
911

1012
class OutOfDisk(Exception):
1113
pass
@@ -531,3 +533,45 @@ def get_default_recycle_pool(plugin_action):
531533

532534
return f'recycle_{plugin_action}_' \
533535
f'{sha1(plugin_action.encode("utf-8")).hexdigest()}'
536+
537+
538+
@contextlib.contextmanager
539+
def capture_rachis_warnings():
540+
'''
541+
Intercepts `RachisWarning` warnings and delays writing them to standard
542+
error until the context is exited. This is useful if the standard error
543+
stream is temporarily redirected at some point within this context manager.
544+
'''
545+
import sys
546+
import warnings
547+
from qiime2.core.exceptions import RachisWarning
548+
549+
captured_warnings = []
550+
551+
warnings_showwarning = warnings.showwarning
552+
553+
def _capture_showwarning(
554+
message, category, filename, lineno, file=None, line=None
555+
):
556+
if issubclass(category, RachisWarning):
557+
captured_warnings.append((str(message), filename, lineno))
558+
else:
559+
warnings_showwarning(
560+
message, category, filename, lineno, file, line
561+
)
562+
563+
warnings.showwarning = _capture_showwarning
564+
565+
yield
566+
567+
if captured_warnings:
568+
sys.stderr.write('\033[33m') # yellow text
569+
sys.stderr.write('\nRACHIS WARNING(S):\n\n')
570+
for warning, file, line in captured_warnings:
571+
sys.stderr.write(f'{warning}\n')
572+
sys.stderr.write(f'[{file}: {line}]\n\n')
573+
574+
sys.stderr.write('\033[0m') # reset text color
575+
sys.stderr.flush()
576+
577+
warnings.showwarning = warnings_showwarning

0 commit comments

Comments
 (0)