Merge pull request #1746 from hroff-1902/json-defaults

Support for defaults in json schema
This commit is contained in:
Matthias
2019-04-24 12:20:39 +02:00
committed by GitHub
9 changed files with 102 additions and 14 deletions

View File

@@ -9,7 +9,7 @@ from argparse import Namespace
from logging.handlers import RotatingFileHandler
from typing import Any, Dict, List, Optional
from jsonschema import Draft4Validator, validate
from jsonschema import Draft4Validator, validators
from jsonschema.exceptions import ValidationError, best_match
from freqtrade import OperationalException, constants
@@ -34,6 +34,27 @@ def set_loggers(log_level: int = 0) -> None:
logging.getLogger('telegram').setLevel(logging.INFO)
def _extend_with_default(validator_class):
validate_properties = validator_class.VALIDATORS["properties"]
def set_defaults(validator, properties, instance, schema):
for prop, subschema in properties.items():
if "default" in subschema:
instance.setdefault(prop, subschema["default"])
for error in validate_properties(
validator, properties, instance, schema,
):
yield error
return validators.extend(
validator_class, {"properties": set_defaults},
)
ValidatorWithDefaults = _extend_with_default(Draft4Validator)
class Configuration(object):
"""
Class to read and init the bot configuration
@@ -331,7 +352,7 @@ class Configuration(object):
:return: Returns the config if valid, otherwise throw an exception
"""
try:
validate(conf, constants.CONF_SCHEMA, Draft4Validator)
ValidatorWithDefaults(constants.CONF_SCHEMA).validate(conf)
return conf
except ValidationError as exception:
logger.critical(

View File

@@ -173,10 +173,10 @@ CONF_SCHEMA = {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'sandbox': {'type': 'boolean'},
'key': {'type': 'string'},
'secret': {'type': 'string'},
'password': {'type': 'string'},
'sandbox': {'type': 'boolean', 'default': False},
'key': {'type': 'string', 'default': ''},
'secret': {'type': 'string', 'default': ''},
'password': {'type': 'string', 'default': ''},
'uid': {'type': 'string'},
'pair_whitelist': {
'type': 'array',
@@ -199,7 +199,7 @@ CONF_SCHEMA = {
'ccxt_config': {'type': 'object'},
'ccxt_async_config': {'type': 'object'}
},
'required': ['name', 'key', 'secret', 'pair_whitelist']
'required': ['name', 'pair_whitelist']
},
'edge': {
'type': 'object',

View File

@@ -53,7 +53,7 @@ class FreqtradeBot(object):
self.rpc: RPCManager = RPCManager(self)
exchange_name = self.config.get('exchange', {}).get('name', 'bittrex').title()
exchange_name = self.config.get('exchange', {}).get('name').title()
self.exchange = ExchangeResolver(exchange_name, self.config).exchange
self.wallets = Wallets(self.config, self.exchange)

View File

@@ -68,7 +68,7 @@ class Backtesting(object):
self.config['dry_run'] = True
self.strategylist: List[IStrategy] = []
exchange_name = self.config.get('exchange', {}).get('name', 'bittrex').title()
exchange_name = self.config.get('exchange', {}).get('name').title()
self.exchange = ExchangeResolver(exchange_name, self.config).exchange
self.fee = self.exchange.get_fee()

View File

@@ -18,6 +18,15 @@ from freqtrade.state import RunMode
from freqtrade.tests.conftest import log_has
@pytest.fixture(scope="function")
def all_conf():
config_file = Path(__file__).parents[2] / "config_full.json.example"
print(config_file)
configuration = Configuration(Namespace())
conf = configuration._load_config_file(str(config_file))
return conf
def test_load_config_invalid_pair(default_conf) -> None:
default_conf['exchange']['pair_whitelist'].append('ETH-BTC')
@@ -608,3 +617,59 @@ def test_validate_tsl(default_conf):
default_conf['trailing_stop_positive_offset'] = 0.015
Configuration(Namespace())
configuration._validate_config_consistency(default_conf)
def test_load_config_default_exchange(all_conf) -> None:
"""
config['exchange'] subtree has required options in it
so it cannot be omitted in the config
"""
del all_conf['exchange']
assert 'exchange' not in all_conf
with pytest.raises(ValidationError,
match=r'\'exchange\' is a required property'):
configuration = Configuration(Namespace())
configuration._validate_config_schema(all_conf)
def test_load_config_default_exchange_name(all_conf) -> None:
"""
config['exchange']['name'] option is required
so it cannot be omitted in the config
"""
del all_conf['exchange']['name']
assert 'name' not in all_conf['exchange']
with pytest.raises(ValidationError,
match=r'\'name\' is a required property'):
configuration = Configuration(Namespace())
configuration._validate_config_schema(all_conf)
@pytest.mark.parametrize("keys", [("exchange", "sandbox", False),
("exchange", "key", ""),
("exchange", "secret", ""),
("exchange", "password", ""),
])
def test_load_config_default_subkeys(all_conf, keys) -> None:
"""
Test for parameters with default values in sub-paths
so they can be omitted in the config and the default value
should is added to the config.
"""
# Get first level key
key = keys[0]
# get second level key
subkey = keys[1]
del all_conf[key][subkey]
assert subkey not in all_conf[key]
configuration = Configuration(Namespace())
configuration._validate_config_schema(all_conf)
assert subkey in all_conf[key]
assert all_conf[key][subkey] == keys[2]