Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MRG] Add support for a local config file #25

Merged
merged 5 commits into from
Apr 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 1 addition & 3 deletions osfclient/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def main():
parser.add_argument('-u', '--username', default=None,
help=('OSF username. Provide password via '
'OSF_PASSWORD environment variable.'))
parser.add_argument('-p', '--project', default=None, help='OSF project.')
subparsers = parser.add_subparsers()

# Clone project
Expand All @@ -29,7 +30,6 @@ def main():
fetch_parser.set_defaults(func=fetch)
fetch_parser.add_argument('-f', help='Force overwriting of local file',
action='store_true')
fetch_parser.add_argument('project', help='OSF project ID')
fetch_parser.add_argument('remote', help='Remote path',
default=None)
fetch_parser.add_argument('local', help='Local path',
Expand All @@ -41,15 +41,13 @@ def main():
' storages for project.')
)
list_parser.set_defaults(func=list_)
list_parser.add_argument('project', help='OSF project ID')

# Upload a single file
upload_parser = subparsers.add_parser('upload',
description=('Upload a new file to'
' an existing project.')
)
upload_parser.set_defaults(func=upload)
upload_parser.add_argument('project', help='OSF project ID')
upload_parser.add_argument('source', help='Local file')
upload_parser.add_argument('destination', help='Remote file path')

Expand Down
46 changes: 43 additions & 3 deletions osfclient/cli.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,57 @@
"""Command line interface to the OSF"""

import os
import configparser
import sys

from .api import OSF
from .utils import norm_remote_path, split_storage


def _setup_osf(args):
# command line argument overrides environment variable
def config_from_file():
if os.path.exists(".osfcli.config"):
config_ = configparser.ConfigParser()
config_.read(".osfcli.config")

config = config_['osf']

else:
config = {}

return config


def config_from_env(config):
username = os.getenv("OSF_USERNAME")
if args.username is not None:
if username is not None:
config['username'] = username

project = os.getenv("OSF_PROJECT")
if project is not None:
config['project'] = project

return config


def _setup_osf(args):
# Command line options have precedence over environment variables,
# which have precedence over the config file.
config = config_from_env(config_from_file())

if args.username is None:
username = config.get('username')
else:
username = args.username

project = config.get('project')
if args.project is None:
args.project = project
# still None? We are in trouble
if args.project is None:
print('You have to specify a project ID via the command line,'
' configuration file or environment variable.')
sys.exit(1)

password = None
if username is not None:
password = os.getenv("OSF_PASSWORD")
Expand Down
83 changes: 83 additions & 0 deletions osfclient/tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from unittest.mock import call
from unittest.mock import Mock
from unittest.mock import patch

import pytest

from osfclient.tests.mocks import MockArgs

from osfclient import cli


@patch('osfclient.cli.os.path.exists', return_value=True)
@patch('osfclient.cli.configparser.ConfigParser')
def test_config_file(MockConfigParser, os_path_exists):
MockConfigParser().__getitem__ = Mock(return_value={'project': '1234'})

config = cli.config_from_file()

assert config == {'project': '1234'}

assert call.read('.osfcli.config') in MockConfigParser().mock_calls
assert call('osf') in MockConfigParser().__getitem__.mock_calls


def test_config_from_env_replace_username():
def simple_getenv(key):
if key == 'OSF_USERNAME':
return 'theusername'

with patch('osfclient.cli.os.getenv', side_effect=simple_getenv):
config = cli.config_from_env({'username': 'notusername'})

assert config == {'username': 'theusername'}


def test_config_from_env_username():
def simple_getenv(key):
if key == 'OSF_USERNAME':
return None

with patch('osfclient.cli.os.getenv', side_effect=simple_getenv):
config = cli.config_from_env({'username': 'theusername'})

assert config == {'username': 'theusername'}


def test_config_from_env_replace_project():
def simple_getenv(key):
if key == 'OSF_PROJECT':
return 'theproject'

with patch('osfclient.cli.os.getenv', side_effect=simple_getenv):
config = cli.config_from_env({'project': 'notproject'})

assert config == {'project': 'theproject'}


def test_config_from_env_project():
def simple_getenv(key):
if key == 'OSF_PROJECT':
return None

with patch('osfclient.cli.os.getenv', side_effect=simple_getenv):
config = cli.config_from_env({'project': 'theproject'})

assert config == {'project': 'theproject'}


def test_config_project(capsys):
# No project in args or the config, should sys.exit(1)
args = MockArgs(project=None)

def simple_config(key):
return {}

with patch('osfclient.cli.config_from_env', side_effect=simple_config):
with pytest.raises(SystemExit):
cli._setup_osf(args)

out, err = capsys.readouterr()
expected = ('specify a project ID via the command line, configuration '
'file or environment variable')
assert expected in out
15 changes: 12 additions & 3 deletions osfclient/tests/test_listing.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Test `osf ls` command"""

from unittest.mock import call
from unittest.mock import patch

from osfclient import OSF
Expand All @@ -11,16 +12,24 @@

@patch('osfclient.cli.OSF')
def test_anonymous_doesnt_use_password(MockOSF):
args = MockArgs()
args = MockArgs(project='1234')

list_(args)
def simple_getenv(key):
return None

with patch('osfclient.cli.os.getenv',
side_effect=simple_getenv) as mock_getenv:
list_(args)

# if there is no username we should not try to obtain a password either
assert call('OSF_USERNAME') in mock_getenv.mock_calls
assert call('OSF_PASSWORD') not in mock_getenv.mock_calls
MockOSF.assert_called_once_with(username=None, password=None)


@patch('osfclient.cli.OSF')
def test_username_password(MockOSF):
args = MockArgs(username='joe@example.com')
args = MockArgs(username='joe@example.com', project='1234')

def simple_getenv(key):
if key == 'OSF_PASSWORD':
Expand Down