From 5bf3a4875c596574bf57a476108eb19c881b88ec Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Fri, 17 Jul 2015 21:58:50 -0700 Subject: [PATCH] Flag for multiple levels of verbosity: some, and lots. --- NEWS | 3 +- README.md | 8 +- atticmatic/attic.py | 32 +++++-- atticmatic/command.py | 12 +-- atticmatic/tests/integration/test_command.py | 10 +- atticmatic/tests/unit/test_attic.py | 99 ++++++++++++++------ atticmatic/verbosity.py | 2 + setup.py | 2 +- 8 files changed, 113 insertions(+), 55 deletions(-) create mode 100644 atticmatic/verbosity.py diff --git a/NEWS b/NEWS index 302227f..f46598a 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,6 @@ -0.0.7-dev +0.0.7 + * Flag for multiple levels of verbosity: some, and lots. * Improved mocking of Python builtins in unit tests. 0.0.6 diff --git a/README.md b/README.md index 771ca53..67b4bc1 100644 --- a/README.md +++ b/README.md @@ -78,9 +78,13 @@ and check backups for consistency problems due to things like file damage. By default, the backup will proceed silently except in the case of errors. But if you'd like to to get additional information about the progress of the -backup as it proceeds, use the verbose option instead: +backup as it proceeds, use the verbosity option: - atticmattic --verbose + atticmattic --verbosity 1 + +Or, for even more progress spew: + + atticmattic --verbosity 2 If you'd like to see the available command-line arguments, view the help: diff --git a/atticmatic/attic.py b/atticmatic/attic.py index fc83997..5a0a4b7 100644 --- a/atticmatic/attic.py +++ b/atticmatic/attic.py @@ -3,13 +3,19 @@ import os import platform import subprocess +from atticmatic.verbosity import VERBOSITY_SOME, VERBOSITY_LOTS -def create_archive(excludes_filename, verbose, source_directories, repository): + +def create_archive(excludes_filename, verbosity, source_directories, repository): ''' Given an excludes filename, a vebosity flag, a space-separated list of source directories, and a local or remote repository path, create an attic archive. ''' sources = tuple(source_directories.split(' ')) + verbosity_flags = { + VERBOSITY_SOME: ('--stats',), + VERBOSITY_LOTS: ('--verbose', '--stats'), + }.get(verbosity, ()) command = ( 'attic', 'create', @@ -19,9 +25,7 @@ def create_archive(excludes_filename, verbose, source_directories, repository): hostname=platform.node(), timestamp=datetime.now().isoformat(), ), - ) + sources + ( - ('--verbose', '--stats') if verbose else () - ) + ) + sources + verbosity_flags subprocess.check_call(command) @@ -48,11 +52,16 @@ def _make_prune_flags(retention_config): ) -def prune_archives(verbose, repository, retention_config): +def prune_archives(verbosity, repository, retention_config): ''' Given a verbosity flag, a local or remote repository path, and a retention config dict, prune attic archives according the the retention policy specified in that configuration. ''' + verbosity_flags = { + VERBOSITY_SOME: ('--stats',), + VERBOSITY_LOTS: ('--verbose', '--stats'), + }.get(verbosity, ()) + command = ( 'attic', 'prune', repository, @@ -60,7 +69,7 @@ def prune_archives(verbose, repository, retention_config): element for pair in _make_prune_flags(retention_config) for element in pair - ) + (('--verbose',) if verbose else ()) + ) + verbosity_flags subprocess.check_call(command) @@ -114,7 +123,7 @@ def _make_check_flags(checks): ) -def check_archives(verbose, repository, consistency_config): +def check_archives(verbosity, repository, consistency_config): ''' Given a verbosity flag, a local or remote repository path, and a consistency config dict, check the contained attic archives for consistency. @@ -125,12 +134,17 @@ def check_archives(verbose, repository, consistency_config): if not checks: return + verbosity_flags = { + VERBOSITY_SOME: ('--verbose',), + VERBOSITY_LOTS: ('--verbose',), + }.get(verbosity, ()) + command = ( 'attic', 'check', repository, - ) + _make_check_flags(checks) + (('--verbose',) if verbose else ()) + ) + _make_check_flags(checks) + verbosity_flags # Attic's check command spews to stdout even without the verbose flag. Suppress it. - stdout = None if verbose else open(os.devnull, 'w') + stdout = None if verbosity_flags else open(os.devnull, 'w') subprocess.check_call(command, stdout=stdout) diff --git a/atticmatic/command.py b/atticmatic/command.py index 0a844b2..37f42be 100644 --- a/atticmatic/command.py +++ b/atticmatic/command.py @@ -29,9 +29,9 @@ def parse_arguments(*arguments): help='Excludes filename', ) parser.add_argument( - '-v', '--verbose', - action='store_true', - help='Display verbose progress information', + '-v', '--verbosity', + type=int, + help='Display verbose progress (1 for some, 2 for lots)', ) return parser.parse_args(arguments) @@ -43,9 +43,9 @@ def main(): config = parse_configuration(args.config_filename) repository = config.location['repository'] - create_archive(args.excludes_filename, args.verbose, **config.location) - prune_archives(args.verbose, repository, config.retention) - check_archives(args.verbose, repository, config.consistency) + create_archive(args.excludes_filename, args.verbosity, **config.location) + prune_archives(args.verbosity, repository, config.retention) + check_archives(args.verbosity, repository, config.consistency) except (ValueError, IOError, CalledProcessError) as error: print(error, file=sys.stderr) sys.exit(1) diff --git a/atticmatic/tests/integration/test_command.py b/atticmatic/tests/integration/test_command.py index 3a5e794..9b75871 100644 --- a/atticmatic/tests/integration/test_command.py +++ b/atticmatic/tests/integration/test_command.py @@ -10,7 +10,7 @@ def test_parse_arguments_with_no_arguments_uses_defaults(): assert parser.config_filename == module.DEFAULT_CONFIG_FILENAME assert parser.excludes_filename == module.DEFAULT_EXCLUDES_FILENAME - assert parser.verbose == False + assert parser.verbosity == None def test_parse_arguments_with_filename_arguments_overrides_defaults(): @@ -18,15 +18,15 @@ def test_parse_arguments_with_filename_arguments_overrides_defaults(): assert parser.config_filename == 'myconfig' assert parser.excludes_filename == 'myexcludes' - assert parser.verbose == False + assert parser.verbosity == None -def test_parse_arguments_with_verbose_flag_overrides_default(): - parser = module.parse_arguments('--verbose') +def test_parse_arguments_with_verbosity_flag_overrides_default(): + parser = module.parse_arguments('--verbosity', '1') assert parser.config_filename == module.DEFAULT_CONFIG_FILENAME assert parser.excludes_filename == module.DEFAULT_EXCLUDES_FILENAME - assert parser.verbose == True + assert parser.verbosity == 1 def test_parse_arguments_with_invalid_arguments_exits(): diff --git a/atticmatic/tests/unit/test_attic.py b/atticmatic/tests/unit/test_attic.py index ab52850..13be67b 100644 --- a/atticmatic/tests/unit/test_attic.py +++ b/atticmatic/tests/unit/test_attic.py @@ -4,6 +4,7 @@ from flexmock import flexmock from atticmatic import attic as module from atticmatic.tests.builtins import builtins_mock +from atticmatic.verbosity import VERBOSITY_SOME, VERBOSITY_LOTS def insert_subprocess_mock(check_call_command, **kwargs): @@ -28,34 +29,43 @@ def insert_datetime_mock(): ).mock +CREATE_COMMAND = ('attic', 'create', '--exclude-from', 'excludes', 'repo::host-now', 'foo', 'bar') + + def test_create_archive_should_call_attic_with_parameters(): - insert_subprocess_mock( - ('attic', 'create', '--exclude-from', 'excludes', 'repo::host-now', 'foo', 'bar'), - ) + insert_subprocess_mock(CREATE_COMMAND) insert_platform_mock() insert_datetime_mock() module.create_archive( excludes_filename='excludes', - verbose=False, + verbosity=None, source_directories='foo bar', repository='repo', ) -def test_create_archive_with_verbose_should_call_attic_with_verbose_parameters(): - insert_subprocess_mock( - ( - 'attic', 'create', '--exclude-from', 'excludes', 'repo::host-now', 'foo', 'bar', - '--verbose', '--stats', - ), - ) +def test_create_archive_with_verbosity_some_should_call_attic_with_stats_parameter(): + insert_subprocess_mock(CREATE_COMMAND + ('--stats',)) insert_platform_mock() insert_datetime_mock() module.create_archive( excludes_filename='excludes', - verbose=True, + verbosity=VERBOSITY_SOME, + source_directories='foo bar', + repository='repo', + ) + + +def test_create_archive_with_verbosity_lots_should_call_attic_with_verbose_parameter(): + insert_subprocess_mock(CREATE_COMMAND + ('--verbose', '--stats')) + insert_platform_mock() + insert_datetime_mock() + + module.create_archive( + excludes_filename='excludes', + verbosity=VERBOSITY_LOTS, source_directories='foo bar', repository='repo', ) @@ -82,40 +92,49 @@ def test_make_prune_flags_should_return_flags_from_config(): assert tuple(result) == BASE_PRUNE_FLAGS +PRUNE_COMMAND = ( + 'attic', 'prune', 'repo', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3', +) + + def test_prune_archives_should_call_attic_with_parameters(): retention_config = flexmock() flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return( BASE_PRUNE_FLAGS, ) - insert_subprocess_mock( - ( - 'attic', 'prune', 'repo', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', - '3', - ), - ) + insert_subprocess_mock(PRUNE_COMMAND) module.prune_archives( - verbose=False, + verbosity=None, repository='repo', retention_config=retention_config, ) -def test_prune_archives_with_verbose_should_call_attic_with_verbose_parameters(): +def test_prune_archives_with_verbosity_some_should_call_attic_with_stats_parameter(): retention_config = flexmock() flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return( BASE_PRUNE_FLAGS, ) - insert_subprocess_mock( - ( - 'attic', 'prune', 'repo', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', - '3', '--verbose', - ), - ) + insert_subprocess_mock(PRUNE_COMMAND + ('--stats',)) module.prune_archives( repository='repo', - verbose=True, + verbosity=VERBOSITY_SOME, + retention_config=retention_config, + ) + + +def test_prune_archives_with_verbosity_lots_should_call_attic_with_verbose_parameter(): + retention_config = flexmock() + flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return( + BASE_PRUNE_FLAGS, + ) + insert_subprocess_mock(PRUNE_COMMAND + ('--verbose', '--stats',)) + + module.prune_archives( + repository='repo', + verbosity=VERBOSITY_LOTS, retention_config=retention_config, ) @@ -171,13 +190,13 @@ def test_check_archives_should_call_attic_with_parameters(): flexmock(module.os).should_receive('devnull') module.check_archives( - verbose=False, + verbosity=None, repository='repo', consistency_config=consistency_config, ) -def test_check_archives_with_verbose_should_call_attic_with_verbose_parameters(): +def test_check_archives_with_verbosity_some_should_call_attic_with_verbose_parameter(): consistency_config = flexmock() flexmock(module).should_receive('_parse_checks').and_return(flexmock()) flexmock(module).should_receive('_make_check_flags').and_return(()) @@ -189,7 +208,25 @@ def test_check_archives_with_verbose_should_call_attic_with_verbose_parameters() insert_datetime_mock() module.check_archives( - verbose=True, + verbosity=VERBOSITY_SOME, + repository='repo', + consistency_config=consistency_config, + ) + + +def test_check_archives_with_verbosity_lots_should_call_attic_with_verbose_parameter(): + consistency_config = flexmock() + flexmock(module).should_receive('_parse_checks').and_return(flexmock()) + flexmock(module).should_receive('_make_check_flags').and_return(()) + insert_subprocess_mock( + ('attic', 'check', 'repo', '--verbose'), + stdout=None, + ) + insert_platform_mock() + insert_datetime_mock() + + module.check_archives( + verbosity=VERBOSITY_LOTS, repository='repo', consistency_config=consistency_config, ) @@ -201,7 +238,7 @@ def test_check_archives_without_any_checks_should_bail(): insert_subprocess_never() module.check_archives( - verbose=False, + verbosity=None, repository='repo', consistency_config=consistency_config, ) diff --git a/atticmatic/verbosity.py b/atticmatic/verbosity.py new file mode 100644 index 0000000..06dfc4c --- /dev/null +++ b/atticmatic/verbosity.py @@ -0,0 +1,2 @@ +VERBOSITY_SOME = 1 +VERBOSITY_LOTS = 2 diff --git a/setup.py b/setup.py index 6fcd51d..2a64c55 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name='atticmatic', - version='0.0.6', + version='0.0.7', description='A wrapper script for Attic backup software that creates and prunes backups', author='Dan Helfman', author_email='witten@torsion.org',