Skip to content

Commit

Permalink
Merge pull request #25 from betatim/config-file
Browse files Browse the repository at this point in the history
[MRG] Add support for a local config file
  • Loading branch information
betatim committed Apr 20, 2017
2 parents f601218 + 1ed6208 commit fe98fa1
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 9 deletions.
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 @@ -28,7 +29,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 @@ -40,15 +40,13 @@ def main():
formatter_class=argparse.RawDescriptionHelpFormatter
)
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.__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter
)
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

0 comments on commit fe98fa1

Please sign in to comment.