Flag for multiple levels of verbosity: some, and lots.
This commit is contained in:
parent
1578b44536
commit
b501a568aa
8 changed files with 113 additions and 55 deletions
3
NEWS
3
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.
|
* Improved mocking of Python builtins in unit tests.
|
||||||
|
|
||||||
0.0.6
|
0.0.6
|
||||||
|
|
|
@ -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
|
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
|
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:
|
If you'd like to see the available command-line arguments, view the help:
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,19 @@ import os
|
||||||
import platform
|
import platform
|
||||||
import subprocess
|
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
|
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.
|
a local or remote repository path, create an attic archive.
|
||||||
'''
|
'''
|
||||||
sources = tuple(source_directories.split(' '))
|
sources = tuple(source_directories.split(' '))
|
||||||
|
verbosity_flags = {
|
||||||
|
VERBOSITY_SOME: ('--stats',),
|
||||||
|
VERBOSITY_LOTS: ('--verbose', '--stats'),
|
||||||
|
}.get(verbosity, ())
|
||||||
|
|
||||||
command = (
|
command = (
|
||||||
'attic', 'create',
|
'attic', 'create',
|
||||||
|
@ -19,9 +25,7 @@ def create_archive(excludes_filename, verbose, source_directories, repository):
|
||||||
hostname=platform.node(),
|
hostname=platform.node(),
|
||||||
timestamp=datetime.now().isoformat(),
|
timestamp=datetime.now().isoformat(),
|
||||||
),
|
),
|
||||||
) + sources + (
|
) + sources + verbosity_flags
|
||||||
('--verbose', '--stats') if verbose else ()
|
|
||||||
)
|
|
||||||
|
|
||||||
subprocess.check_call(command)
|
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
|
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.
|
attic archives according the the retention policy specified in that configuration.
|
||||||
'''
|
'''
|
||||||
|
verbosity_flags = {
|
||||||
|
VERBOSITY_SOME: ('--stats',),
|
||||||
|
VERBOSITY_LOTS: ('--verbose', '--stats'),
|
||||||
|
}.get(verbosity, ())
|
||||||
|
|
||||||
command = (
|
command = (
|
||||||
'attic', 'prune',
|
'attic', 'prune',
|
||||||
repository,
|
repository,
|
||||||
|
@ -60,7 +69,7 @@ def prune_archives(verbose, repository, retention_config):
|
||||||
element
|
element
|
||||||
for pair in _make_prune_flags(retention_config)
|
for pair in _make_prune_flags(retention_config)
|
||||||
for element in pair
|
for element in pair
|
||||||
) + (('--verbose',) if verbose else ())
|
) + verbosity_flags
|
||||||
|
|
||||||
subprocess.check_call(command)
|
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
|
Given a verbosity flag, a local or remote repository path, and a consistency config dict, check
|
||||||
the contained attic archives for consistency.
|
the contained attic archives for consistency.
|
||||||
|
@ -125,12 +134,17 @@ def check_archives(verbose, repository, consistency_config):
|
||||||
if not checks:
|
if not checks:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
verbosity_flags = {
|
||||||
|
VERBOSITY_SOME: ('--verbose',),
|
||||||
|
VERBOSITY_LOTS: ('--verbose',),
|
||||||
|
}.get(verbosity, ())
|
||||||
|
|
||||||
command = (
|
command = (
|
||||||
'attic', 'check',
|
'attic', 'check',
|
||||||
repository,
|
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.
|
# 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)
|
subprocess.check_call(command, stdout=stdout)
|
||||||
|
|
|
@ -29,9 +29,9 @@ def parse_arguments(*arguments):
|
||||||
help='Excludes filename',
|
help='Excludes filename',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-v', '--verbose',
|
'-v', '--verbosity',
|
||||||
action='store_true',
|
type=int,
|
||||||
help='Display verbose progress information',
|
help='Display verbose progress (1 for some, 2 for lots)',
|
||||||
)
|
)
|
||||||
|
|
||||||
return parser.parse_args(arguments)
|
return parser.parse_args(arguments)
|
||||||
|
@ -43,9 +43,9 @@ def main():
|
||||||
config = parse_configuration(args.config_filename)
|
config = parse_configuration(args.config_filename)
|
||||||
repository = config.location['repository']
|
repository = config.location['repository']
|
||||||
|
|
||||||
create_archive(args.excludes_filename, args.verbose, **config.location)
|
create_archive(args.excludes_filename, args.verbosity, **config.location)
|
||||||
prune_archives(args.verbose, repository, config.retention)
|
prune_archives(args.verbosity, repository, config.retention)
|
||||||
check_archives(args.verbose, repository, config.consistency)
|
check_archives(args.verbosity, repository, config.consistency)
|
||||||
except (ValueError, IOError, CalledProcessError) as error:
|
except (ValueError, IOError, CalledProcessError) as error:
|
||||||
print(error, file=sys.stderr)
|
print(error, file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
|
@ -10,7 +10,7 @@ def test_parse_arguments_with_no_arguments_uses_defaults():
|
||||||
|
|
||||||
assert parser.config_filename == module.DEFAULT_CONFIG_FILENAME
|
assert parser.config_filename == module.DEFAULT_CONFIG_FILENAME
|
||||||
assert parser.excludes_filename == module.DEFAULT_EXCLUDES_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():
|
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.config_filename == 'myconfig'
|
||||||
assert parser.excludes_filename == 'myexcludes'
|
assert parser.excludes_filename == 'myexcludes'
|
||||||
assert parser.verbose == False
|
assert parser.verbosity == None
|
||||||
|
|
||||||
|
|
||||||
def test_parse_arguments_with_verbose_flag_overrides_default():
|
def test_parse_arguments_with_verbosity_flag_overrides_default():
|
||||||
parser = module.parse_arguments('--verbose')
|
parser = module.parse_arguments('--verbosity', '1')
|
||||||
|
|
||||||
assert parser.config_filename == module.DEFAULT_CONFIG_FILENAME
|
assert parser.config_filename == module.DEFAULT_CONFIG_FILENAME
|
||||||
assert parser.excludes_filename == module.DEFAULT_EXCLUDES_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():
|
def test_parse_arguments_with_invalid_arguments_exits():
|
||||||
|
|
|
@ -4,6 +4,7 @@ from flexmock import flexmock
|
||||||
|
|
||||||
from atticmatic import attic as module
|
from atticmatic import attic as module
|
||||||
from atticmatic.tests.builtins import builtins_mock
|
from atticmatic.tests.builtins import builtins_mock
|
||||||
|
from atticmatic.verbosity import VERBOSITY_SOME, VERBOSITY_LOTS
|
||||||
|
|
||||||
|
|
||||||
def insert_subprocess_mock(check_call_command, **kwargs):
|
def insert_subprocess_mock(check_call_command, **kwargs):
|
||||||
|
@ -28,34 +29,43 @@ def insert_datetime_mock():
|
||||||
).mock
|
).mock
|
||||||
|
|
||||||
|
|
||||||
|
CREATE_COMMAND = ('attic', 'create', '--exclude-from', 'excludes', 'repo::host-now', 'foo', 'bar')
|
||||||
|
|
||||||
|
|
||||||
def test_create_archive_should_call_attic_with_parameters():
|
def test_create_archive_should_call_attic_with_parameters():
|
||||||
insert_subprocess_mock(
|
insert_subprocess_mock(CREATE_COMMAND)
|
||||||
('attic', 'create', '--exclude-from', 'excludes', 'repo::host-now', 'foo', 'bar'),
|
|
||||||
)
|
|
||||||
insert_platform_mock()
|
insert_platform_mock()
|
||||||
insert_datetime_mock()
|
insert_datetime_mock()
|
||||||
|
|
||||||
module.create_archive(
|
module.create_archive(
|
||||||
excludes_filename='excludes',
|
excludes_filename='excludes',
|
||||||
verbose=False,
|
verbosity=None,
|
||||||
source_directories='foo bar',
|
source_directories='foo bar',
|
||||||
repository='repo',
|
repository='repo',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_create_archive_with_verbose_should_call_attic_with_verbose_parameters():
|
def test_create_archive_with_verbosity_some_should_call_attic_with_stats_parameter():
|
||||||
insert_subprocess_mock(
|
insert_subprocess_mock(CREATE_COMMAND + ('--stats',))
|
||||||
(
|
|
||||||
'attic', 'create', '--exclude-from', 'excludes', 'repo::host-now', 'foo', 'bar',
|
|
||||||
'--verbose', '--stats',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
insert_platform_mock()
|
insert_platform_mock()
|
||||||
insert_datetime_mock()
|
insert_datetime_mock()
|
||||||
|
|
||||||
module.create_archive(
|
module.create_archive(
|
||||||
excludes_filename='excludes',
|
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',
|
source_directories='foo bar',
|
||||||
repository='repo',
|
repository='repo',
|
||||||
)
|
)
|
||||||
|
@ -82,40 +92,49 @@ def test_make_prune_flags_should_return_flags_from_config():
|
||||||
assert tuple(result) == BASE_PRUNE_FLAGS
|
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():
|
def test_prune_archives_should_call_attic_with_parameters():
|
||||||
retention_config = flexmock()
|
retention_config = flexmock()
|
||||||
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
|
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
|
||||||
BASE_PRUNE_FLAGS,
|
BASE_PRUNE_FLAGS,
|
||||||
)
|
)
|
||||||
insert_subprocess_mock(
|
insert_subprocess_mock(PRUNE_COMMAND)
|
||||||
(
|
|
||||||
'attic', 'prune', 'repo', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly',
|
|
||||||
'3',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
module.prune_archives(
|
module.prune_archives(
|
||||||
verbose=False,
|
verbosity=None,
|
||||||
repository='repo',
|
repository='repo',
|
||||||
retention_config=retention_config,
|
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()
|
retention_config = flexmock()
|
||||||
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
|
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
|
||||||
BASE_PRUNE_FLAGS,
|
BASE_PRUNE_FLAGS,
|
||||||
)
|
)
|
||||||
insert_subprocess_mock(
|
insert_subprocess_mock(PRUNE_COMMAND + ('--stats',))
|
||||||
(
|
|
||||||
'attic', 'prune', 'repo', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly',
|
|
||||||
'3', '--verbose',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
module.prune_archives(
|
module.prune_archives(
|
||||||
repository='repo',
|
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,
|
retention_config=retention_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -171,13 +190,13 @@ def test_check_archives_should_call_attic_with_parameters():
|
||||||
flexmock(module.os).should_receive('devnull')
|
flexmock(module.os).should_receive('devnull')
|
||||||
|
|
||||||
module.check_archives(
|
module.check_archives(
|
||||||
verbose=False,
|
verbosity=None,
|
||||||
repository='repo',
|
repository='repo',
|
||||||
consistency_config=consistency_config,
|
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()
|
consistency_config = flexmock()
|
||||||
flexmock(module).should_receive('_parse_checks').and_return(flexmock())
|
flexmock(module).should_receive('_parse_checks').and_return(flexmock())
|
||||||
flexmock(module).should_receive('_make_check_flags').and_return(())
|
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()
|
insert_datetime_mock()
|
||||||
|
|
||||||
module.check_archives(
|
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',
|
repository='repo',
|
||||||
consistency_config=consistency_config,
|
consistency_config=consistency_config,
|
||||||
)
|
)
|
||||||
|
@ -201,7 +238,7 @@ def test_check_archives_without_any_checks_should_bail():
|
||||||
insert_subprocess_never()
|
insert_subprocess_never()
|
||||||
|
|
||||||
module.check_archives(
|
module.check_archives(
|
||||||
verbose=False,
|
verbosity=None,
|
||||||
repository='repo',
|
repository='repo',
|
||||||
consistency_config=consistency_config,
|
consistency_config=consistency_config,
|
||||||
)
|
)
|
||||||
|
|
2
atticmatic/verbosity.py
Normal file
2
atticmatic/verbosity.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
VERBOSITY_SOME = 1
|
||||||
|
VERBOSITY_LOTS = 2
|
2
setup.py
2
setup.py
|
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='atticmatic',
|
name='atticmatic',
|
||||||
version='0.0.6',
|
version='0.0.7',
|
||||||
description='A wrapper script for Attic backup software that creates and prunes backups',
|
description='A wrapper script for Attic backup software that creates and prunes backups',
|
||||||
author='Dan Helfman',
|
author='Dan Helfman',
|
||||||
author_email='witten@torsion.org',
|
author_email='witten@torsion.org',
|
||||||
|
|
Loading…
Reference in a new issue