Skip to content

Commit

Permalink
[watchmedo] Support setting output verbosity with -q, --quiet, and …
Browse files Browse the repository at this point in the history
…`-v, --verbose` (#889)

* 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.

* fix handling no verbosity flags and add tests

* add a changelog entry

* fix: add missing adding to command_parsers

* use a custom exception class for log level "parsing"
  • Loading branch information
taleinat committed May 15, 2022
1 parent 4baea36 commit 9a5df95
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 10 deletions.
1 change: 1 addition & 0 deletions changelog.rst
Expand Up @@ -13,6 +13,7 @@ Changelog
- [watchmedo] Fix broken parsing of boolean arguments. (`#855 <https://github.com/gorakhargosh/watchdog/issues/855>`_)
- [watchmedo] Fix broken parsing of commands from ``auto-restart``, and ``shell-command``. (`#855 <https://github.com/gorakhargosh/watchdog/issues/855>`_)
- [inotify] Fix hang when unscheduling watch on a path in an unmounted filesystem. (`#869 <https://github.com/gorakhargosh/watchdog/pull/869>`_)
- [watchmedo] Support setting verbosity level via ``-q/--quiet`` and ``-v/--verbose`` arguments. (`#889 <https://github.com/gorakhargosh/watchdog/pull/889>`_)
- Thanks to our beloved contributors: @taleinat, @kianmeng, @palfrey, @IlayRosenberg, @BoboTiG

2.1.7
Expand Down
16 changes: 11 additions & 5 deletions src/watchdog/tricks/__init__.py
Expand Up @@ -41,6 +41,8 @@
"""

import functools
import logging
import os
import signal
import subprocess
Expand All @@ -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."""
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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()
42 changes: 37 additions & 5 deletions src/watchdog/watchmedo.py
Expand Up @@ -60,7 +60,8 @@ def _split_lines(self, text, width):
return text.splitlines()


epilog = '''Copyright 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>.
epilog = '''\
Copyright 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>.
Copyright 2012 Google, Inc & contributors.
Licensed under the terms of the Apache license, version 2.0. Please see
Expand All @@ -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):
Expand All @@ -94,6 +96,12 @@ 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)
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)
Expand Down Expand Up @@ -397,7 +405,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)
Expand Down Expand Up @@ -632,14 +641,37 @@ 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 LogLevelException("-q/--quiet may be specified only once.")
if verbosity > 2:
raise LogLevelException("-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()
if args.top_command is None:
cli.print_help()
else:
args.func(args)
return 1

try:
log_level = _get_log_level_from_args(args)
except LogLevelException as exc:
print("Error: " + exc.args[0], file=sys.stderr)
command_parsers[args.top_command].print_help()
return 1
logging.getLogger('watchdog').setLevel(log_level)

args.func(args)
return 0


if __name__ == '__main__':
main()
sys.exit(main())
46 changes: 46 additions & 0 deletions tests/test_0_watchmedo.py
Expand Up @@ -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((watchmedo.LogLevelException, 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"
Expand Down

0 comments on commit 9a5df95

Please sign in to comment.