.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
settings_loader.py
Go to the documentation of this file.
1# SPDX-License-Identifier: AGPL-3.0-or-later
2# pylint: disable=missing-module-docstring, too-many-branches
3
4from typing import Optional
5from os import environ
6from os.path import dirname, join, abspath, isfile
7from collections.abc import Mapping
8from itertools import filterfalse
9
10import yaml
11
12from searx.exceptions import SearxSettingsException
13
14
15searx_dir = abspath(dirname(__file__))
16
17
18def existing_filename_or_none(file_name: str) -> Optional[str]:
19 if isfile(file_name):
20 return file_name
21 return None
22
23
24def load_yaml(file_name):
25 try:
26 with open(file_name, 'r', encoding='utf-8') as settings_yaml:
27 return yaml.safe_load(settings_yaml)
28 except IOError as e:
29 raise SearxSettingsException(e, file_name) from e
30 except yaml.YAMLError as e:
31 raise SearxSettingsException(e, file_name) from e
32
33
35 return existing_filename_or_none(join(searx_dir, 'settings.yml'))
36
37
38def get_user_settings_path() -> Optional[str]:
39 """Get an user settings file.
40 By descending priority:
41 1. ``environ['SEARXNG_SETTINGS_PATH']``
42 2. ``/etc/searxng/settings.yml`` except if ``SEARXNG_DISABLE_ETC_SETTINGS`` is ``true`` or ``1``
43 3. ``None``
44 """
45
46 # check the environment variable SEARXNG_SETTINGS_PATH
47 # if the environment variable is defined, this is the last check
48 if 'SEARXNG_SETTINGS_PATH' in environ:
49 return existing_filename_or_none(environ['SEARXNG_SETTINGS_PATH'])
50
51 # if SEARXNG_DISABLE_ETC_SETTINGS don't look any further
52 if environ.get('SEARXNG_DISABLE_ETC_SETTINGS', '').lower() in ('1', 'true'):
53 return None
54
55 # check /etc/searxng/settings.yml
56 # (continue with other locations if the file is not found)
57 return existing_filename_or_none('/etc/searxng/settings.yml')
58
59
60def update_dict(default_dict, user_dict):
61 for k, v in user_dict.items():
62 if isinstance(v, Mapping):
63 default_dict[k] = update_dict(default_dict.get(k, {}), v)
64 else:
65 default_dict[k] = v
66 return default_dict
67
68
69def update_settings(default_settings, user_settings):
70 # merge everything except the engines
71 for k, v in user_settings.items():
72 if k not in ('use_default_settings', 'engines'):
73 if k in default_settings and isinstance(v, Mapping):
74 update_dict(default_settings[k], v)
75 else:
76 default_settings[k] = v
77
78 categories_as_tabs = user_settings.get('categories_as_tabs')
79 if categories_as_tabs:
80 default_settings['categories_as_tabs'] = categories_as_tabs
81
82 # parse the engines
83 remove_engines = None
84 keep_only_engines = None
85 use_default_settings = user_settings.get('use_default_settings')
86 if isinstance(use_default_settings, dict):
87 remove_engines = use_default_settings.get('engines', {}).get('remove')
88 keep_only_engines = use_default_settings.get('engines', {}).get('keep_only')
89
90 if 'engines' in user_settings or remove_engines is not None or keep_only_engines is not None:
91 engines = default_settings['engines']
92
93 # parse "use_default_settings.engines.remove"
94 if remove_engines is not None:
95 engines = list(filterfalse(lambda engine: (engine.get('name')) in remove_engines, engines))
96
97 # parse "use_default_settings.engines.keep_only"
98 if keep_only_engines is not None:
99 engines = list(filter(lambda engine: (engine.get('name')) in keep_only_engines, engines))
100
101 # parse "engines"
102 user_engines = user_settings.get('engines')
103 if user_engines:
104 engines_dict = dict((definition['name'], definition) for definition in engines)
105 for user_engine in user_engines:
106 default_engine = engines_dict.get(user_engine['name'])
107 if default_engine:
108 update_dict(default_engine, user_engine)
109 else:
110 engines.append(user_engine)
111
112 # store the result
113 default_settings['engines'] = engines
114
115 return default_settings
116
117
118def is_use_default_settings(user_settings):
119 use_default_settings = user_settings.get('use_default_settings')
120 if use_default_settings is True:
121 return True
122 if isinstance(use_default_settings, dict):
123 return True
124 if use_default_settings is False or use_default_settings is None:
125 return False
126 raise ValueError('Invalid value for use_default_settings')
127
128
129def load_settings(load_user_settings=True):
130 default_settings_path = get_default_settings_path()
131 user_settings_path = get_user_settings_path()
132 if user_settings_path is None or not load_user_settings:
133 # no user settings
134 return (load_yaml(default_settings_path), 'load the default settings from {}'.format(default_settings_path))
135
136 # user settings
137 user_settings = load_yaml(user_settings_path)
138 if is_use_default_settings(user_settings):
139 # the user settings are merged with the default configuration
140 default_settings = load_yaml(default_settings_path)
141 update_settings(default_settings, user_settings)
142 return (
143 default_settings,
144 'merge the default settings ( {} ) and the user settings ( {} )'.format(
145 default_settings_path, user_settings_path
146 ),
147 )
148
149 # the user settings, fully replace the default configuration
150 return (user_settings, 'load the user settings from {}'.format(user_settings_path))
is_use_default_settings(user_settings)
load_settings(load_user_settings=True)
Optional[str] get_user_settings_path()
update_settings(default_settings, user_settings)
Optional[str] existing_filename_or_none(str file_name)
update_dict(default_dict, user_dict)