2019-08-10 12:15:09 +00:00
|
|
|
"""
|
|
|
|
This module contain functions to load the configuration file
|
|
|
|
"""
|
2024-05-12 14:29:24 +00:00
|
|
|
|
2019-08-10 12:15:09 +00:00
|
|
|
import logging
|
2020-03-22 19:09:01 +00:00
|
|
|
import re
|
2019-08-10 12:15:09 +00:00
|
|
|
import sys
|
2022-04-07 18:13:52 +00:00
|
|
|
from copy import deepcopy
|
2020-03-22 19:09:01 +00:00
|
|
|
from pathlib import Path
|
2023-01-21 14:01:56 +00:00
|
|
|
from typing import Any, Dict, List, Optional
|
2019-08-10 12:15:09 +00:00
|
|
|
|
2020-03-22 19:09:01 +00:00
|
|
|
import rapidjson
|
2019-08-10 12:15:09 +00:00
|
|
|
|
2022-09-18 11:20:36 +00:00
|
|
|
from freqtrade.constants import MINIMAL_CONFIG, Config
|
2024-03-19 06:08:05 +00:00
|
|
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
2022-04-07 18:13:52 +00:00
|
|
|
from freqtrade.misc import deep_merge_dicts
|
2019-08-10 12:15:09 +00:00
|
|
|
|
2020-09-28 17:39:41 +00:00
|
|
|
|
2019-08-10 12:15:09 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2019-08-11 21:32:03 +00:00
|
|
|
CONFIG_PARSE_MODE = rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS
|
|
|
|
|
|
|
|
|
2020-03-22 19:09:01 +00:00
|
|
|
def log_config_error_range(path: str, errmsg: str) -> str:
|
|
|
|
"""
|
|
|
|
Parses configuration file and prints range around error
|
|
|
|
"""
|
2024-05-12 14:29:24 +00:00
|
|
|
if path != "-":
|
|
|
|
offsetlist = re.findall(r"(?<=Parse\serror\sat\soffset\s)\d+", errmsg)
|
2020-03-22 19:09:01 +00:00
|
|
|
if offsetlist:
|
|
|
|
offset = int(offsetlist[0])
|
|
|
|
text = Path(path).read_text()
|
|
|
|
# Fetch an offset of 80 characters around the error line
|
2024-05-12 14:29:24 +00:00
|
|
|
subtext = text[offset - min(80, offset) : offset + 80]
|
|
|
|
segments = subtext.split("\n")
|
2020-03-23 06:54:27 +00:00
|
|
|
if len(segments) > 3:
|
|
|
|
# Remove first and last lines, to avoid odd truncations
|
2024-05-12 14:29:24 +00:00
|
|
|
return "\n".join(segments[1:-1])
|
2020-03-23 06:54:27 +00:00
|
|
|
else:
|
|
|
|
return subtext
|
2024-05-12 14:29:24 +00:00
|
|
|
return ""
|
2020-03-22 19:09:01 +00:00
|
|
|
|
|
|
|
|
2021-04-06 09:59:58 +00:00
|
|
|
def load_file(path: Path) -> Dict[str, Any]:
|
|
|
|
try:
|
2024-05-12 14:29:24 +00:00
|
|
|
with path.open("r") as file:
|
2021-04-06 09:59:58 +00:00
|
|
|
config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE)
|
|
|
|
except FileNotFoundError:
|
2024-03-15 17:19:56 +00:00
|
|
|
raise OperationalException(f'File "{path}" not found!') from None
|
2021-04-06 09:59:58 +00:00
|
|
|
return config
|
|
|
|
|
|
|
|
|
2019-08-10 12:15:09 +00:00
|
|
|
def load_config_file(path: str) -> Dict[str, Any]:
|
|
|
|
"""
|
|
|
|
Loads a config file from the given path
|
|
|
|
:param path: path as str
|
|
|
|
:return: configuration as dictionary
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# Read config from stdin if requested in the options
|
2024-05-12 14:29:24 +00:00
|
|
|
with Path(path).open() if path != "-" else sys.stdin as file:
|
2019-08-11 21:32:03 +00:00
|
|
|
config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE)
|
2019-08-10 12:15:09 +00:00
|
|
|
except FileNotFoundError:
|
|
|
|
raise OperationalException(
|
|
|
|
f'Config file "{path}" not found!'
|
2024-05-12 14:29:24 +00:00
|
|
|
" Please create a config file or check whether it exists."
|
|
|
|
) from None
|
2020-03-22 19:09:01 +00:00
|
|
|
except rapidjson.JSONDecodeError as e:
|
|
|
|
err_range = log_config_error_range(path, str(e))
|
2024-03-19 06:08:05 +00:00
|
|
|
raise ConfigurationError(
|
2024-05-12 15:51:21 +00:00
|
|
|
f"{e}\nPlease verify the following segment of your configuration:\n{err_range}"
|
2024-05-12 14:29:24 +00:00
|
|
|
if err_range
|
|
|
|
else "Please verify your configuration file for syntax errors."
|
2020-03-22 19:09:01 +00:00
|
|
|
)
|
2019-08-10 12:15:09 +00:00
|
|
|
|
|
|
|
return config
|
2022-04-07 18:13:52 +00:00
|
|
|
|
|
|
|
|
2023-01-21 14:01:56 +00:00
|
|
|
def load_from_files(
|
2024-05-12 14:29:24 +00:00
|
|
|
files: List[str], base_path: Optional[Path] = None, level: int = 0
|
|
|
|
) -> Dict[str, Any]:
|
2022-04-07 18:29:03 +00:00
|
|
|
"""
|
|
|
|
Recursively load configuration files if specified.
|
|
|
|
Sub-files are assumed to be relative to the initial config.
|
|
|
|
"""
|
2022-09-18 11:20:36 +00:00
|
|
|
config: Config = {}
|
2022-04-07 18:29:03 +00:00
|
|
|
if level > 5:
|
2024-03-19 06:08:05 +00:00
|
|
|
raise ConfigurationError("Config loop detected.")
|
2022-04-07 18:13:52 +00:00
|
|
|
|
|
|
|
if not files:
|
|
|
|
return deepcopy(MINIMAL_CONFIG)
|
2022-04-08 15:26:51 +00:00
|
|
|
files_loaded = []
|
2022-04-07 18:13:52 +00:00
|
|
|
# We expect here a list of config filenames
|
2022-04-07 18:29:03 +00:00
|
|
|
for filename in files:
|
2024-05-12 14:29:24 +00:00
|
|
|
logger.info(f"Using config: {filename} ...")
|
|
|
|
if filename == "-":
|
2022-04-07 18:29:03 +00:00
|
|
|
# Immediately load stdin and return
|
|
|
|
return load_config_file(filename)
|
|
|
|
file = Path(filename)
|
|
|
|
if base_path:
|
|
|
|
# Prepend basepath to allow for relative assignments
|
|
|
|
file = base_path / file
|
|
|
|
|
|
|
|
config_tmp = load_config_file(str(file))
|
2024-05-12 14:29:24 +00:00
|
|
|
if "add_config_files" in config_tmp:
|
2022-04-08 15:36:50 +00:00
|
|
|
config_sub = load_from_files(
|
2024-05-12 14:29:24 +00:00
|
|
|
config_tmp["add_config_files"], file.resolve().parent, level + 1
|
|
|
|
)
|
|
|
|
files_loaded.extend(config_sub.get("config_files", []))
|
2022-04-10 08:14:34 +00:00
|
|
|
config_tmp = deep_merge_dicts(config_tmp, config_sub)
|
2022-04-07 18:29:03 +00:00
|
|
|
|
2022-04-08 15:26:51 +00:00
|
|
|
files_loaded.insert(0, str(file))
|
|
|
|
|
2022-04-07 18:29:03 +00:00
|
|
|
# Merge config options, overwriting prior values
|
|
|
|
config = deep_merge_dicts(config_tmp, config)
|
2022-04-07 18:13:52 +00:00
|
|
|
|
2024-05-12 14:29:24 +00:00
|
|
|
config["config_files"] = files_loaded
|
2022-04-07 18:13:52 +00:00
|
|
|
|
|
|
|
return config
|