Passed
Push — master ( 360a2b...59e88b )
by Emmanuel
101:27
created

stakkr.configreader._merge_dictionaries()   A

Complexity

Conditions 4

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 8
nop 2
dl 0
loc 11
ccs 8
cts 8
cp 1
crap 4
rs 10
c 0
b 0
f 0
1
# coding: utf-8
2 1
"""Simple Config Reader."""
3
4 1
from os import path
5 1
from sys import stderr
6 1
import anyconfig
7 1
from jsonschema.exceptions import _Error
8 1
from stakkr.file_utils import get_file, find_project_dir
9 1
from yaml import FullLoader
10
11
12 1
class Config:
13
    """
14
    Parser of Stakkr.
15
16
    Set default values and validate stakkr.yml with specs
17
    """
18
19 1
    def __init__(self, config_file: str):
20
        """
21
        Build list of files to validate a config, set default values
22
        Then the given config file
23
        """
24 1
        self.config_file, self.project_dir = get_config_and_project_dir(config_file)
25 1
        self._build_config_files_list()
26 1
        self._build_config_schemas_list()
27 1
        self.error = ''
28
29 1
    def display_errors(self):
30
        """Display errors in STDERR."""
31 1
        from click import style
32
33 1
        msg = 'Failed validating config ('
34 1
        msg += ', '.join(self.config_files)
35 1
        msg += '):\n    - {}\n'.format(self.error)
36 1
        msg += '\nMake sure you have the right services.\n'
37 1
        stderr.write(style(msg, fg='red'))
38
39 1
    def read(self):
40
        """
41
        Parse the configs and validate it.
42
43
        It could be either local or from a local services
44
        (first local then packages by alphabetical order).
45
        """
46
47 1
        spec = {}
48 1
        for filepath in self.spec_files:
49 1
            yaml = anyconfig.load(filepath, Loader=FullLoader)
50 1
            spec = _merge_dictionaries(spec, yaml)
51
        # Waiting anyconfig to work for that :
52
        # schema = anyconfig.load(self.spec_files, Loader=FullLoader)
53
54 1
        conf = {}
55 1
        for filepath in self.config_files:
56 1
            yaml = anyconfig.load(filepath, Loader=FullLoader)
57 1
            conf = _merge_dictionaries(conf, yaml)
58
        # Waiting anyconfig to work for that :
59
        # config = anyconfig.load(self.spec_files, Loader=FullLoader)
60
61
        # Make sure the compiled configuration is valid
62 1
        valid, errs = anyconfig.validate(conf, spec, ac_schema_safe=False, ac_schema_errors=True)
63 1
        if valid is False:
64 1
            self.error = errs[0]
65 1
            return False
66
67 1
        conf['project_dir'] = path.realpath(path.dirname(self.config_file))
68 1
        if conf['project_name'] == '':
69 1
            conf['project_name'] = path.basename(conf['project_dir'])
70
71 1
        return conf
72
73 1
    def _build_config_files_list(self):
74 1
        self.config_files = [
75
            # Stakkr default config
76
            get_file('static', 'config_default.yml'),
77
            '{}/services/*/config_default.yml'.format(self.project_dir)]
78
        # Stakkr main config file finally with user's values
79 1
        self.config_files += [self.config_file]
80
81 1
    def _build_config_schemas_list(self):
82 1
        self.spec_files = [
83
            # Stakkr config validation
84
            get_file('static', 'config_schema.yml'),
85
            '{}/services/*/config_schema.yml'.format(self.project_dir)]
86
87
88 1
def get_config_and_project_dir(config_file: str):
89
    """Guess config file name and project dir"""
90 1
    if config_file is not None:
91 1
        config_file = path.abspath(config_file)
92 1
        project_dir = path.dirname(config_file)
93
    else:
94 1
        project_dir = find_project_dir()
95
        config_file = '{}/stakkr.yml'.format(project_dir)
96
97 1
    return config_file, project_dir
98
99
100 1
def _merge_dictionaries(dict1, dict2):
101 1
    for key, val in dict1.items():
102 1
        if isinstance(val, dict):
103 1
            dict2_node = dict2.setdefault(key, {})
104 1
            _merge_dictionaries(val, dict2_node)
105
106
        else:
107 1
            if key not in dict2:
108 1
                dict2[key] = val
109
110
    return dict2
111