From e3eaad07b1bfb82ca4f959285b8cf866faa54758 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 2 Sep 2017 01:10:21 +0200 Subject: [PATCH] use jsonschema instead of custom type validations --- requirements.txt | 3 +- utils.py | 144 ++++++++++++++++++++--------------------------- 2 files changed, 64 insertions(+), 83 deletions(-) diff --git a/requirements.txt b/requirements.txt index 934380d1c..ca6ef974a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,5 @@ matplotlib==2.0.2 PYQT5==5.9 scikit-learn==0.19.0 scipy==0.19.1 -stockstats==0.2.0 \ No newline at end of file +stockstats==0.2.0 +jsonschema==2.6.0 \ No newline at end of file diff --git a/utils.py b/utils.py index faff4619a..e7bb006fd 100644 --- a/utils.py +++ b/utils.py @@ -1,102 +1,82 @@ import json import logging -from typing import List +from jsonschema import validate from wrapt import synchronized -from bittrex.bittrex import Bittrex logger = logging.getLogger(__name__) _cur_conf = None +# Required json-schema for user specified config +_conf_schema = { + 'type': 'object', + 'properties': { + 'max_open_trades': {'type': 'integer'}, + 'stake_currency': {'type': 'string'}, + 'stake_amount': {'type': 'number'}, + 'dry_run': {'type': 'boolean'}, + 'minimal_roi': { + 'type': 'object', + 'patternProperties': { + '^[0-9.]+$': {'type': 'number'} + }, + 'minProperties': 1 + }, + 'poloniex': {'$ref': '#/definitions/exchange'}, + 'bittrex': {'$ref': '#/definitions/exchange'}, + 'telegram': { + 'type': 'object', + 'properties': { + 'enabled': {'type': 'boolean'}, + 'token': {'type': 'string'}, + 'chat_id': {'type': 'string'}, + }, + 'required': ['enabled', 'token', 'chat_id'] + } + }, + 'definitions': { + 'exchange': { + 'type': 'object', + 'properties': { + 'enabled': {'type': 'boolean'}, + 'key': {'type': 'string'}, + 'secret': {'type': 'string'}, + 'pair_whitelist': { + 'type': 'array', + 'items': {'type': 'string'}, + 'uniqueItems': True + } + }, + 'required': ['enabled', 'key', 'secret', 'pair_whitelist'] + } + }, + 'anyOf': [ + {'required': ['poloniex']}, + {'required': ['bittrex']} + ], + 'required': [ + 'max_open_trades', + 'stake_currency', + 'stake_amount', + 'dry_run', + 'minimal_roi', + 'telegram' + ] +} + + @synchronized def get_conf(filename: str='config.json') -> dict: """ - Loads the config into memory and returns the instance of it + Loads the config into memory validates it + and returns the singleton instance :return: dict """ global _cur_conf if not _cur_conf: with open(filename) as file: _cur_conf = json.load(file) - validate_conf(_cur_conf) + validate(_cur_conf, _conf_schema) return _cur_conf - - -def validate_conf(conf: dict) -> None: - """ - Validates if the minimal possible config is provided - :param conf: config as dict - :return: None, raises ValueError if something is wrong - """ - if not isinstance(conf.get('max_open_trades'), int): - raise ValueError('max_open_trades must be a int') - if not isinstance(conf.get('stake_currency'), str): - raise ValueError('stake_currency must be a str') - if not isinstance(conf.get('stake_amount'), float): - raise ValueError('stake_amount must be a float') - if not isinstance(conf.get('dry_run'), bool): - raise ValueError('dry_run must be a boolean') - if not isinstance(conf.get('minimal_roi'), dict): - raise ValueError('minimal_roi must be a dict') - - for index, (minutes, threshold) in enumerate(conf.get('minimal_roi').items()): - if not isinstance(minutes, str): - raise ValueError('minimal_roi[{}].key must be a string'.format(index)) - if not isinstance(threshold, float): - raise ValueError('minimal_roi[{}].value must be a float'.format(index)) - - if conf.get('telegram'): - telegram = conf.get('telegram') - if not isinstance(telegram.get('token'), str): - raise ValueError('telegram.token must be a string') - if not isinstance(telegram.get('chat_id'), str): - raise ValueError('telegram.chat_id must be a string') - - if conf.get('poloniex'): - poloniex = conf.get('poloniex') - if not isinstance(poloniex.get('key'), str): - raise ValueError('poloniex.key must be a string') - if not isinstance(poloniex.get('secret'), str): - raise ValueError('poloniex.secret must be a string') - if not isinstance(poloniex.get('pair_whitelist'), list): - raise ValueError('poloniex.pair_whitelist must be a list') - if poloniex.get('enabled', False): - raise ValueError('poloniex is currently not implemented') - #if not poloniex.get('pair_whitelist'): - # raise ValueError('poloniex.pair_whitelist must contain some pairs') - - if conf.get('bittrex'): - bittrex = conf.get('bittrex') - if not isinstance(bittrex.get('key'), str): - raise ValueError('bittrex.key must be a string') - if not isinstance(bittrex.get('secret'), str): - raise ValueError('bittrex.secret must be a string') - if not isinstance(bittrex.get('pair_whitelist'), list): - raise ValueError('bittrex.pair_whitelist must be a list') - if bittrex.get('enabled', False): - if not bittrex.get('pair_whitelist'): - raise ValueError('bittrex.pair_whitelist must contain some pairs') - validate_bittrex_pairs(bittrex.get('pair_whitelist')) - - if conf.get('poloniex', {}).get('enabled', False) \ - and conf.get('bittrex', {}).get('enabled', False): - raise ValueError('Cannot use poloniex and bittrex at the same time') - - logger.info('Config is valid ...') - - -def validate_bittrex_pairs(pairs: List[str]) -> None: - """ - Validates if all given pairs exist on bittrex - :param pairs: list of str - :return: None - """ - data = Bittrex(None, None).get_markets() - if not data['success']: - raise RuntimeError('BITTREX: {}'.format(data['message'])) - available_markets = [market['MarketName'].replace('-', '_')for market in data['result']] - for pair in pairs: - if pair not in available_markets: - raise ValueError('Invalid pair: {}'.format(pair))