mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
commit
c78ac67ba9
7 changed files with 243 additions and 126 deletions
|
|
@ -21,7 +21,7 @@ General Section
|
||||||
next sync. The data is needed to determine whether a new item means it has
|
next sync. The data is needed to determine whether a new item means it has
|
||||||
been added on one side or deleted on the other.
|
been added on one side or deleted on the other.
|
||||||
|
|
||||||
- ``processes``: Optional, defines the amount of maximal connections to use for
|
- ``processes``: Optional, defines the maximal amount of threads to use for
|
||||||
syncing. By default there is no limit, which means vdirsyncer will try to
|
syncing. By default there is no limit, which means vdirsyncer will try to
|
||||||
open a connection for each collection to be synced. The value ``0`` is
|
open a connection for each collection to be synced. The value ``0`` is
|
||||||
ignored. Setting this to ``1`` will only synchronize one collection at a
|
ignored. Setting this to ``1`` will only synchronize one collection at a
|
||||||
|
|
@ -32,6 +32,12 @@ General Section
|
||||||
Raspberry Pi is so slow that multiple connections don't help much, since the
|
Raspberry Pi is so slow that multiple connections don't help much, since the
|
||||||
CPU and not the network is the bottleneck.
|
CPU and not the network is the bottleneck.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Due to restrictions in Python's threading module, setting ``processes``
|
||||||
|
to anything else than ``1`` will mean that you can't properly abort the
|
||||||
|
program with ``^C`` anymore.
|
||||||
|
|
||||||
.. _pair_config:
|
.. _pair_config:
|
||||||
|
|
||||||
Pair Section
|
Pair Section
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,8 @@ To use it, you must install keyring_.
|
||||||
|
|
||||||
.. _keyring: https://bitbucket.org/kang/python-keyring-lib
|
.. _keyring: https://bitbucket.org/kang/python-keyring-lib
|
||||||
|
|
||||||
*vdirsyncer* will use the full resource URL as the key when saving.
|
*vdirsyncer* will use the hostname as key prefixed with ``vdirsyncer:`` when
|
||||||
|
saving and fetching, e.g. ``vdirsyncer:owncloud.example.com``.
|
||||||
When retrieving the key, it will try to remove segments of the URL's path until
|
|
||||||
it finds a password. For example, if you save a password under the key
|
|
||||||
``vdirsyncer:http://example.com``, it will be used as a fallback for all
|
|
||||||
resources on ``example.com``. If you additionally save a password under the key
|
|
||||||
``vdirsyncer:http://example.com/special/``, that password will be used for all
|
|
||||||
resources on ``example.com`` whose path starts with ``/special/``.
|
|
||||||
|
|
||||||
*keyring* support these keyrings:
|
*keyring* support these keyrings:
|
||||||
|
|
||||||
|
|
|
||||||
23
tests/test_doubleclick.py
Normal file
23
tests/test_doubleclick.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
tests.test_doubleclick
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
:copyright: (c) 2014 Markus Unterwaditzer & contributors
|
||||||
|
:license: MIT, see LICENSE for more details.
|
||||||
|
'''
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
from vdirsyncer.doubleclick import _ctx_stack, click, ctx as global_ctx
|
||||||
|
|
||||||
|
|
||||||
|
def test_simple():
|
||||||
|
@click.command()
|
||||||
|
@click.pass_context
|
||||||
|
def cli(ctx):
|
||||||
|
assert global_ctx
|
||||||
|
assert _ctx_stack.top is ctx
|
||||||
|
|
||||||
|
assert not global_ctx
|
||||||
|
runner = CliRunner()
|
||||||
|
runner.invoke(cli)
|
||||||
|
|
@ -8,14 +8,31 @@
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
import pytest
|
import pytest
|
||||||
import vdirsyncer.utils as utils
|
import vdirsyncer.utils as utils
|
||||||
|
import vdirsyncer.doubleclick as doubleclick
|
||||||
from vdirsyncer.utils.vobject import split_collection
|
from vdirsyncer.utils.vobject import split_collection
|
||||||
|
|
||||||
from .. import blow_up, normalize_item, SIMPLE_TEMPLATE, BARE_EVENT_TEMPLATE
|
from .. import blow_up, normalize_item, SIMPLE_TEMPLATE, BARE_EVENT_TEMPLATE
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyNetrc(object):
|
||||||
|
def authenticators(self, hostname):
|
||||||
|
return None
|
||||||
|
|
||||||
|
class EmptyKeyring(object):
|
||||||
|
def get_password(self, *a, **kw):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def empty_password_storages(monkeypatch):
|
||||||
|
monkeypatch.setattr('netrc.netrc', EmptyNetrc)
|
||||||
|
monkeypatch.setattr(utils, 'keyring', EmptyKeyring())
|
||||||
|
|
||||||
|
|
||||||
def test_parse_options():
|
def test_parse_options():
|
||||||
o = {
|
o = {
|
||||||
'foo': 'yes',
|
'foo': 'yes',
|
||||||
|
|
@ -67,31 +84,17 @@ def test_get_password_from_netrc(monkeypatch):
|
||||||
assert calls == [hostname]
|
assert calls == [hostname]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('resources_to_test', range(1, 8))
|
def test_get_password_from_system_keyring(monkeypatch):
|
||||||
def test_get_password_from_system_keyring(monkeypatch, resources_to_test):
|
|
||||||
username = 'foouser'
|
username = 'foouser'
|
||||||
password = 'foopass'
|
password = 'foopass'
|
||||||
resource = 'http://example.com/path/to/whatever/'
|
resource = 'http://example.com/path/to/whatever/'
|
||||||
hostname = 'example.com'
|
hostname = 'example.com'
|
||||||
|
|
||||||
class KeyringMock(object):
|
class KeyringMock(object):
|
||||||
def __init__(self):
|
|
||||||
p = utils.password_key_prefix
|
|
||||||
self.resources = [
|
|
||||||
p + 'http://example.com/path/to/whatever/',
|
|
||||||
p + 'http://example.com/path/to/whatever',
|
|
||||||
p + 'http://example.com/path/to/',
|
|
||||||
p + 'http://example.com/path/to',
|
|
||||||
p + 'http://example.com/path/',
|
|
||||||
p + 'http://example.com/path',
|
|
||||||
p + 'http://example.com/',
|
|
||||||
][:resources_to_test]
|
|
||||||
|
|
||||||
def get_password(self, resource, _username):
|
def get_password(self, resource, _username):
|
||||||
assert _username == username
|
assert _username == username
|
||||||
assert resource == self.resources.pop(0)
|
assert resource == utils.password_key_prefix + hostname
|
||||||
if not self.resources:
|
return password
|
||||||
return password
|
|
||||||
|
|
||||||
monkeypatch.setattr(utils, 'keyring', KeyringMock())
|
monkeypatch.setattr(utils, 'keyring', KeyringMock())
|
||||||
|
|
||||||
|
|
@ -110,20 +113,9 @@ def test_get_password_from_system_keyring(monkeypatch, resources_to_test):
|
||||||
assert netrc_calls == [hostname]
|
assert netrc_calls == [hostname]
|
||||||
|
|
||||||
|
|
||||||
def test_get_password_from_prompt(monkeypatch):
|
def test_get_password_from_prompt():
|
||||||
getpass_calls = []
|
getpass_calls = []
|
||||||
|
|
||||||
class Netrc(object):
|
|
||||||
def authenticators(self, hostname):
|
|
||||||
return None
|
|
||||||
|
|
||||||
class Keyring(object):
|
|
||||||
def get_password(self, *a, **kw):
|
|
||||||
return None
|
|
||||||
|
|
||||||
monkeypatch.setattr('netrc.netrc', Netrc)
|
|
||||||
monkeypatch.setattr(utils, 'keyring', Keyring())
|
|
||||||
|
|
||||||
user = 'my_user'
|
user = 'my_user'
|
||||||
resource = 'http://example.com'
|
resource = 'http://example.com'
|
||||||
|
|
||||||
|
|
@ -136,8 +128,35 @@ def test_get_password_from_prompt(monkeypatch):
|
||||||
result = runner.invoke(fake_app, input='my_password\n\n')
|
result = runner.invoke(fake_app, input='my_password\n\n')
|
||||||
assert not result.exception
|
assert not result.exception
|
||||||
assert result.output.splitlines() == [
|
assert result.output.splitlines() == [
|
||||||
'Server password for {} at the resource {}: '.format(user, resource),
|
'Server password for {} at host {}: '.format(user, 'example.com'),
|
||||||
|
'Password is my_password'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_password_from_cache(monkeypatch):
|
||||||
|
user = 'my_user'
|
||||||
|
resource = 'http://example.com'
|
||||||
|
|
||||||
|
@doubleclick.click.command()
|
||||||
|
@doubleclick.click.pass_context
|
||||||
|
def fake_app(ctx):
|
||||||
|
ctx.obj = {}
|
||||||
|
x = utils.get_password(user, resource)
|
||||||
|
click.echo('Password is {}'.format(x))
|
||||||
|
monkeypatch.setattr(doubleclick.click, 'prompt', blow_up)
|
||||||
|
|
||||||
|
assert (user, 'example.com') in ctx.obj['passwords']
|
||||||
|
x = utils.get_password(user, resource)
|
||||||
|
click.echo('Password is {}'.format(x))
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(fake_app, input='my_password\n')
|
||||||
|
assert not result.exception
|
||||||
|
assert result.output.splitlines() == [
|
||||||
|
'Server password for {} at host {}: '.format(user, 'example.com'),
|
||||||
'Save this password in the keyring? [y/N]: ',
|
'Save this password in the keyring? [y/N]: ',
|
||||||
|
'Password is my_password',
|
||||||
|
'debug: Got password for my_user from internal cache',
|
||||||
'Password is my_password'
|
'Password is my_password'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -350,13 +350,14 @@ def _create_app():
|
||||||
cli_logger.debug('Using {} processes.'.format(processes))
|
cli_logger.debug('Using {} processes.'.format(processes))
|
||||||
|
|
||||||
if processes == 1:
|
if processes == 1:
|
||||||
cli_logger.debug('Not using multiprocessing.')
|
cli_logger.debug('Not using threads.')
|
||||||
rv = (_sync_collection(x) for x in actions)
|
rv = (_sync_collection(x) for x in actions)
|
||||||
else:
|
else:
|
||||||
cli_logger.debug('Using multiprocessing.')
|
cli_logger.debug('Using threads.')
|
||||||
from multiprocessing import Pool
|
from multiprocessing.dummy import Pool
|
||||||
p = Pool(processes=general.get('processes', 0) or len(actions))
|
p = Pool(processes=general.get('processes', 0) or len(actions))
|
||||||
rv = p.map_async(_sync_collection, actions).get(10**9)
|
|
||||||
|
rv = p.imap_unordered(_sync_collection, actions)
|
||||||
|
|
||||||
if not all(rv):
|
if not all(rv):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
@ -377,13 +378,15 @@ def sync_collection(config_a, config_b, pair_name, collection, pair_options,
|
||||||
collection_description = pair_name if collection is None \
|
collection_description = pair_name if collection is None \
|
||||||
else '{} from {}'.format(collection, pair_name)
|
else '{} from {}'.format(collection, pair_name)
|
||||||
|
|
||||||
a = storage_instance_from_config(config_a)
|
|
||||||
b = storage_instance_from_config(config_b)
|
|
||||||
|
|
||||||
cli_logger.info('Syncing {}'.format(collection_description))
|
|
||||||
status = load_status(general['status_path'], status_name)
|
|
||||||
rv = True
|
rv = True
|
||||||
try:
|
try:
|
||||||
|
cli_logger.info('Syncing {}'.format(collection_description))
|
||||||
|
|
||||||
|
a = storage_instance_from_config(config_a)
|
||||||
|
b = storage_instance_from_config(config_b)
|
||||||
|
|
||||||
|
status = load_status(general['status_path'], status_name)
|
||||||
|
cli_logger.debug('Loaded status for {}'.format(collection_description))
|
||||||
sync(
|
sync(
|
||||||
a, b, status,
|
a, b, status,
|
||||||
conflict_resolution=pair_options.get('conflict_resolution', None),
|
conflict_resolution=pair_options.get('conflict_resolution', None),
|
||||||
|
|
@ -414,10 +417,13 @@ def sync_collection(config_a, config_b, pair_name, collection, pair_options,
|
||||||
'Item href on side B: {e.href_b}\n'
|
'Item href on side B: {e.href_b}\n'
|
||||||
.format(collection=collection_description, e=e, docs=DOCS_HOME)
|
.format(collection=collection_description, e=e, docs=DOCS_HOME)
|
||||||
)
|
)
|
||||||
except Exception:
|
except (click.Abort, KeyboardInterrupt):
|
||||||
|
rv = False
|
||||||
|
except Exception as e:
|
||||||
rv = False
|
rv = False
|
||||||
cli_logger.exception('Unhandled exception occured while syncing {}.'
|
cli_logger.exception('Unhandled exception occured while syncing {}.'
|
||||||
.format(collection_description))
|
.format(collection_description))
|
||||||
|
|
||||||
save_status(general['status_path'], status_name, status)
|
if rv:
|
||||||
|
save_status(general['status_path'], status_name, status)
|
||||||
return rv
|
return rv
|
||||||
|
|
|
||||||
|
|
@ -3,53 +3,134 @@
|
||||||
vdirsyncer.utils.doubleclick
|
vdirsyncer.utils.doubleclick
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Utilities for writing multiprocessing applications with click.
|
Utilities for writing threaded applications with click.
|
||||||
|
|
||||||
Currently the only relevant object here is the ``click`` object, which
|
Two objects are useful:
|
||||||
provides everything importable from click. It also wraps some UI functions
|
|
||||||
such that they don't produce overlapping output or prompt the user at the
|
- There is a global ``ctx`` object to be used.
|
||||||
same time.
|
|
||||||
|
- The ``click`` object's attributes are supposed to be used instead of the
|
||||||
|
click package's content.
|
||||||
|
|
||||||
|
- It wraps some UI functions such that they don't produce overlapping
|
||||||
|
output or prompt the user at the same time.
|
||||||
|
|
||||||
|
- It wraps BaseCommand subclasses such that their invocation changes the
|
||||||
|
ctx global, and also changes the shortcut decorators to use the new
|
||||||
|
classes.
|
||||||
|
|
||||||
:copyright: (c) 2014 Markus Unterwaditzer & contributors
|
:copyright: (c) 2014 Markus Unterwaditzer & contributors
|
||||||
:license: MIT, see LICENSE for more details.
|
:license: MIT, see LICENSE for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import multiprocessing
|
import threading
|
||||||
|
|
||||||
UI_FUNCTIONS = frozenset(['echo', 'echo_via_pager', 'prompt', 'clear', 'edit',
|
|
||||||
'launch', 'getchar', 'pause'])
|
|
||||||
|
|
||||||
|
|
||||||
_ui_lock = multiprocessing.Lock()
|
class _ClickProxy(object):
|
||||||
|
def __init__(self, wrappers, click=None):
|
||||||
|
if click is None:
|
||||||
|
import click
|
||||||
|
self._click = click
|
||||||
|
self._cache = {}
|
||||||
|
self._wrappers = dict(wrappers)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name not in self._cache:
|
||||||
|
f = getattr(self._click, name)
|
||||||
|
f = self._wrappers.get(name, lambda x: x)(f)
|
||||||
|
self._cache[name] = f
|
||||||
|
|
||||||
|
return self._cache[name]
|
||||||
|
|
||||||
|
|
||||||
|
_ui_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
def _ui_function(f):
|
def _ui_function(f):
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
def inner(*a, **kw):
|
def inner(*a, **kw):
|
||||||
_ui_lock.acquire()
|
with _ui_lock:
|
||||||
try:
|
rv = f(*a, **kw)
|
||||||
return f(*a, **kw)
|
return rv
|
||||||
finally:
|
|
||||||
_ui_lock.release()
|
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
|
|
||||||
class _ClickProxy(object):
|
class _Stack(object):
|
||||||
def __init__(self, needs_wrapper, click=None):
|
def __init__(self):
|
||||||
if click is None:
|
self._stack = []
|
||||||
import click
|
|
||||||
self._click = click
|
@property
|
||||||
self._cache = {}
|
def top(self):
|
||||||
self._needs_wrapper = frozenset(needs_wrapper)
|
return self._stack[-1]
|
||||||
|
|
||||||
|
def push(self, value):
|
||||||
|
self._stack.append(value)
|
||||||
|
|
||||||
|
def pop(self):
|
||||||
|
return self._stack.pop()
|
||||||
|
|
||||||
|
|
||||||
|
class _StackProxy(object):
|
||||||
|
def __init__(self, stack):
|
||||||
|
self._doubleclick_stack = stack
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
try:
|
||||||
|
self._doubleclick_stack.top
|
||||||
|
except IndexError:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
__nonzero__ = __bool__
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if name not in self._cache:
|
return getattr(self._doubleclick_stack.top, name)
|
||||||
f = getattr(self._click, name)
|
|
||||||
if name in self._needs_wrapper:
|
|
||||||
f = _ui_function(f)
|
|
||||||
self._cache[name] = f
|
|
||||||
|
|
||||||
return self._cache[name]
|
|
||||||
|
|
||||||
click = _ClickProxy(UI_FUNCTIONS)
|
_ctx_stack = _Stack()
|
||||||
|
ctx = _StackProxy(_ctx_stack)
|
||||||
|
|
||||||
|
|
||||||
|
def _ctx_pushing_class(cls):
|
||||||
|
class ContextPusher(cls):
|
||||||
|
def invoke(self, ctx):
|
||||||
|
_ctx_stack.push(ctx)
|
||||||
|
try:
|
||||||
|
cls.invoke(self, ctx)
|
||||||
|
finally:
|
||||||
|
_ctx_stack.pop()
|
||||||
|
|
||||||
|
return ContextPusher
|
||||||
|
|
||||||
|
|
||||||
|
def _command_class_wrapper(cls_name):
|
||||||
|
def inner(f):
|
||||||
|
def wrapper(name=None, **attrs):
|
||||||
|
attrs.setdefault('cls', getattr(click, cls_name))
|
||||||
|
return f(name, **attrs)
|
||||||
|
return wrapper
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
WRAPPERS = {
|
||||||
|
'echo': _ui_function,
|
||||||
|
'echo_via_pager': _ui_function,
|
||||||
|
'prompt': _ui_function,
|
||||||
|
'confirm': _ui_function,
|
||||||
|
'clear': _ui_function,
|
||||||
|
'edit': _ui_function,
|
||||||
|
'launch': _ui_function,
|
||||||
|
'getchar': _ui_function,
|
||||||
|
'pause': _ui_function,
|
||||||
|
'BaseCommand': _ctx_pushing_class,
|
||||||
|
'Command': _ctx_pushing_class,
|
||||||
|
'MultiCommand': _ctx_pushing_class,
|
||||||
|
'Group': _ctx_pushing_class,
|
||||||
|
'CommandCollection': _ctx_pushing_class,
|
||||||
|
'command': _command_class_wrapper('Command'),
|
||||||
|
'group': _command_class_wrapper('Group')
|
||||||
|
}
|
||||||
|
|
||||||
|
click = _ClickProxy(WRAPPERS)
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,12 @@
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .. import exceptions, log
|
from .. import exceptions, log
|
||||||
from ..doubleclick import click
|
from ..doubleclick import click, ctx
|
||||||
from .compat import iteritems, urlparse
|
from .compat import iteritems, urlparse
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -88,7 +89,7 @@ def parse_options(items, section=None):
|
||||||
.format(section, key, e))
|
.format(section, key, e))
|
||||||
|
|
||||||
|
|
||||||
def get_password(username, resource):
|
def get_password(username, resource, _lock=threading.Lock()):
|
||||||
"""tries to access saved password or asks user for it
|
"""tries to access saved password or asks user for it
|
||||||
|
|
||||||
will try the following in this order:
|
will try the following in this order:
|
||||||
|
|
@ -110,71 +111,58 @@ def get_password(username, resource):
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for func in (_password_from_netrc, _password_from_keyring):
|
if ctx:
|
||||||
password = func(username, resource)
|
password_cache = ctx.obj.setdefault('passwords', {})
|
||||||
if password is not None:
|
|
||||||
logger.debug('Got password for {} from {}'
|
|
||||||
.format(username, func.__doc__))
|
|
||||||
return password
|
|
||||||
|
|
||||||
prompt = ('Server password for {} at the resource {}'
|
with _lock:
|
||||||
.format(username, resource))
|
host = urlparse.urlsplit(resource).hostname
|
||||||
password = click.prompt(prompt, hide_input=True)
|
for func in (_password_from_cache, _password_from_netrc,
|
||||||
|
_password_from_keyring):
|
||||||
|
password = func(username, host)
|
||||||
|
if password is not None:
|
||||||
|
logger.debug('Got password for {} from {}'
|
||||||
|
.format(username, func.__doc__))
|
||||||
|
return password
|
||||||
|
|
||||||
if keyring is not None and \
|
prompt = ('Server password for {} at host {}'.format(username, host))
|
||||||
click.confirm('Save this password in the keyring?', default=False):
|
password = click.prompt(prompt, hide_input=True)
|
||||||
keyring.set_password(password_key_prefix + resource,
|
|
||||||
username, password)
|
|
||||||
|
|
||||||
return password
|
if ctx and func is not _password_from_cache:
|
||||||
|
password_cache[(username, host)] = password
|
||||||
|
if keyring is not None and \
|
||||||
|
click.confirm('Save this password in the keyring?',
|
||||||
|
default=False):
|
||||||
|
keyring.set_password(password_key_prefix + resource,
|
||||||
|
username, password)
|
||||||
|
|
||||||
|
return password
|
||||||
|
|
||||||
|
|
||||||
def _password_from_netrc(username, resource):
|
def _password_from_cache(username, host):
|
||||||
|
'''internal cache'''
|
||||||
|
if ctx:
|
||||||
|
return ctx.obj['passwords'].get((username, host), None)
|
||||||
|
|
||||||
|
|
||||||
|
def _password_from_netrc(username, host):
|
||||||
'''.netrc'''
|
'''.netrc'''
|
||||||
from netrc import netrc
|
from netrc import netrc
|
||||||
|
|
||||||
hostname = urlparse.urlsplit(resource).hostname
|
|
||||||
try:
|
try:
|
||||||
netrc_user, account, password = \
|
netrc_user, account, password = \
|
||||||
netrc().authenticators(hostname) or (None, None, None)
|
netrc().authenticators(host) or (None, None, None)
|
||||||
if netrc_user == username:
|
if netrc_user == username:
|
||||||
return password
|
return password
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _password_from_keyring(username, resource):
|
def _password_from_keyring(username, host):
|
||||||
'''system keyring'''
|
'''system keyring'''
|
||||||
if keyring is None:
|
if keyring is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
key = resource
|
return keyring.get_password(password_key_prefix + host, username)
|
||||||
password = None
|
|
||||||
|
|
||||||
while True:
|
|
||||||
password = keyring.get_password(password_key_prefix + key, username)
|
|
||||||
if password is not None:
|
|
||||||
return password
|
|
||||||
|
|
||||||
parsed = urlparse.urlsplit(key)
|
|
||||||
path = parsed.path
|
|
||||||
if not path:
|
|
||||||
return None
|
|
||||||
elif path.endswith('/'):
|
|
||||||
path = path.rstrip('/')
|
|
||||||
else:
|
|
||||||
path = path.rsplit('/', 1)[0] + '/'
|
|
||||||
|
|
||||||
new_key = urlparse.urlunsplit((
|
|
||||||
parsed.scheme,
|
|
||||||
parsed.netloc,
|
|
||||||
path,
|
|
||||||
parsed.query,
|
|
||||||
parsed.fragment
|
|
||||||
))
|
|
||||||
if new_key == key:
|
|
||||||
return None
|
|
||||||
key = new_key
|
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, data=None, headers=None, auth=None, verify=None,
|
def request(method, url, data=None, headers=None, auth=None, verify=None,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue