From c18393e5df5e9d788b31fe610d55445855a4a1ae Mon Sep 17 00:00:00 2001 From: Tal Einat <532281+taleinat@users.noreply.github.com> Date: Fri, 13 May 2022 22:47:43 +0300 Subject: [PATCH 1/5] support setting output verbosity This changes the internal use of echo to use a logger instead of sys.stdout.write. This uses the stdlib logging library, with the common convention of logger = logging.getLogger(__name__), so that all watchdog logs are under the "watchdog" logging context. Finally, this adds -q/--quiet and -v/--verbose command line options to watchmedo. --- src/watchdog/tricks/__init__.py | 16 +++++++++++----- src/watchdog/watchmedo.py | 32 +++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/watchdog/tricks/__init__.py b/src/watchdog/tricks/__init__.py index 300d3597..e15f970c 100644 --- a/src/watchdog/tricks/__init__.py +++ b/src/watchdog/tricks/__init__.py @@ -41,6 +41,8 @@ """ +import functools +import logging import os import signal import subprocess @@ -50,6 +52,10 @@ from watchdog.events import PatternMatchingEventHandler +logger = logging.getLogger(__name__) +echo_events = functools.partial(echo.echo, write=lambda msg: logger.info(msg)) + + class Trick(PatternMatchingEventHandler): """Your tricks should subclass this class.""" @@ -80,19 +86,19 @@ class LoggerTrick(Trick): def on_any_event(self, event): pass - @echo.echo + @echo_events def on_modified(self, event): pass - @echo.echo + @echo_events def on_deleted(self, event): pass - @echo.echo + @echo_events def on_created(self, event): pass - @echo.echo + @echo_events def on_moved(self, event): pass @@ -202,7 +208,7 @@ def kill_process(stop_signal): pass self.process = None - @echo.echo + @echo_events def on_any_event(self, event): self.stop() self.start() diff --git a/src/watchdog/watchmedo.py b/src/watchdog/watchmedo.py index f610268e..9510a129 100755 --- a/src/watchdog/watchmedo.py +++ b/src/watchdog/watchmedo.py @@ -60,7 +60,8 @@ def _split_lines(self, text, width): return text.splitlines() -epilog = '''Copyright 2011 Yesudeep Mangalapilly . +epilog = '''\ +Copyright 2011 Yesudeep Mangalapilly . Copyright 2012 Google, Inc & contributors. Licensed under the terms of the Apache license, version 2.0. Please see @@ -69,6 +70,7 @@ def _split_lines(self, text, width): cli = ArgumentParser(epilog=epilog, formatter_class=HelpFormatter) cli.add_argument('--version', action='version', version=VERSION_STRING) subparsers = cli.add_subparsers(dest='top_command') +command_parsers = {} def argument(*name_or_flags, **kwargs): @@ -94,6 +96,11 @@ def decorator(func): description=desc, aliases=cmd_aliases, formatter_class=HelpFormatter) + verbosity_group = parser.add_mutually_exclusive_group() + verbosity_group.add_argument('-q', '--quiet', dest='verbosity', + action='append_const', const=-1) + verbosity_group.add_argument('-v', '--verbose', dest='verbosity', + action='append_const', const=1) for arg in args: parser.add_argument(*arg[0], **arg[1]) parser.set_defaults(func=func) @@ -397,7 +404,8 @@ def log(args): from watchdog.tricks import LoggerTrick if args.trace: - echo.echo_class(LoggerTrick) + class_module_logger = logging.getLogger(LoggerTrick.__module__) + echo.echo_class(LoggerTrick, write=lambda msg: class_module_logger.info(msg)) patterns, ignore_patterns =\ parse_patterns(args.patterns, args.ignore_patterns) @@ -637,9 +645,23 @@ def main(): args = cli.parse_args() if args.command is None: cli.print_help() - else: - args.func(args) + return 1 + + verbosity = sum(args.verbosity) + if verbosity < -1: + print("Error: -q/--quiet may be specified only once.", file=sys.stderr) + command_parsers[args.top_command].print_help() + return 1 + if verbosity > 2: + print("Error: -v/--verbose may be specified up to 2 times.", file=sys.stderr) + command_parsers[args.top_command].print_help() + return 1 + log_level = ['ERROR', 'WARNING', 'INFO', 'DEBUG'][1 + verbosity] + logging.getLogger('watchdog').setLevel(log_level) + + args.func(args) + return 0 if __name__ == '__main__': - main() + sys.exit(main()) From c947b4415b8a16fa89d8c51d8ef7f3e1d342bf36 Mon Sep 17 00:00:00 2001 From: Tal Einat <532281+taleinat@users.noreply.github.com> Date: Sun, 15 May 2022 11:54:57 +0300 Subject: [PATCH 2/5] fix handling no verbosity flags and add tests --- src/watchdog/watchmedo.py | 21 +++++++++++------- tests/test_0_watchmedo.py | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/watchdog/watchmedo.py b/src/watchdog/watchmedo.py index 23335b72..486b90dc 100755 --- a/src/watchdog/watchmedo.py +++ b/src/watchdog/watchmedo.py @@ -640,6 +640,15 @@ def handler_termination_signal(_signum, _frame): handler.stop() +def _get_log_level_from_args(args): + verbosity = sum(args.verbosity or []) + if verbosity < -1: + raise Exception("-q/--quiet may be specified only once.") + if verbosity > 2: + raise Exception("-v/--verbose may be specified up to 2 times.") + return ['ERROR', 'WARNING', 'INFO', 'DEBUG'][1 + verbosity] + + def main(): """Entry-point function.""" args = cli.parse_args() @@ -647,16 +656,12 @@ def main(): cli.print_help() return 1 - verbosity = sum(args.verbosity) - if verbosity < -1: - print("Error: -q/--quiet may be specified only once.", file=sys.stderr) - command_parsers[args.top_command].print_help() - return 1 - if verbosity > 2: - print("Error: -v/--verbose may be specified up to 2 times.", file=sys.stderr) + try: + log_level = _get_log_level_from_args(args) + except Exception as exc: + print("Error: " + exc.args[0], file=sys.stderr) command_parsers[args.top_command].print_help() return 1 - log_level = ['ERROR', 'WARNING', 'INFO', 'DEBUG'][1 + verbosity] logging.getLogger('watchdog').setLevel(log_level) args.func(args) diff --git a/tests/test_0_watchmedo.py b/tests/test_0_watchmedo.py index 2719122f..664871fa 100644 --- a/tests/test_0_watchmedo.py +++ b/tests/test_0_watchmedo.py @@ -96,6 +96,52 @@ def test_shell_command_arg_parsing(): assert args.command == "'cmd'" +@pytest.mark.parametrize("cmdline", [ + ["auto-restart", "-d", ".", "cmd"], + ["log", "."] +]) +@pytest.mark.parametrize("verbosity", [ + ([], "WARNING"), + (["-q"], "ERROR"), + (["--quiet"], "ERROR"), + (["-v"], "INFO"), + (["--verbose"], "INFO"), + (["-vv"], "DEBUG"), + (["-v", "-v"], "DEBUG"), + (["--verbose", "-v"], "DEBUG"), +]) +def test_valid_verbosity(cmdline, verbosity): + (verbosity_cmdline_args, expected_log_level) = verbosity + cmd = [cmdline[0], *verbosity_cmdline_args, *cmdline[1:]] + args = watchmedo.cli.parse_args(cmd) + log_level = watchmedo._get_log_level_from_args(args) + assert log_level == expected_log_level + + +@pytest.mark.parametrize("cmdline", [ + ["auto-restart", "-d", ".", "cmd"], + ["log", "."] +]) +@pytest.mark.parametrize("verbosity_cmdline_args", [ + ["-q", "-v"], + ["-v", "-q"], + ["-qq"], + ["-q", "-q"], + ["--quiet", "--quiet"], + ["--quiet", "-q"], + ["-vvv"], + ["-vvvv"], + ["-v", "-v", "-v"], + ["-vv", "-v"], + ["--verbose", "-vv"], +]) +def test_invalid_verbosity(cmdline, verbosity_cmdline_args): + cmd = [cmdline[0], *verbosity_cmdline_args, *cmdline[1:]] + with pytest.raises((Exception, SystemExit)): + args = watchmedo.cli.parse_args(cmd) + watchmedo._get_log_level_from_args(args) + + @pytest.mark.parametrize("command", ["tricks-from", "tricks"]) def test_tricks_from_file(command, tmp_path): tricks_file = tmp_path / "tricks.yaml" From 382f64e6d3e4af1bf80c03dad39cda6422f38f1a Mon Sep 17 00:00:00 2001 From: Tal Einat <532281+taleinat@users.noreply.github.com> Date: Sun, 15 May 2022 12:02:59 +0300 Subject: [PATCH 3/5] add a changelog entry --- changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.rst b/changelog.rst index 33f8edb2..71765ad1 100644 --- a/changelog.rst +++ b/changelog.rst @@ -13,6 +13,7 @@ Changelog - [watchmedo] Fix broken parsing of boolean arguments. (`#855 `_) - [watchmedo] Fix broken parsing of commands from ``auto-restart``, and ``shell-command``. (`#855 `_) - [inotify] Fix hang when unscheduling watch on a path in an unmounted filesystem. (`#869 `_) +- [watchmedo] Support setting verbosity level via ``-q/--quiet`` and ``-v/--verbose`` arguments. (`#889 `_) - Thanks to our beloved contributors: @taleinat, @kianmeng, @palfrey, @IlayRosenberg, @BoboTiG 2.1.7 From 6e338e75a1669cdf240234adfd746adc714158c0 Mon Sep 17 00:00:00 2001 From: Tal Einat <532281+taleinat@users.noreply.github.com> Date: Sun, 15 May 2022 12:11:32 +0300 Subject: [PATCH 4/5] fix: add missing adding to command_parsers --- src/watchdog/watchmedo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/watchdog/watchmedo.py b/src/watchdog/watchmedo.py index 486b90dc..e6beaa90 100755 --- a/src/watchdog/watchmedo.py +++ b/src/watchdog/watchmedo.py @@ -96,6 +96,7 @@ def decorator(func): description=desc, aliases=cmd_aliases, formatter_class=HelpFormatter) + command_parsers[name] = parser verbosity_group = parser.add_mutually_exclusive_group() verbosity_group.add_argument('-q', '--quiet', dest='verbosity', action='append_const', const=-1) From 1f6ff1bfeb3b7ab42504ac6cb6b9ae5e5c0acd90 Mon Sep 17 00:00:00 2001 From: Tal Einat <532281+taleinat@users.noreply.github.com> Date: Sun, 15 May 2022 12:16:41 +0300 Subject: [PATCH 5/5] use a custom exception class for log level "parsing" --- src/watchdog/watchmedo.py | 10 +++++++--- tests/test_0_watchmedo.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/watchdog/watchmedo.py b/src/watchdog/watchmedo.py index e6beaa90..20d0f772 100755 --- a/src/watchdog/watchmedo.py +++ b/src/watchdog/watchmedo.py @@ -641,12 +641,16 @@ def handler_termination_signal(_signum, _frame): handler.stop() +class LogLevelException(Exception): + pass + + def _get_log_level_from_args(args): verbosity = sum(args.verbosity or []) if verbosity < -1: - raise Exception("-q/--quiet may be specified only once.") + raise LogLevelException("-q/--quiet may be specified only once.") if verbosity > 2: - raise Exception("-v/--verbose may be specified up to 2 times.") + raise LogLevelException("-v/--verbose may be specified up to 2 times.") return ['ERROR', 'WARNING', 'INFO', 'DEBUG'][1 + verbosity] @@ -659,7 +663,7 @@ def main(): try: log_level = _get_log_level_from_args(args) - except Exception as exc: + except LogLevelException as exc: print("Error: " + exc.args[0], file=sys.stderr) command_parsers[args.top_command].print_help() return 1 diff --git a/tests/test_0_watchmedo.py b/tests/test_0_watchmedo.py index 664871fa..3ab0ac96 100644 --- a/tests/test_0_watchmedo.py +++ b/tests/test_0_watchmedo.py @@ -137,7 +137,7 @@ def test_valid_verbosity(cmdline, verbosity): ]) def test_invalid_verbosity(cmdline, verbosity_cmdline_args): cmd = [cmdline[0], *verbosity_cmdline_args, *cmdline[1:]] - with pytest.raises((Exception, SystemExit)): + with pytest.raises((watchmedo.LogLevelException, SystemExit)): args = watchmedo.cli.parse_args(cmd) watchmedo._get_log_level_from_args(args)