diff --git a/madcc/conf.py b/madcc/conf.py deleted file mode 100644 index 7f6a3a8..0000000 --- a/madcc/conf.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import sys -from imp import load_source -from pathlib import Path -from . import const - - -class Settings(dict): - def __getattr__(self, item): - return self.get(item) - - def __setattr__(self, key, value): - self[key] = value - - def init(self, args=None): - """Fills `settings` with values from `settings.py` and env.""" - self._setup_user_dir() - self._init_settings_file() - - try: - self.update(self._settings_from_file()) - except Exception: - exception("Can't load settings from file", sys.exc_info()) - - # try: - # self.update(self._settings_from_env()) - # except Exception: - # exception("Can't load settings from env", sys.exc_info()) - - # self.update(self._settings_from_args(args)) - - def _setup_user_dir(self): - """Returns user config dir, create it when it doesn't exist.""" - xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '~/.config') - user_dir = Path(xdg_config_home, 'madcc').expanduser() - - if not user_dir.is_dir(): - user_dir.mkdir(parents=True) - self.user_dir = user_dir - - def _init_settings_file(self): - """Create default settings file if it does not exist.""" - settings_path = self.user_dir.joinpath('settings.py') - if not settings_path.is_file(): - with settings_path.open(mode='w') as settings_file: - settings_file.write(const.SETTINGS_HEADER) - for setting in const.DEFAULT_SETTINGS.items(): - settings_file.write(u'# {} = {}\n'.format(*setting)) - - def _settings_from_file(self): - """Loads settings from file.""" - settings = load_source( - 'settings', str(self.user_dir.joinpath('settings.py'))) - return {key: getattr(settings, key) - for key in const.DEFAULT_SETTINGS.keys() - if hasattr(settings, key)} - - -settings = Settings(const.DEFAULT_SETTINGS) diff --git a/madcc/const.py b/madcc/const.py deleted file mode 100644 index 3a38472..0000000 --- a/madcc/const.py +++ /dev/null @@ -1,12 +0,0 @@ -class _GenConst(object): - def __init__(self, name): - self._name = name - - def __repr__(self): - return u''.format(self._name) - - -DEFAULT_SETTINGS = {} -SETTINGS_HEADER = u"""# madcc settings file -# -""" diff --git a/madcc/entrypoints/crypto_assets.py b/madcc/entrypoints/crypto_assets.py index 103500e..d814e06 100644 --- a/madcc/entrypoints/crypto_assets.py +++ b/madcc/entrypoints/crypto_assets.py @@ -1,7 +1,7 @@ #!/usr/bin/env python from ..utils import crypto_assets -import configparser +import json import sys from clint import resources @@ -11,22 +11,22 @@ def main(): resources.init('madtech', 'madcc') - if not resources.user.read('config.ini'): - config = configparser.ConfigParser() - config['crypto_assets'] = {} + if not resources.user.read('config.json'): + config = dict() + config['crypto_assets'] = dict() config['crypto_assets']['crypto_file'] = resources.user.path + '/crypto.txt' config['crypto_assets']['currency'] = 'eur' - with resources.user.open('config.ini', 'w') as configfile: - config.write(configfile) + + configfile = resources.user.open('config.json', 'w') + configfile.write(json.dumps(config, sort_keys=True, indent=4)) else: - config = configparser.ConfigParser() - with resources.user.open('config.ini', 'r') as configfile: - config.read_file(configfile) + configfile = resources.user.open('config.json', 'r') + config = json.loads(configfile.read()) args = Args() if next(iter(args.grouped.get('--currency', [])), '').upper() in crypto_assets.CURRENCIES: - currency = args.grouped.get('--currency', {}).get(0) + currency = next(iter(args.grouped.get('--currency', [])), '') elif str(args.last or '').upper() in crypto_assets.CURRENCIES: currency = args.last else: @@ -39,11 +39,11 @@ def main(): crypto_data = crypto_assets.parse_crypto_file(config['crypto_assets']['crypto_file']) if not crypto_data: - sys.exit(1) + return False headers, crypto_table = crypto_assets.generate_crypto_table(currency, crypto_data) - print(tabulate(crypto_table, headers=headers, floatfmt='.{}f'.format(decimals))) + return tabulate(crypto_table, headers=headers, floatfmt='.{}f'.format(decimals)) -if __name__ == "__main__": - main() +if __name__ == "__main__": # pragma: no cover + print(main()) diff --git a/madcc/entrypoints/kraken_limits.py b/madcc/entrypoints/kraken_limits.py index d32e4b6..c83fdfc 100644 --- a/madcc/entrypoints/kraken_limits.py +++ b/madcc/entrypoints/kraken_limits.py @@ -1,10 +1,14 @@ from ..kraken.kraken import KrakenUtils -def main(): - k = KrakenUtils() +def main(authfile=None): + k = KrakenUtils(authfile=authfile) deposit_limit = k.deposit_limit() withdraw_limit = k.withdraw_limit() - print('deposit max: {} EUR'.format(deposit_limit)) - print('withdraw max: {} BTC'.format(withdraw_limit)) + return 'deposit max: {} EUR\nwithdraw max: {} BTC'.format(deposit_limit, + withdraw_limit) + + +if __name__ == "__main__": # pragma: no cover + print(main()) diff --git a/madcc/kraken/kraken.py b/madcc/kraken/kraken.py index 81ce4f7..da4734c 100644 --- a/madcc/kraken/kraken.py +++ b/madcc/kraken/kraken.py @@ -6,15 +6,16 @@ import krakenex import requests - -from ..conf import settings +from clint import resources class KrakenUtils(object): - def __init__(self): - # TODO: get config somewhere else, don't load it in the module? - settings.init() - self._set_auth(str(settings.user_dir.joinpath('kraken.auth'))) + def __init__(self, authfile=None): + self._auth_file = authfile + self._set_auth() + if not self.api_key or not self.api_secret: + print('No api key or secret found') + sys.exit(1) self.api = krakenex.API(key=self.api_key, secret=self.api_secret) if not self.api_live(): # TODO: use logging instead of print @@ -37,9 +38,17 @@ def _init_auth_file(self): else: print('Only public api calls allowed') - def _set_auth(self, auth_file=None): + def _set_auth(self): + self.api_key = None + self.api_secret = None """Read the api auth data from file""" - self.api_key, self.api_secret = open(auth_file).read().splitlines() + if self._auth_file: + self.api_key, self.api_secret = open(self._auth_file).read().splitlines() + else: + resources.init('madtech', 'madcc') + self._auth_file = resources.user.path + '/kraken.auth' + if resources.user.read('kraken.auth'): + self.api_key, self.api_secret = resources.user.read('kraken.auth').splitlines() def api_live(self): res = self.api.query_public('Time') diff --git a/tests/test_crypto_assets.py b/tests/test_crypto_assets.py index 08b8c97..ec6d199 100644 --- a/tests/test_crypto_assets.py +++ b/tests/test_crypto_assets.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from madcc.utils import crypto_assets @@ -45,7 +43,7 @@ ] ) -demo_output = """symbol amount % eur price eur total +crypto_output = """symbol amount % eur price eur total -------- -------- ----- ----------- ----------- bitcoin 12.05 52.35 6615.32 79714.56 ethereum 80.20 25.88 491.39 39409.12 @@ -57,6 +55,7 @@ def test_convert(mocker): mocker.patch('requests.get') crypto_assets.convert('eur', 10, 'usd') + crypto_assets.requests.get.assert_called_with( crypto_assets.currency_api, params={'base': 'EUR', 'symbols': 'USD'} @@ -68,7 +67,7 @@ def test_convert_same(): def test_parse_crypto_file(mocker): - with patch('builtins.open', mocker.mock_open(read_data=raw_crypto_file)) as m: + with mocker.mock_module.patch('builtins.open', mocker.mock_open(read_data=raw_crypto_file)) as m: result = crypto_assets.parse_crypto_file('crypto_file') m.assert_called_once_with('crypto_file') @@ -76,8 +75,7 @@ def test_parse_crypto_file(mocker): def test_parse_crypto_file_fail_open(mocker): - - with patch('builtins.open', mocker.mock_open()) as m: + with mocker.mock_module.patch('builtins.open', mocker.mock_open()) as m: m.side_effect = IOError result = crypto_assets.parse_crypto_file('crypto_file') @@ -94,8 +92,8 @@ def test_retrieve_ticker_data(mocker): def test_generate_crypto_table(mocker): mocker.patch.object(crypto_assets, 'retrieve_ticker_data', return_value=full_ticker_data) - result = crypto_assets.generate_crypto_table('eur', parsed_crypto_file) + assert result == generated_crypto_table @@ -105,4 +103,94 @@ def test_generate_crypto_table_missing_data(): def test_demo(mocker): mocker.patch.object(crypto_assets, 'generate_crypto_table', return_value=generated_crypto_table) - assert crypto_assets.demo() == demo_output + + assert crypto_assets.demo() == crypto_output + + +@pytest.fixture(scope='session') +def config_dir(tmpdir_factory): + return tmpdir_factory.mktemp('madcc') + + +def test_crypto_assets_cli_no_config(mocker, config_dir): + mocker.patch.object(crypto_assets_cli, 'resources') + crypto_assets_cli.resources.user.read.return_value = None + crypto_assets_cli.resources.user.path = str(config_dir) + crypto_assets_cli.resources.user.open.return_value = config_dir.join('config.json') + crypto_assets_cli.main() + + crypto_assets_cli.resources.user.open.assert_called_once_with('config.json', 'w') + + +def test_crypto_assets_cli_without_data(mocker, config_dir): + mocker.patch.object(crypto_assets_cli, 'resources') + crypto_assets_cli.resources.user.read.return_value = True + crypto_assets_cli.resources.user.open.return_value = config_dir.join('config.json') + crypto_assets_cli.main() + + crypto_assets_cli.resources.user.open.assert_called_once_with('config.json', 'r') + assert crypto_assets_cli.main() is False + + +def test_crypto_assets_cli_with_data(mocker, config_dir): + mocker.patch.object(crypto_assets_cli, 'resources') + crypto_assets_cli.resources.user.read.return_value = None + crypto_assets_cli.resources.user.path = str(config_dir) + crypto_assets_cli.resources.user.open.return_value = config_dir.join('config.json') + mocker.patch.object(crypto_assets_cli, 'crypto_assets') + crypto_assets_cli.crypto_assets.generate_crypto_table.return_value = generated_crypto_table + config_dir.join('crypto.txt').write(raw_crypto_file) + + assert crypto_assets_cli.main() == crypto_output + + +def test_crypto_assets_cli_currency_eur(mocker, config_dir): + mocker.patch.object(crypto_assets_cli, 'resources') + crypto_assets_cli.resources.user.read.return_value = None + crypto_assets_cli.resources.user.path = str(config_dir) + crypto_assets_cli.resources.user.open.return_value = config_dir.join('config.json') + mocker.patch('madcc.utils.crypto_assets.generate_crypto_table', autospect=True, return_value=generated_crypto_table) + mocker.patch.object(crypto_assets_cli, 'Args') + crypto_assets_cli.Args.return_value.last = 'eur' + crypto_assets_cli.main() + + crypto_assets_cli.crypto_assets.generate_crypto_table.assert_called_with('eur', parsed_crypto_file) + + +def test_crypto_assets_cli_currency_usd(mocker, config_dir): + mocker.patch.object(crypto_assets_cli, 'resources') + crypto_assets_cli.resources.user.read.return_value = None + crypto_assets_cli.resources.user.path = str(config_dir) + crypto_assets_cli.resources.user.open.return_value = config_dir.join('config.json') + mocker.patch('madcc.utils.crypto_assets.generate_crypto_table', autospect=True, return_value=generated_crypto_table) + mocker.patch.object(crypto_assets_cli, 'Args') + crypto_assets_cli.Args.return_value.last = 'usd' + crypto_assets_cli.main() + + crypto_assets_cli.crypto_assets.generate_crypto_table.assert_called_with('usd', parsed_crypto_file) + + +def test_crypto_assets_cli_currency_btc(mocker, config_dir): + mocker.patch.object(crypto_assets_cli, 'resources') + crypto_assets_cli.resources.user.read.return_value = None + crypto_assets_cli.resources.user.path = str(config_dir) + crypto_assets_cli.resources.user.open.return_value = config_dir.join('config.json') + mocker.patch('madcc.utils.crypto_assets.generate_crypto_table', autospect=True, return_value=generated_crypto_table) + mocker.patch.object(crypto_assets_cli, 'Args') + crypto_assets_cli.Args.return_value.last = 'btc' + crypto_assets_cli.main() + + crypto_assets_cli.crypto_assets.generate_crypto_table.assert_called_with('btc', parsed_crypto_file) + + +def test_crypto_assets_cli_currency_unknown(mocker, config_dir): + mocker.patch.object(crypto_assets_cli, 'resources') + crypto_assets_cli.resources.user.read.return_value = None + crypto_assets_cli.resources.user.path = str(config_dir) + crypto_assets_cli.resources.user.open.return_value = config_dir.join('config.json') + mocker.patch('madcc.utils.crypto_assets.generate_crypto_table', autospect=True, return_value=generated_crypto_table) + mocker.patch.object(crypto_assets_cli, 'Args') + crypto_assets_cli.Args.return_value.last = 'abc' + crypto_assets_cli.main() + + crypto_assets_cli.crypto_assets.generate_crypto_table.assert_called_with('eur', parsed_crypto_file) diff --git a/tests/test_kraken.py b/tests/test_kraken.py index 7785bf1..c2dddd2 100644 --- a/tests/test_kraken.py +++ b/tests/test_kraken.py @@ -2,3 +2,26 @@ from madcc.kraken import KrakenUtils from madcc.entrypoints import kraken_limits + + +# some_api_key\nsome_api_secret both base64 encoded +raw_kraken_auth = """c29tZV9hcGlfa2V5Cg== +c29tZV9hcGlfc2VjcmV0Cg==""" + +wrong_credentials_result = """deposit max: False EUR +withdraw max: False BTC""" + + +@pytest.fixture(scope='session') +def config_dir(tmpdir_factory): + return tmpdir_factory.mktemp('madcc') + + +# TODO: this tests the krakenex api more than my code +# also mock the kraken api to test failures better and circumvent network +# access +def test_kraken_limits_wrong_credentials(mocker, config_dir): + config_dir.join('kraken.auth').write(raw_kraken_auth) + result = kraken_limits.main(str(config_dir.join('kraken.auth'))) + + assert result == wrong_credentials_result