on master help sign in
Merge pull request #1861 from return42/fix-prefs
6a3ef55Markus Heiser
Makefile+5−2
@@ -72,9 +72,12 @@ $(GH_PAGES)::
72
72
73PHONY += test test.pylint test.pep8 test.unit test.robot
73PHONY += test test.pylint test.pep8 test.unit test.robot
74
74
75+test: test.pylint test.pep8 test.unit test.robot
76+
75# TODO: balance linting with pylint
77# TODO: balance linting with pylint
76−test: test.pep8 test.unit test.robot
78+test.pylint: pyenvinstall
77− - make pylint
79+ $(call cmd,pylint,searx/preferences.py)
80+ $(call cmd,pylint,searx/testing.py)
78
81
79test.pep8: pyenvinstall
82test.pep8: pyenvinstall
80 $(PY_ENV_ACT); ./manage.sh pep8_check
83 $(PY_ENV_ACT); ./manage.sh pep8_check
searx/preferences.py+152−62
@@ -1,3 +1,9 @@
1+# SPDX-License-Identifier: AGPL-3.0-or-later
2+"""Searx preferences implementation.
3+"""
4+
5+# pylint: disable=useless-object-inheritance
6+
1from base64 import urlsafe_b64encode, urlsafe_b64decode
7from base64 import urlsafe_b64encode, urlsafe_b64decode
2from zlib import compress, decompress
8from zlib import compress, decompress
3from sys import version
9from sys import version
@@ -8,6 +14,7 @@ from searx.utils import match_language
8from searx.url_utils import parse_qs, urlencode
14from searx.url_utils import parse_qs, urlencode
9
15
10if version[0] == '3':
16if version[0] == '3':
17+ # pylint: disable=invalid-name
11 unicode = str
18 unicode = str
12
19
13
20
@@ -20,11 +27,14 @@ DOI_RESOLVERS = list(settings['doi_resolvers'])
20
27
21
28
22class MissingArgumentException(Exception):
29class MissingArgumentException(Exception):
23− pass
30+ """Exption from ``cls._post_init`` when a argument is missed.
31+ """
24
32
25
33
26class ValidationException(Exception):
34class ValidationException(Exception):
27− pass
35+
36+ """Exption from ``cls._post_init`` when configuration value is invalid.
37+ """
28
38
29
39
30class Setting(object):
40class Setting(object):
@@ -42,33 +52,45 @@ class Setting(object):
42 pass
52 pass
43
53
44 def parse(self, data):
54 def parse(self, data):
55+ """Parse ``data`` and store the result at ``self.value``
56+
57+ If needed, its overwritten in the inheritance.
58+ """
45 self.value = data
59 self.value = data
46
60
47 def get_value(self):
61 def get_value(self):
62+ """Returns the value of the setting
63+
64+ If needed, its overwritten in the inheritance.
65+ """
48 return self.value
66 return self.value
49
67
50 def save(self, name, resp):
68 def save(self, name, resp):
69+ """Save cookie ``name`` in the HTTP reponse obect
70+
71+ If needed, its overwritten in the inheritance."""
51 resp.set_cookie(name, self.value, max_age=COOKIE_MAX_AGE)
72 resp.set_cookie(name, self.value, max_age=COOKIE_MAX_AGE)
52
73
53
74
54class StringSetting(Setting):
75class StringSetting(Setting):
55 """Setting of plain string values"""
76 """Setting of plain string values"""
56− pass
57
77
58
78
59class EnumStringSetting(Setting):
79class EnumStringSetting(Setting):
60 """Setting of a value which can only come from the given choices"""
80 """Setting of a value which can only come from the given choices"""
61
81
62− def _validate_selection(self, selection):
63− if selection not in self.choices:
64− raise ValidationException('Invalid value: "{0}"'.format(selection))
65−
66 def _post_init(self):
82 def _post_init(self):
67 if not hasattr(self, 'choices'):
83 if not hasattr(self, 'choices'):
68 raise MissingArgumentException('Missing argument: choices')
84 raise MissingArgumentException('Missing argument: choices')
69 self._validate_selection(self.value)
85 self._validate_selection(self.value)
70
86
87+ def _validate_selection(self, selection):
88+ if selection not in self.choices: # pylint: disable=no-member
89+ raise ValidationException('Invalid value: "{0}"'.format(selection))
90+
71 def parse(self, data):
91 def parse(self, data):
92+ """Parse and validate ``data`` and store the result at ``self.value``
93+ """
72 self._validate_selection(data)
94 self._validate_selection(data)
73 self.value = data
95 self.value = data
74
96
@@ -78,7 +100,7 @@ class MultipleChoiceSetting(EnumStringSetting):
78
100
79 def _validate_selections(self, selections):
101 def _validate_selections(self, selections):
80 for item in selections:
102 for item in selections:
81− if item not in self.choices:
103+ if item not in self.choices: # pylint: disable=no-member
82 raise ValidationException('Invalid value: "{0}"'.format(selections))
104 raise ValidationException('Invalid value: "{0}"'.format(selections))
83
105
84 def _post_init(self):
106 def _post_init(self):
@@ -87,6 +109,8 @@ class MultipleChoiceSetting(EnumStringSetting):
87 self._validate_selections(self.value)
109 self._validate_selections(self.value)
88
110
89 def parse(self, data):
111 def parse(self, data):
112+ """Parse and validate ``data`` and store the result at ``self.value``
113+ """
90 if data == '':
114 if data == '':
91 self.value = []
115 self.value = []
92 return
116 return
@@ -95,38 +119,47 @@ class MultipleChoiceSetting(EnumStringSetting):
95 self._validate_selections(elements)
119 self._validate_selections(elements)
96 self.value = elements
120 self.value = elements
97
121
98− def parse_form(self, data):
122+ def parse_form(self, data): # pylint: disable=missing-function-docstring
99 self.value = []
123 self.value = []
100 for choice in data:
124 for choice in data:
101− if choice in self.choices and choice not in self.value:
125+ if choice in self.choices and choice not in self.value: # pylint: disable=no-member
102 self.value.append(choice)
126 self.value.append(choice)
103
127
104 def save(self, name, resp):
128 def save(self, name, resp):
129+ """Save cookie ``name`` in the HTTP reponse obect
130+ """
105 resp.set_cookie(name, ','.join(self.value), max_age=COOKIE_MAX_AGE)
131 resp.set_cookie(name, ','.join(self.value), max_age=COOKIE_MAX_AGE)
106
132
107
133
108class SetSetting(Setting):
134class SetSetting(Setting):
135+ """Setting of values of type ``set`` (comma separated string) """
109 def _post_init(self):
136 def _post_init(self):
110 if not hasattr(self, 'values'):
137 if not hasattr(self, 'values'):
111 self.values = set()
138 self.values = set()
112
139
113 def get_value(self):
140 def get_value(self):
141+ """Returns a string with comma separated values.
142+ """
114 return ','.join(self.values)
143 return ','.join(self.values)
115
144
116 def parse(self, data):
145 def parse(self, data):
146+ """Parse and validate ``data`` and store the result at ``self.value``
147+ """
117 if data == '':
148 if data == '':
118− self.values = set()
149+ self.values = set() # pylint: disable=attribute-defined-outside-init
119 return
150 return
120
151
121 elements = data.split(',')
152 elements = data.split(',')
122 for element in elements:
153 for element in elements:
123 self.values.add(element)
154 self.values.add(element)
124
155
125− def parse_form(self, data):
156+ def parse_form(self, data): # pylint: disable=missing-function-docstring
126 elements = data.split(',')
157 elements = data.split(',')
127− self.values = set(elements)
158+ self.values = set(elements) # pylint: disable=attribute-defined-outside-init
128
159
129 def save(self, name, resp):
160 def save(self, name, resp):
161+ """Save cookie ``name`` in the HTTP reponse obect
162+ """
130 resp.set_cookie(name, ','.join(self.values), max_age=COOKIE_MAX_AGE)
163 resp.set_cookie(name, ','.join(self.values), max_age=COOKIE_MAX_AGE)
131
164
132
165
@@ -134,14 +167,19 @@ class SearchLanguageSetting(EnumStringSetting):
134 """Available choices may change, so user's value may not be in choices anymore"""
167 """Available choices may change, so user's value may not be in choices anymore"""
135
168
136 def _validate_selection(self, selection):
169 def _validate_selection(self, selection):
170+ if selection != "" and not match_language(
171+ # pylint: disable=no-member
137− if not match_language(selection, self.choices, fallback=None) and selection != "":
172+ selection, self.choices, fallback=None):
138 raise ValidationException('Invalid language code: "{0}"'.format(selection))
173 raise ValidationException('Invalid language code: "{0}"'.format(selection))
139
174
140 def parse(self, data):
175 def parse(self, data):
176+ """Parse and validate ``data`` and store the result at ``self.value``
177+ """
141− if data not in self.choices and data != self.value:
178+ if data not in self.choices and data != self.value: # pylint: disable=no-member
142 # hack to give some backwards compatibility with old language cookies
179 # hack to give some backwards compatibility with old language cookies
143 data = str(data).replace('_', '-')
180 data = str(data).replace('_', '-')
144 lang = data.split('-')[0]
181 lang = data.split('-')[0]
182+ # pylint: disable=no-member
145 if data in self.choices:
183 if data in self.choices:
146 pass
184 pass
147 elif lang in self.choices:
185 elif lang in self.choices:
@@ -157,16 +195,21 @@ class MapSetting(Setting):
157 def _post_init(self):
195 def _post_init(self):
158 if not hasattr(self, 'map'):
196 if not hasattr(self, 'map'):
159 raise MissingArgumentException('missing argument: map')
197 raise MissingArgumentException('missing argument: map')
160− if self.value not in self.map.values():
198+ if self.value not in self.map.values(): # pylint: disable=no-member
161 raise ValidationException('Invalid default value')
199 raise ValidationException('Invalid default value')
162
200
163 def parse(self, data):
201 def parse(self, data):
202+ """Parse and validate ``data`` and store the result at ``self.value``
203+ """
204+ # pylint: disable=no-member
164 if data not in self.map:
205 if data not in self.map:
165 raise ValidationException('Invalid choice: {0}'.format(data))
206 raise ValidationException('Invalid choice: {0}'.format(data))
166 self.value = self.map[data]
207 self.value = self.map[data]
167− self.key = data
208+ self.key = data # pylint: disable=attribute-defined-outside-init
168
209
169 def save(self, name, resp):
210 def save(self, name, resp):
211+ """Save cookie ``name`` in the HTTP reponse obect
212+ """
170 if hasattr(self, 'key'):
213 if hasattr(self, 'key'):
171 resp.set_cookie(name, self.key, max_age=COOKIE_MAX_AGE)
214 resp.set_cookie(name, self.key, max_age=COOKIE_MAX_AGE)
172
215
@@ -180,24 +223,26 @@ class SwitchableSetting(Setting):
180 if not hasattr(self, 'choices'):
223 if not hasattr(self, 'choices'):
181 raise MissingArgumentException('missing argument: choices')
224 raise MissingArgumentException('missing argument: choices')
182
225
183− def transform_form_items(self, items):
226+ def transform_form_items(self, items): # pylint: disable=missing-function-docstring
227+ # pylint: disable=no-self-use
184 return items
228 return items
185
229
186− def transform_values(self, values):
230+ def transform_values(self, values): # pylint: disable=missing-function-docstring
231+ # pylint: disable=no-self-use
187 return values
232 return values
188
233
189− def parse_cookie(self, data):
234+ def parse_cookie(self, data): # pylint: disable=missing-function-docstring
235+ # pylint: disable=attribute-defined-outside-init
190 if data[DISABLED] != '':
236 if data[DISABLED] != '':
191 self.disabled = set(data[DISABLED].split(','))
237 self.disabled = set(data[DISABLED].split(','))
192 if data[ENABLED] != '':
238 if data[ENABLED] != '':
193 self.enabled = set(data[ENABLED].split(','))
239 self.enabled = set(data[ENABLED].split(','))
194
240
195− def parse_form(self, items):
241+ def parse_form(self, items): # pylint: disable=missing-function-docstring
196 items = self.transform_form_items(items)
242 items = self.transform_form_items(items)
197−
198− self.disabled = set()
243+ self.disabled = set() # pylint: disable=attribute-defined-outside-init
199− self.enabled = set()
244+ self.enabled = set() # pylint: disable=attribute-defined-outside-init
200− for choice in self.choices:
245+ for choice in self.choices: # pylint: disable=no-member
201 if choice['default_on']:
246 if choice['default_on']:
202 if choice['id'] in items:
247 if choice['id'] in items:
203 self.disabled.add(choice['id'])
248 self.disabled.add(choice['id'])
@@ -205,31 +250,34 @@ class SwitchableSetting(Setting):
205 if choice['id'] not in items:
250 if choice['id'] not in items:
206 self.enabled.add(choice['id'])
251 self.enabled.add(choice['id'])
207
252
208− def save(self, resp):
253+ def save(self, resp): # pylint: disable=arguments-differ
254+ """Save cookie in the HTTP reponse obect
255+ """
209 resp.set_cookie('disabled_{0}'.format(self.value), ','.join(self.disabled), max_age=COOKIE_MAX_AGE)
256 resp.set_cookie('disabled_{0}'.format(self.value), ','.join(self.disabled), max_age=COOKIE_MAX_AGE)
210 resp.set_cookie('enabled_{0}'.format(self.value), ','.join(self.enabled), max_age=COOKIE_MAX_AGE)
257 resp.set_cookie('enabled_{0}'.format(self.value), ','.join(self.enabled), max_age=COOKIE_MAX_AGE)
211
258
212− def get_disabled(self):
259+ def get_disabled(self): # pylint: disable=missing-function-docstring
213 disabled = self.disabled
260 disabled = self.disabled
214− for choice in self.choices:
261+ for choice in self.choices: # pylint: disable=no-member
215 if not choice['default_on'] and choice['id'] not in self.enabled:
262 if not choice['default_on'] and choice['id'] not in self.enabled:
216 disabled.add(choice['id'])
263 disabled.add(choice['id'])
217 return self.transform_values(disabled)
264 return self.transform_values(disabled)
218
265
219− def get_enabled(self):
266+ def get_enabled(self): # pylint: disable=missing-function-docstring
220 enabled = self.enabled
267 enabled = self.enabled
221− for choice in self.choices:
268+ for choice in self.choices: # pylint: disable=no-member
222 if choice['default_on'] and choice['id'] not in self.disabled:
269 if choice['default_on'] and choice['id'] not in self.disabled:
223 enabled.add(choice['id'])
270 enabled.add(choice['id'])
224 return self.transform_values(enabled)
271 return self.transform_values(enabled)
225
272
226
273
227class EnginesSetting(SwitchableSetting):
274class EnginesSetting(SwitchableSetting):
275+ """Engine settings"""
228
276
229 def _post_init(self):
277 def _post_init(self):
230 super(EnginesSetting, self)._post_init()
278 super(EnginesSetting, self)._post_init()
231 transformed_choices = []
279 transformed_choices = []
232− for engine_name, engine in self.choices.items():
280+ for engine_name, engine in self.choices.items(): # pylint: disable=no-member,access-member-before-definition
233 for category in engine.categories:
281 for category in engine.categories:
234 transformed_choice = dict()
282 transformed_choice = dict()
235 transformed_choice['default_on'] = not engine.disabled
283 transformed_choice['default_on'] = not engine.disabled
@@ -251,11 +299,12 @@ class EnginesSetting(SwitchableSetting):
251
299
252
300
253class PluginsSetting(SwitchableSetting):
301class PluginsSetting(SwitchableSetting):
302+ """Plugin settings"""
254
303
255 def _post_init(self):
304 def _post_init(self):
256 super(PluginsSetting, self)._post_init()
305 super(PluginsSetting, self)._post_init()
257 transformed_choices = []
306 transformed_choices = []
258− for plugin in self.choices:
307+ for plugin in self.choices: # pylint: disable=access-member-before-definition
259 transformed_choice = dict()
308 transformed_choice = dict()
260 transformed_choice['default_on'] = plugin.default_on
309 transformed_choice['default_on'] = plugin.default_on
261 transformed_choice['id'] = plugin.id
310 transformed_choice['id'] = plugin.id
@@ -272,33 +321,64 @@ class Preferences(object):
272 def __init__(self, themes, categories, engines, plugins):
321 def __init__(self, themes, categories, engines, plugins):
273 super(Preferences, self).__init__()
322 super(Preferences, self).__init__()
274
323
324+ self.key_value_settings = {
275− self.key_value_settings = {'categories': MultipleChoiceSetting(['general'], choices=categories + ['none']),
325+ 'categories': MultipleChoiceSetting(
326+ ['general'], choices=categories + ['none']
327+ ),
276− 'language': SearchLanguageSetting(settings['search']['default_lang'],
328+ 'language': SearchLanguageSetting(
329+ settings['search'].get('default_lang', ''),
277− choices=list(LANGUAGE_CODES) + ['']),
330+ choices=list(LANGUAGE_CODES) + ['']
331+ ),
278− 'locale': EnumStringSetting(settings['ui']['default_locale'],
332+ 'locale': EnumStringSetting(
333+ settings['ui'].get('default_locale', ''),
279− choices=list(settings['locales'].keys()) + ['']),
334+ choices=list(settings['locales'].keys()) + ['']
335+ ),
280− 'autocomplete': EnumStringSetting(settings['search']['autocomplete'],
336+ 'autocomplete': EnumStringSetting(
337+ settings['search'].get('autocomplete', ''),
281− choices=list(autocomplete.backends.keys()) + ['']),
338+ choices=list(autocomplete.backends.keys()) + ['']
339+ ),
340+ 'image_proxy': MapSetting(
282− 'image_proxy': MapSetting(settings['server']['image_proxy'],
341+ settings['server'].get('image_proxy', False),
342+ map={
283− map={'': settings['server']['image_proxy'],
343+ '': settings['server'].get('image_proxy', 0),
284− '0': False,
344+ '0': False,
285− '1': True,
345+ '1': True,
286− 'True': True,
346+ 'True': True,
287− 'False': False}),
347+ 'False': False
348+ }
349+ ),
288− 'method': EnumStringSetting('POST', choices=('GET', 'POST')),
350+ 'method': EnumStringSetting(
351+ 'POST',
352+ choices=('GET', 'POST')
353+ ),
354+ 'safesearch': MapSetting(
289− 'safesearch': MapSetting(settings['search']['safe_search'], map={'0': 0,
355+ settings['search'].get('safe_search', 0),
356+ map={
357+ '0': 0,
290− '1': 1,
358+ '1': 1,
291− '2': 2}),
359+ '2': 2
360+ }
361+ ),
292− 'theme': EnumStringSetting(settings['ui']['default_theme'], choices=themes),
362+ 'theme': EnumStringSetting(
363+ settings['ui'].get('default_theme', 'oscar'),
364+ choices=themes
365+ ),
293− 'results_on_new_tab': MapSetting(False, map={'0': False,
366+ 'results_on_new_tab': MapSetting(
367+ False,
368+ map={
369+ '0': False,
294− '1': True,
370+ '1': True,
295− 'False': False,
371+ 'False': False,
296− 'True': True}),
372+ 'True': True
373+ }
374+ ),
297− 'doi_resolver': MultipleChoiceSetting(['oadoi.org'], choices=DOI_RESOLVERS),
375+ 'doi_resolver': MultipleChoiceSetting(
376+ ['oadoi.org'], choices=DOI_RESOLVERS
377+ ),
298− 'oscar-style': EnumStringSetting(
378+ 'oscar-style': EnumStringSetting(
299− settings['ui'].get('theme_args', {}).get('oscar_style', 'logicodev'),
379+ settings['ui'].get('theme_args', {}).get('oscar_style', 'logicodev'),
300− choices=['', 'logicodev', 'logicodev-dark', 'pointhi']),
380+ choices=['', 'logicodev', 'logicodev-dark', 'pointhi']),
301− }
381+ }
302
382
303 self.engines = EnginesSetting('engines', choices=engines)
383 self.engines = EnginesSetting('engines', choices=engines)
304 self.plugins = PluginsSetting('plugins', choices=plugins)
384 self.plugins = PluginsSetting('plugins', choices=plugins)
@@ -306,6 +386,7 @@ class Preferences(object):
306 self.unknown_params = {}
386 self.unknown_params = {}
307
387
308 def get_as_url_params(self):
388 def get_as_url_params(self):
389+ """Return preferences as URL parameters"""
309 settings_kv = {}
390 settings_kv = {}
310 for k, v in self.key_value_settings.items():
391 for k, v in self.key_value_settings.items():
311 if isinstance(v, MultipleChoiceSetting):
392 if isinstance(v, MultipleChoiceSetting):
@@ -324,6 +405,7 @@ class Preferences(object):
324 return urlsafe_b64encode(compress(urlencode(settings_kv).encode('utf-8'))).decode('utf-8')
405 return urlsafe_b64encode(compress(urlencode(settings_kv).encode('utf-8'))).decode('utf-8')
325
406
326 def parse_encoded_data(self, input_data):
407 def parse_encoded_data(self, input_data):
408+ """parse (base64) preferences from request (``flask.request.form['preferences']``)"""
327 decoded_data = decompress(urlsafe_b64decode(input_data.encode('utf-8')))
409 decoded_data = decompress(urlsafe_b64decode(input_data.encode('utf-8')))
328 dict_data = {}
410 dict_data = {}
329 for x, y in parse_qs(decoded_data).items():
411 for x, y in parse_qs(decoded_data).items():
@@ -331,6 +413,7 @@ class Preferences(object):
331 self.parse_dict(dict_data)
413 self.parse_dict(dict_data)
332
414
333 def parse_dict(self, input_data):
415 def parse_dict(self, input_data):
416+ """parse preferences from request (``flask.request.form``)"""
334 for user_setting_name, user_setting in input_data.items():
417 for user_setting_name, user_setting in input_data.items():
335 if user_setting_name in self.key_value_settings:
418 if user_setting_name in self.key_value_settings:
336 self.key_value_settings[user_setting_name].parse(user_setting)
419 self.key_value_settings[user_setting_name].parse(user_setting)
@@ -351,6 +434,7 @@ class Preferences(object):
351 self.unknown_params[user_setting_name] = user_setting
434 self.unknown_params[user_setting_name] = user_setting
352
435
353 def parse_form(self, input_data):
436 def parse_form(self, input_data):
437+ """Parse formular (``<input>``) data from a ``flask.request.form``"""
354 disabled_engines = []
438 disabled_engines = []
355 enabled_categories = []
439 enabled_categories = []
356 disabled_plugins = []
440 disabled_plugins = []
@@ -373,12 +457,18 @@ class Preferences(object):
373
457
374 # cannot be used in case of engines or plugins
458 # cannot be used in case of engines or plugins
375 def get_value(self, user_setting_name):
459 def get_value(self, user_setting_name):
460+ """Returns the value for ``user_setting_name``
461+ """
462+ ret_val = None
376 if user_setting_name in self.key_value_settings:
463 if user_setting_name in self.key_value_settings:
377− return self.key_value_settings[user_setting_name].get_value()
464+ ret_val = self.key_value_settings[user_setting_name].get_value()
378 if user_setting_name in self.unknown_params:
465 if user_setting_name in self.unknown_params:
379− return self.unknown_params[user_setting_name]
466+ ret_val = self.unknown_params[user_setting_name]
467+ return ret_val
380
468
381 def save(self, resp):
469 def save(self, resp):
470+ """Save cookie in the HTTP reponse obect
471+ """
382 for user_setting_name, user_setting in self.key_value_settings.items():
472 for user_setting_name, user_setting in self.key_value_settings.items():
383 user_setting.save(user_setting_name, resp)
473 user_setting.save(user_setting_name, resp)
384 self.engines.save(resp)
474 self.engines.save(resp)
@@ -388,7 +478,7 @@ class Preferences(object):
388 resp.set_cookie(k, v, max_age=COOKIE_MAX_AGE)
478 resp.set_cookie(k, v, max_age=COOKIE_MAX_AGE)
389 return resp
479 return resp
390
480
391− def validate_token(self, engine):
481+ def validate_token(self, engine): # pylint: disable=missing-function-docstring
392 valid = True
482 valid = True
393 if hasattr(engine, 'tokens') and engine.tokens:
483 if hasattr(engine, 'tokens') and engine.tokens:
394 valid = False
484 valid = False
searx/settings_robot.yml−4
@@ -3,8 +3,6 @@ general:
3 instance_name : "searx_test"
3 instance_name : "searx_test"
4
4
5search:
5search:
6− safe_search : 0
7− autocomplete : ""
8 language: "all"
6 language: "all"
9
7
10server:
8server:
@@ -12,14 +10,12 @@ server:
12 bind_address : 127.0.0.1
10 bind_address : 127.0.0.1
13 secret_key : "ultrasecretkey" # change this!
11 secret_key : "ultrasecretkey" # change this!
14 base_url : False
12 base_url : False
15− image_proxy : False
16 http_protocol_version : "1.0"
13 http_protocol_version : "1.0"
17
14
18ui:
15ui:
19 static_path : ""
16 static_path : ""
20 templates_path : ""
17 templates_path : ""
21 default_theme : oscar
18 default_theme : oscar
22− default_locale : ""
23
19
24outgoing:
20outgoing:
25 request_timeout : 1.0 # seconds
21 request_timeout : 1.0 # seconds
searx/testing.py+18−11
@@ -1,12 +1,14 @@
1# -*- coding: utf-8 -*-
1# -*- coding: utf-8 -*-
2+# SPDX-License-Identifier: AGPL-3.0-or-later
2"""Shared testing code."""
3"""Shared testing code."""
3
4
5+# pylint: disable=missing-function-docstring
4
6
5import os
7import os
6import subprocess
8import subprocess
7import traceback
9import traceback
8
10
9−from os.path import dirname, join, abspath
11+from os.path import dirname, join, abspath, realpath
10
12
11from splinter import Browser
13from splinter import Browser
12from unittest2 import TestCase
14from unittest2 import TestCase
@@ -17,21 +19,21 @@ class SearxTestLayer:
17
19
18 __name__ = u'SearxTestLayer'
20 __name__ = u'SearxTestLayer'
19
21
22+ @classmethod
20 def setUp(cls):
23 def setUp(cls):
21 pass
24 pass
22− setUp = classmethod(setUp)
23
25
26+ @classmethod
24 def tearDown(cls):
27 def tearDown(cls):
25 pass
28 pass
26− tearDown = classmethod(tearDown)
27
29
30+ @classmethod
28 def testSetUp(cls):
31 def testSetUp(cls):
29 pass
32 pass
30− testSetUp = classmethod(testSetUp)
31
33
34+ @classmethod
32 def testTearDown(cls):
35 def testTearDown(cls):
33 pass
36 pass
34− testTearDown = classmethod(testTearDown)
35
37
36
38
37class SearxRobotLayer():
39class SearxRobotLayer():
@@ -41,14 +43,19 @@ class SearxRobotLayer():
41 os.setpgrp() # create new process group, become its leader
43 os.setpgrp() # create new process group, become its leader
42
44
43 # get program paths
45 # get program paths
44− webapp = os.path.join(
45− os.path.abspath(os.path.dirname(os.path.realpath(__file__))),
46+ webapp = join(abspath(dirname(realpath(__file__))), 'webapp.py')
46− 'webapp.py'
47− )
48 exe = 'python'
47 exe = 'python'
49
48
49+ # The Flask app is started by Flask.run(...), don't enable Flask's debug
50+ # mode, the debugger from Flask will cause wired process model, where
51+ # the server never dies. Further read:
52+ #
53+ # - debug mode: https://flask.palletsprojects.com/quickstart/#debug-mode
54+ # - Flask.run(..): https://flask.palletsprojects.com/api/#flask.Flask.run
55+
56+ os.environ['SEARX_DEBUG'] = '0'
57+
50 # set robot settings path
58 # set robot settings path
51− os.environ['SEARX_DEBUG'] = '1'
52 os.environ['SEARX_SETTINGS_PATH'] = abspath(
59 os.environ['SEARX_SETTINGS_PATH'] = abspath(
53 dirname(__file__) + '/settings_robot.yml')
60 dirname(__file__) + '/settings_robot.yml')
54
61
@@ -105,7 +112,7 @@ if __name__ == '__main__':
105 try:
112 try:
106 test_layer.setUp()
113 test_layer.setUp()
107 run_robot_tests([getattr(robot, x) for x in dir(robot) if x.startswith('test_')])
114 run_robot_tests([getattr(robot, x) for x in dir(robot) if x.startswith('test_')])
108− except Exception:
115+ except Exception: # pylint: disable=broad-except
109 errors = True
116 errors = True
110 print('Error occured: {0}'.format(traceback.format_exc()))
117 print('Error occured: {0}'.format(traceback.format_exc()))
111 test_layer.tearDown()
118 test_layer.tearDown()