.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
settings_defaults.py
Go to the documentation of this file.
1# SPDX-License-Identifier: AGPL-3.0-or-later
2"""Implementation of the default settings.
3
4"""
5
6import typing
7import numbers
8import errno
9import os
10import logging
11from base64 import b64decode
12from os.path import dirname, abspath
13
14from .sxng_locales import sxng_locales
15
16searx_dir = abspath(dirname(__file__))
17
18logger = logging.getLogger('searx')
19OUTPUT_FORMATS = ['html', 'csv', 'json', 'rss']
20SXNG_LOCALE_TAGS = ['all', 'auto'] + list(l[0] for l in sxng_locales)
21SIMPLE_STYLE = ('auto', 'light', 'dark', 'black')
22CATEGORIES_AS_TABS = {
23 'general': {},
24 'images': {},
25 'videos': {},
26 'news': {},
27 'map': {},
28 'music': {},
29 'it': {},
30 'science': {},
31 'files': {},
32 'social media': {},
33}
34STR_TO_BOOL = {
35 '0': False,
36 'false': False,
37 'off': False,
38 '1': True,
39 'true': True,
40 'on': True,
41}
42_UNDEFINED = object()
43
44
46 """Check and update a setting value"""
47
49 self,
50 type_definition: typing.Union[None, typing.Any, typing.Tuple[typing.Any]] = None,
51 default: typing.Any = None,
52 environ_name: str = None,
53 ):
55 type_definition if type_definition is None or isinstance(type_definition, tuple) else (type_definition,)
56 )
57 self.default = default
58 self.environ_name = environ_name
59
60 @property
62 types_str = [t.__name__ if isinstance(t, type) else repr(t) for t in self.type_definition]
63 return ', '.join(types_str)
64
65 def check_type_definition(self, value: typing.Any) -> None:
66 if value in self.type_definition:
67 return
68 type_list = tuple(t for t in self.type_definition if isinstance(t, type))
69 if not isinstance(value, type_list):
70 raise ValueError('The value has to be one of these types/values: {}'.format(self.type_definition_reprtype_definition_repr))
71
72 def __call__(self, value: typing.Any) -> typing.Any:
73 if value == _UNDEFINED:
74 value = self.default
75 # override existing value with environ
76 if self.environ_name and self.environ_name in os.environ:
77 value = os.environ[self.environ_name]
78 if self.type_definition == (bool,):
79 value = STR_TO_BOOL[value.lower()]
80
81 self.check_type_definition(value)
82 return value
83
84
86 """Check the value is a sublist of type definition."""
87
88 def check_type_definition(self, value: typing.Any) -> typing.Any:
89 if not isinstance(value, list):
90 raise ValueError('The value has to a list')
91 for item in value:
92 if not item in self.type_definitiontype_definition[0]:
93 raise ValueError('{} not in {}'.format(item, self.type_definitiontype_definition))
94
95
97 """Check and update a setting value that is a directory path"""
98
99 def check_type_definition(self, value: typing.Any) -> typing.Any:
100 super().check_type_definition(value)
101 if not os.path.isdir(value):
102 raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), value)
103
104 def __call__(self, value: typing.Any) -> typing.Any:
105 if value == '':
106 value = self.default
107 return super().__call__(value)
108
109
111 """str are base64 decoded"""
112
113 def __call__(self, value: typing.Any) -> typing.Any:
114 if isinstance(value, str):
115 value = b64decode(value)
116 return super().__call__(value)
117
118
119def apply_schema(settings, schema, path_list):
120 error = False
121 for key, value in schema.items():
122 if isinstance(value, SettingsValue):
123 try:
124 settings[key] = value(settings.get(key, _UNDEFINED))
125 except Exception as e: # pylint: disable=broad-except
126 # don't stop now: check other values
127 logger.error('%s: %s', '.'.join([*path_list, key]), e)
128 error = True
129 elif isinstance(value, dict):
130 error = error or apply_schema(settings.setdefault(key, {}), schema[key], [*path_list, key])
131 else:
132 settings.setdefault(key, value)
133 if len(path_list) == 0 and error:
134 raise ValueError('Invalid settings.yml')
135 return error
136
137
138SCHEMA = {
139 'general': {
140 'debug': SettingsValue(bool, False, 'SEARXNG_DEBUG'),
141 'instance_name': SettingsValue(str, 'SearXNG'),
142 'privacypolicy_url': SettingsValue((None, False, str), None),
143 'contact_url': SettingsValue((None, False, str), None),
144 'donation_url': SettingsValue((bool, str), "https://docs.searxng.org/donate.html"),
145 'enable_metrics': SettingsValue(bool, True),
146 'open_metrics': SettingsValue(str, ''),
147 },
148 'brand': {
149 'issue_url': SettingsValue(str, 'https://github.com/searxng/searxng/issues'),
150 'new_issue_url': SettingsValue(str, 'https://github.com/searxng/searxng/issues/new'),
151 'docs_url': SettingsValue(str, 'https://docs.searxng.org'),
152 'public_instances': SettingsValue((False, str), 'https://searx.space'),
153 'wiki_url': SettingsValue(str, 'https://github.com/searxng/searxng/wiki'),
154 'custom': SettingsValue(dict, {'links': {}}),
155 },
156 'search': {
157 'safe_search': SettingsValue((0, 1, 2), 0),
158 'autocomplete': SettingsValue(str, ''),
159 'autocomplete_min': SettingsValue(int, 4),
160 'favicon_resolver': SettingsValue(str, ''),
161 'default_lang': SettingsValue(tuple(SXNG_LOCALE_TAGS + ['']), ''),
162 'languages': SettingSublistValue(SXNG_LOCALE_TAGS, SXNG_LOCALE_TAGS),
163 'ban_time_on_fail': SettingsValue(numbers.Real, 5),
164 'max_ban_time_on_fail': SettingsValue(numbers.Real, 120),
165 'suspended_times': {
166 'SearxEngineAccessDenied': SettingsValue(numbers.Real, 86400),
167 'SearxEngineCaptcha': SettingsValue(numbers.Real, 86400),
168 'SearxEngineTooManyRequests': SettingsValue(numbers.Real, 3600),
169 'cf_SearxEngineCaptcha': SettingsValue(numbers.Real, 1296000),
170 'cf_SearxEngineAccessDenied': SettingsValue(numbers.Real, 86400),
171 'recaptcha_SearxEngineCaptcha': SettingsValue(numbers.Real, 604800),
172 },
173 'formats': SettingsValue(list, OUTPUT_FORMATS),
174 'max_page': SettingsValue(int, 0),
175 },
176 'server': {
177 'port': SettingsValue((int, str), 8888, 'SEARXNG_PORT'),
178 'bind_address': SettingsValue(str, '127.0.0.1', 'SEARXNG_BIND_ADDRESS'),
179 'limiter': SettingsValue(bool, False, 'SEARXNG_LIMITER'),
180 'public_instance': SettingsValue(bool, False, 'SEARXNG_PUBLIC_INSTANCE'),
181 'secret_key': SettingsValue(str, environ_name='SEARXNG_SECRET'),
182 'base_url': SettingsValue((False, str), False, 'SEARXNG_BASE_URL'),
183 'image_proxy': SettingsValue(bool, False, 'SEARXNG_IMAGE_PROXY'),
184 'http_protocol_version': SettingsValue(('1.0', '1.1'), '1.0'),
185 'method': SettingsValue(('POST', 'GET'), 'POST'),
186 'default_http_headers': SettingsValue(dict, {}),
187 },
188 'redis': {
189 'url': SettingsValue((None, False, str), False, 'SEARXNG_REDIS_URL'),
190 },
191 'ui': {
192 'static_path': SettingsDirectoryValue(str, os.path.join(searx_dir, 'static')),
193 'static_use_hash': SettingsValue(bool, False, 'SEARXNG_STATIC_USE_HASH'),
194 'templates_path': SettingsDirectoryValue(str, os.path.join(searx_dir, 'templates')),
195 'default_theme': SettingsValue(str, 'simple'),
196 'default_locale': SettingsValue(str, ''),
197 'theme_args': {
198 'simple_style': SettingsValue(SIMPLE_STYLE, 'auto'),
199 },
200 'center_alignment': SettingsValue(bool, False),
201 'results_on_new_tab': SettingsValue(bool, False),
202 'advanced_search': SettingsValue(bool, False),
203 'query_in_title': SettingsValue(bool, False),
204 'infinite_scroll': SettingsValue(bool, False),
205 'cache_url': SettingsValue(str, 'https://web.archive.org/web/'),
206 'search_on_category_select': SettingsValue(bool, True),
207 'hotkeys': SettingsValue(('default', 'vim'), 'default'),
208 'url_formatting': SettingsValue(('pretty', 'full', 'host'), 'pretty'),
209 },
210 'preferences': {
211 'lock': SettingsValue(list, []),
212 },
213 'outgoing': {
214 'useragent_suffix': SettingsValue(str, ''),
215 'request_timeout': SettingsValue(numbers.Real, 3.0),
216 'enable_http2': SettingsValue(bool, True),
217 'verify': SettingsValue((bool, str), True),
218 'max_request_timeout': SettingsValue((None, numbers.Real), None),
219 'pool_connections': SettingsValue(int, 100),
220 'pool_maxsize': SettingsValue(int, 10),
221 'keepalive_expiry': SettingsValue(numbers.Real, 5.0),
222 # default maximum redirect
223 # from https://github.com/psf/requests/blob/8c211a96cdbe9fe320d63d9e1ae15c5c07e179f8/requests/models.py#L55
224 'max_redirects': SettingsValue(int, 30),
225 'retries': SettingsValue(int, 0),
226 'proxies': SettingsValue((None, str, dict), None),
227 'source_ips': SettingsValue((None, str, list), None),
228 # Tor configuration
229 'using_tor_proxy': SettingsValue(bool, False),
230 'extra_proxy_timeout': SettingsValue(int, 0),
231 'networks': {},
232 },
233 'result_proxy': {
234 'url': SettingsValue((None, str), None),
235 'key': SettingsBytesValue((None, bytes), None),
236 'proxify_results': SettingsValue(bool, False),
237 },
238 'plugins': SettingsValue(list, []),
239 'enabled_plugins': SettingsValue((None, list), None),
240 'checker': {
241 'off_when_debug': SettingsValue(bool, True, None),
242 'scheduling': SettingsValue((None, dict), None, None),
243 },
244 'categories_as_tabs': SettingsValue(dict, CATEGORIES_AS_TABS),
245 'engines': SettingsValue(list, []),
246 'doi_resolvers': {},
247}
248
249
251 apply_schema(settings, SCHEMA, [])
252 return settings
typing.Any check_type_definition(self, typing.Any value)
typing.Any __call__(self, typing.Any value)
typing.Any __call__(self, typing.Any value)
typing.Any check_type_definition(self, typing.Any value)
__init__(self, typing.Union[None, typing.Any, typing.Tuple[typing.Any]] type_definition=None, typing.Any default=None, str environ_name=None)
None check_type_definition(self, typing.Any value)
typing.Any __call__(self, typing.Any value)
apply_schema(settings, schema, path_list)