Make .cli a subpackage

This commit is contained in:
Markus Unterwaditzer 2014-12-20 01:59:59 +01:00
parent ecb40579df
commit cfe252d458
3 changed files with 161 additions and 143 deletions

View file

@ -66,7 +66,7 @@ def test_storage_instance_from_config(monkeypatch):
import vdirsyncer.storage
monkeypatch.setitem(vdirsyncer.storage.storage_names, 'lol', lol)
config = {'type': 'lol', 'foo': 'bar', 'baz': 1}
assert cli.storage_instance_from_config(config) == 'OK'
assert cli.utils.storage_instance_from_config(config) == 'OK'
def test_parse_pairs_args():
@ -246,10 +246,13 @@ def test_deprecated_item_status(tmpdir):
'ident_two': ['href_a', 'etag_a', 'href_b', 'etag_b']
}
assert cli.load_status(str(tmpdir), 'mypair', data_type='items') == data
assert cli.utils.load_status(
str(tmpdir), 'mypair', data_type='items') == data
cli.save_status(str(tmpdir), 'mypair', data_type='items', data=data)
assert cli.load_status(str(tmpdir), 'mypair', data_type='items') == data
cli.utils.save_status(
str(tmpdir), 'mypair', data_type='items', data=data)
assert cli.utils.load_status(
str(tmpdir), 'mypair', data_type='items') == data
def test_collections_cache_invalidation(tmpdir):

147
vdirsyncer/cli/__init__.py Normal file
View file

@ -0,0 +1,147 @@
# -*- coding: utf-8 -*-
'''
vdirsyncer.cli
~~~~~~~~~~~~~~
:copyright: (c) 2014 Markus Unterwaditzer & contributors
:license: MIT, see LICENSE for more details.
'''
import functools
import os
import sys
from .utils import CliError, WorkerQueue, cli_logger, collections_for_pair, \
handle_cli_error, load_config, parse_pairs_args, sync_pair
from .. import __version__, log
from ..doubleclick import click
from ..utils import expand_path
def catch_errors(f):
@functools.wraps(f)
def inner(*a, **kw):
try:
f(*a, **kw)
except:
if not handle_cli_error():
sys.exit(1)
return inner
def validate_verbosity(ctx, param, value):
x = getattr(log.logging, value.upper(), None)
if x is None:
raise click.BadParameter('Invalid verbosity value {}. Must be '
'CRITICAL, ERROR, WARNING, INFO or DEBUG'
.format(value))
return x
@click.group()
@click.option('--verbosity', '-v', default='INFO',
callback=validate_verbosity,
help='Either CRITICAL, ERROR, WARNING, INFO or DEBUG')
@click.version_option(version=__version__)
@click.pass_context
@catch_errors
def app(ctx, verbosity):
'''
vdirsyncer -- synchronize calendars and contacts
'''
log.add_handler(log.stdout_handler)
log.set_level(verbosity)
if ctx.obj is None:
ctx.obj = {}
if 'config' not in ctx.obj:
fname = expand_path(os.environ.get('VDIRSYNCER_CONFIG',
'~/.vdirsyncer/config'))
if not os.path.exists(fname):
xdg_config_dir = os.environ.get('XDG_CONFIG_HOME',
expand_path('~/.config/'))
fname = os.path.join(xdg_config_dir, 'vdirsyncer/config')
try:
with open(fname) as f:
ctx.obj['config'] = load_config(f)
except Exception as e:
raise CliError('Error during reading config {}: {}'
.format(fname, e))
main = app
max_workers_option = click.option(
'--max-workers', default=0, type=click.IntRange(min=0, max=None),
help=('Use at most this many connections, 0 means unlimited.')
)
@app.command()
@click.argument('pairs', nargs=-1)
@click.option('--force-delete/--no-force-delete',
help=('Disable data-loss protection for the given pairs. '
'Can be passed multiple times'))
@max_workers_option
@click.pass_context
@catch_errors
def sync(ctx, pairs, force_delete, max_workers):
'''
Synchronize the given collections or pairs. If no arguments are given,
all will be synchronized.
Examples:
`vdirsyncer sync` will sync everything configured.
`vdirsyncer sync bob frank` will sync the pairs "bob" and "frank".
`vdirsyncer sync bob/first_collection` will sync "first_collection"
from the pair "bob".
'''
general, all_pairs, all_storages = ctx.obj['config']
cli_logger.debug('Using {} maximal workers.'.format(max_workers))
wq = WorkerQueue(max_workers)
wq.handled_jobs = set()
for pair_name, collections in parse_pairs_args(pairs, all_pairs):
wq.spawn_worker()
wq.put(
functools.partial(sync_pair, pair_name=pair_name,
collections_to_sync=collections,
general=general, all_pairs=all_pairs,
all_storages=all_storages,
force_delete=force_delete))
wq.join()
@app.command()
@click.argument('pairs', nargs=-1)
@max_workers_option
@click.pass_context
@catch_errors
def discover(ctx, pairs, max_workers):
'''
Refresh collection cache for the given pairs.
'''
general, all_pairs, all_storages = ctx.obj['config']
cli_logger.debug('Using {} maximal workers.'.format(max_workers))
wq = WorkerQueue(max_workers)
for pair in (pairs or all_pairs):
try:
name_a, name_b, pair_options = all_pairs[pair]
except KeyError:
raise CliError('Pair not found: {}\n'
'These are the pairs found: {}'
.format(pair, list(all_pairs)))
wq.spawn_worker()
wq.put(functools.partial(
(lambda wq, **kwargs: collections_for_pair(**kwargs)),
status_path=general['status_path'], name_a=name_a,
name_b=name_b, pair_name=pair, config_a=all_storages[name_a],
config_b=all_storages[name_b], pair_options=pair_options,
skip_cache=True))
wq.join()

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
'''
vdirsyncer.cli
~~~~~~~~~~~~~~
vdirsyncer.cli.utils
~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2014 Markus Unterwaditzer & contributors
:license: MIT, see LICENSE for more details.
@ -17,11 +17,11 @@ import sys
import threading
from itertools import chain
from . import DOCS_HOME, PROJECT_HOME, __version__, log
from .doubleclick import click
from .storage import storage_names
from .sync import StorageEmpty, SyncConflict, sync
from .utils import expand_path, get_class_init_args, parse_options, \
from .. import DOCS_HOME, PROJECT_HOME, log
from ..doubleclick import click
from ..storage import storage_names
from ..sync import StorageEmpty, SyncConflict, sync
from ..utils import expand_path, get_class_init_args, parse_options, \
safe_write
@ -380,138 +380,6 @@ def parse_pairs_args(pairs_args, all_pairs):
return rv.items()
# We create the app inside a factory and destroy that factory after first use
# to avoid pollution of the module namespace.
def _create_app():
def catch_errors(f):
@functools.wraps(f)
def inner(*a, **kw):
try:
f(*a, **kw)
except:
if not handle_cli_error():
sys.exit(1)
return inner
def validate_verbosity(ctx, param, value):
x = getattr(log.logging, value.upper(), None)
if x is None:
raise click.BadParameter('Invalid verbosity value {}. Must be '
'CRITICAL, ERROR, WARNING, INFO or DEBUG'
.format(value))
return x
@click.group()
@click.option('--verbosity', '-v', default='INFO',
callback=validate_verbosity,
help='Either CRITICAL, ERROR, WARNING, INFO or DEBUG')
@click.version_option(version=__version__)
@click.pass_context
@catch_errors
def app(ctx, verbosity):
'''
vdirsyncer -- synchronize calendars and contacts
'''
log.add_handler(log.stdout_handler)
log.set_level(verbosity)
if ctx.obj is None:
ctx.obj = {}
if 'config' not in ctx.obj:
fname = expand_path(os.environ.get('VDIRSYNCER_CONFIG',
'~/.vdirsyncer/config'))
if not os.path.exists(fname):
xdg_config_dir = os.environ.get('XDG_CONFIG_HOME',
expand_path('~/.config/'))
fname = os.path.join(xdg_config_dir, 'vdirsyncer/config')
try:
with open(fname) as f:
ctx.obj['config'] = load_config(f)
except Exception as e:
raise CliError('Error during reading config {}: {}'
.format(fname, e))
max_workers_option = click.option(
'--max-workers', default=0, type=click.IntRange(min=0, max=None),
help=('Use at most this many connections, 0 means unlimited.')
)
@app.command()
@click.argument('pairs', nargs=-1)
@click.option('--force-delete/--no-force-delete',
help=('Disable data-loss protection for the given pairs. '
'Can be passed multiple times'))
@max_workers_option
@click.pass_context
@catch_errors
def sync(ctx, pairs, force_delete, max_workers):
'''
Synchronize the given collections or pairs. If no arguments are given,
all will be synchronized.
Examples:
`vdirsyncer sync` will sync everything configured.
`vdirsyncer sync bob frank` will sync the pairs "bob" and "frank".
`vdirsyncer sync bob/first_collection` will sync "first_collection"
from the pair "bob".
'''
general, all_pairs, all_storages = ctx.obj['config']
cli_logger.debug('Using {} maximal workers.'.format(max_workers))
wq = WorkerQueue(max_workers)
wq.handled_jobs = set()
for pair_name, collections in parse_pairs_args(pairs, all_pairs):
wq.spawn_worker()
wq.put(
functools.partial(sync_pair, pair_name=pair_name,
collections_to_sync=collections,
general=general, all_pairs=all_pairs,
all_storages=all_storages,
force_delete=force_delete))
wq.join()
@app.command()
@click.argument('pairs', nargs=-1)
@max_workers_option
@click.pass_context
@catch_errors
def discover(ctx, pairs, max_workers):
'''
Refresh collection cache for the given pairs.
'''
general, all_pairs, all_storages = ctx.obj['config']
cli_logger.debug('Using {} maximal workers.'.format(max_workers))
wq = WorkerQueue(max_workers)
for pair in (pairs or all_pairs):
try:
name_a, name_b, pair_options = all_pairs[pair]
except KeyError:
raise CliError('Pair not found: {}\n'
'These are the pairs found: {}'
.format(pair, list(all_pairs)))
wq.spawn_worker()
wq.put(functools.partial(
(lambda wq, **kwargs: collections_for_pair(**kwargs)),
status_path=general['status_path'], name_a=name_a,
name_b=name_b, pair_name=pair, config_a=all_storages[name_a],
config_b=all_storages[name_b], pair_options=pair_options,
skip_cache=True))
wq.join()
return app
app = main = _create_app()
del _create_app
def sync_pair(wq, pair_name, collections_to_sync, general, all_pairs,
all_storages, force_delete):