Merge pull request #708 from not-my-profile/pref-refactor

Refactor `preferences`
This commit is contained in:
Martin Fischer 2022-01-07 09:45:23 +01:00 committed by GitHub
commit e12525a1fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 137 additions and 166 deletions

View File

@ -13,7 +13,7 @@ usage::
import sys import sys
import copy import copy
from typing import List from typing import Dict, List, Optional
from os.path import realpath, dirname from os.path import realpath, dirname
from babel.localedata import locale_identifiers from babel.localedata import locale_identifiers
@ -67,10 +67,10 @@ class Engine: # pylint: disable=too-few-public-methods
timeout: float timeout: float
# Defaults for the namespace of an engine module, see :py:func:`load_engine`` # Defaults for the namespace of an engine module, see :py:func:`load_engine`
categories = {'general': []} categories = {'general': []}
engines = {} engines: Dict[str, Engine] = {}
engine_shortcuts = {} engine_shortcuts = {}
"""Simple map of registered *shortcuts* to name of the engine (or ``None``). """Simple map of registered *shortcuts* to name of the engine (or ``None``).
@ -81,7 +81,7 @@ engine_shortcuts = {}
""" """
def load_engine(engine_data): def load_engine(engine_data: dict) -> Optional[Engine]:
"""Load engine from ``engine_data``. """Load engine from ``engine_data``.
:param dict engine_data: Attributes from YAML ``settings:engines/<engine>`` :param dict engine_data: Attributes from YAML ``settings:engines/<engine>``
@ -157,7 +157,7 @@ def set_loggers(engine, engine_name):
module.logger = logger.getChild(module_engine_name) module.logger = logger.getChild(module_engine_name)
def update_engine_attributes(engine, engine_data): def update_engine_attributes(engine: Engine, engine_data):
# set engine attributes from engine_data # set engine attributes from engine_data
for param_name, param_value in engine_data.items(): for param_name, param_value in engine_data.items():
if param_name == 'categories': if param_name == 'categories':
@ -175,7 +175,7 @@ def update_engine_attributes(engine, engine_data):
setattr(engine, arg_name, copy.deepcopy(arg_value)) setattr(engine, arg_name, copy.deepcopy(arg_value))
def set_language_attributes(engine): def set_language_attributes(engine: Engine):
# assign supported languages from json file # assign supported languages from json file
if engine.name in ENGINES_LANGUAGES: if engine.name in ENGINES_LANGUAGES:
engine.supported_languages = ENGINES_LANGUAGES[engine.name] engine.supported_languages = ENGINES_LANGUAGES[engine.name]
@ -248,7 +248,7 @@ def is_missing_required_attributes(engine):
return missing return missing
def is_engine_active(engine): def is_engine_active(engine: Engine):
# check if engine is inactive # check if engine is inactive
if engine.inactive is True: if engine.inactive is True:
return False return False
@ -260,7 +260,7 @@ def is_engine_active(engine):
return True return True
def register_engine(engine): def register_engine(engine: Engine):
if engine.name in engines: if engine.name in engines:
logger.error('Engine config error: ambigious name: {0}'.format(engine.name)) logger.error('Engine config error: ambigious name: {0}'.format(engine.name))
sys.exit(1) sys.exit(1)

View File

@ -10,10 +10,20 @@ from os.path import abspath, basename, dirname, exists, join
from shutil import copyfile from shutil import copyfile
from pkgutil import iter_modules from pkgutil import iter_modules
from logging import getLogger from logging import getLogger
from typing import List
from searx import logger, settings from searx import logger, settings
class Plugin: # pylint: disable=too-few-public-methods
"""This class is currently never initialized and only used for type hinting."""
id: str
name: str
description: str
default_on: bool
logger = logger.getChild("plugins") logger = logger.getChild("plugins")
required_attrs = ( required_attrs = (
@ -175,7 +185,7 @@ def load_and_initialize_plugin(plugin_module_name, external, init_args):
class PluginStore: class PluginStore:
def __init__(self): def __init__(self):
self.plugins = [] self.plugins: List[Plugin] = []
def __iter__(self): def __iter__(self):
for plugin in self.plugins: for plugin in self.plugins:

View File

@ -8,44 +8,36 @@
from base64 import urlsafe_b64encode, urlsafe_b64decode from base64 import urlsafe_b64encode, urlsafe_b64decode
from zlib import compress, decompress from zlib import compress, decompress
from urllib.parse import parse_qs, urlencode from urllib.parse import parse_qs, urlencode
from typing import Iterable, Dict, List
import flask
from searx import settings, autocomplete from searx import settings, autocomplete
from searx.engines import Engine
from searx.plugins import Plugin
from searx.locales import LOCALE_NAMES from searx.locales import LOCALE_NAMES
from searx.webutils import VALID_LANGUAGE_CODE from searx.webutils import VALID_LANGUAGE_CODE
from searx.engines import OTHER_CATEGORY from searx.engines import OTHER_CATEGORY
COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 5 # 5 years COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 5 # 5 years
DISABLED = 0
ENABLED = 1
DOI_RESOLVERS = list(settings['doi_resolvers']) DOI_RESOLVERS = list(settings['doi_resolvers'])
class MissingArgumentException(Exception):
"""Exption from ``cls._post_init`` when a argument is missed."""
class ValidationException(Exception): class ValidationException(Exception):
"""Exption from ``cls._post_init`` when configuration value is invalid.""" """Exption from ``cls.__init__`` when configuration value is invalid."""
class Setting: class Setting:
"""Base class of user settings""" """Base class of user settings"""
def __init__(self, default_value, locked=False, **kwargs): def __init__(self, default_value, locked: bool = False):
super().__init__() super().__init__()
self.value = default_value self.value = default_value
self.locked = locked self.locked = locked
for key, value in kwargs.items():
setattr(self, key, value)
self._post_init() def parse(self, data: str):
def _post_init(self):
pass
def parse(self, data):
"""Parse ``data`` and store the result at ``self.value`` """Parse ``data`` and store the result at ``self.value``
If needed, its overwritten in the inheritance. If needed, its overwritten in the inheritance.
@ -59,7 +51,7 @@ class Setting:
""" """
return self.value return self.value
def save(self, name, resp): def save(self, name: str, resp: flask.Response):
"""Save cookie ``name`` in the HTTP reponse obect """Save cookie ``name`` in the HTTP reponse obect
If needed, its overwritten in the inheritance.""" If needed, its overwritten in the inheritance."""
@ -73,35 +65,35 @@ class StringSetting(Setting):
class EnumStringSetting(Setting): class EnumStringSetting(Setting):
"""Setting of a value which can only come from the given choices""" """Setting of a value which can only come from the given choices"""
def _post_init(self): def __init__(self, default_value: str, choices: Iterable[str], locked=False):
if not hasattr(self, 'choices'): super().__init__(default_value, locked)
raise MissingArgumentException('Missing argument: choices') self.choices = choices
self._validate_selection(self.value) self._validate_selection(self.value)
def _validate_selection(self, selection): def _validate_selection(self, selection: str):
if selection not in self.choices: # pylint: disable=no-member if selection not in self.choices:
raise ValidationException('Invalid value: "{0}"'.format(selection)) raise ValidationException('Invalid value: "{0}"'.format(selection))
def parse(self, data): def parse(self, data: str):
"""Parse and validate ``data`` and store the result at ``self.value``""" """Parse and validate ``data`` and store the result at ``self.value``"""
self._validate_selection(data) self._validate_selection(data)
self.value = data self.value = data
class MultipleChoiceSetting(EnumStringSetting): class MultipleChoiceSetting(Setting):
"""Setting of values which can only come from the given choices""" """Setting of values which can only come from the given choices"""
def _validate_selections(self, selections): def __init__(self, default_value: List[str], choices: Iterable[str], locked=False):
for item in selections: super().__init__(default_value, locked)
if item not in self.choices: # pylint: disable=no-member self.choices = choices
raise ValidationException('Invalid value: "{0}"'.format(selections))
def _post_init(self):
if not hasattr(self, 'choices'):
raise MissingArgumentException('Missing argument: choices')
self._validate_selections(self.value) self._validate_selections(self.value)
def parse(self, data): def _validate_selections(self, selections: List[str]):
for item in selections:
if item not in self.choices:
raise ValidationException('Invalid value: "{0}"'.format(selections))
def parse(self, data: str):
"""Parse and validate ``data`` and store the result at ``self.value``""" """Parse and validate ``data`` and store the result at ``self.value``"""
if data == '': if data == '':
self.value = [] self.value = []
@ -111,16 +103,16 @@ class MultipleChoiceSetting(EnumStringSetting):
self._validate_selections(elements) self._validate_selections(elements)
self.value = elements self.value = elements
def parse_form(self, data): def parse_form(self, data: List[str]):
if self.locked: if self.locked:
return return
self.value = [] self.value = []
for choice in data: for choice in data:
if choice in self.choices and choice not in self.value: # pylint: disable=no-member if choice in self.choices and choice not in self.value:
self.value.append(choice) self.value.append(choice)
def save(self, name, resp): def save(self, name: str, resp: flask.Response):
"""Save cookie ``name`` in the HTTP reponse obect""" """Save cookie ``name`` in the HTTP reponse obect"""
resp.set_cookie(name, ','.join(self.value), max_age=COOKIE_MAX_AGE) resp.set_cookie(name, ','.join(self.value), max_age=COOKIE_MAX_AGE)
@ -128,32 +120,32 @@ class MultipleChoiceSetting(EnumStringSetting):
class SetSetting(Setting): class SetSetting(Setting):
"""Setting of values of type ``set`` (comma separated string)""" """Setting of values of type ``set`` (comma separated string)"""
def _post_init(self): def __init__(self, *args, **kwargs):
if not hasattr(self, 'values'): super().__init__(*args, **kwargs)
self.values = set() self.values = set()
def get_value(self): def get_value(self):
"""Returns a string with comma separated values.""" """Returns a string with comma separated values."""
return ','.join(self.values) return ','.join(self.values)
def parse(self, data): def parse(self, data: str):
"""Parse and validate ``data`` and store the result at ``self.value``""" """Parse and validate ``data`` and store the result at ``self.value``"""
if data == '': if data == '':
self.values = set() # pylint: disable=attribute-defined-outside-init self.values = set()
return return
elements = data.split(',') elements = data.split(',')
for element in elements: for element in elements:
self.values.add(element) self.values.add(element)
def parse_form(self, data): def parse_form(self, data: str):
if self.locked: if self.locked:
return return
elements = data.split(',') elements = data.split(',')
self.values = set(elements) # pylint: disable=attribute-defined-outside-init self.values = set(elements)
def save(self, name, resp): def save(self, name: str, resp: flask.Response):
"""Save cookie ``name`` in the HTTP reponse obect""" """Save cookie ``name`` in the HTTP reponse obect"""
resp.set_cookie(name, ','.join(self.values), max_age=COOKIE_MAX_AGE) resp.set_cookie(name, ','.join(self.values), max_age=COOKIE_MAX_AGE)
@ -165,13 +157,13 @@ class SearchLanguageSetting(EnumStringSetting):
if selection != '' and not VALID_LANGUAGE_CODE.match(selection): if selection != '' and not VALID_LANGUAGE_CODE.match(selection):
raise ValidationException('Invalid language code: "{0}"'.format(selection)) raise ValidationException('Invalid language code: "{0}"'.format(selection))
def parse(self, data): def parse(self, data: str):
"""Parse and validate ``data`` and store the result at ``self.value``""" """Parse and validate ``data`` and store the result at ``self.value``"""
if data not in self.choices and data != self.value: # pylint: disable=no-member if data not in self.choices and data != self.value:
# hack to give some backwards compatibility with old language cookies # hack to give some backwards compatibility with old language cookies
data = str(data).replace('_', '-') data = str(data).replace('_', '-')
lang = data.split('-', maxsplit=1)[0] lang = data.split('-', maxsplit=1)[0]
# pylint: disable=no-member
if data in self.choices: if data in self.choices:
pass pass
elif lang in self.choices: elif lang in self.choices:
@ -185,34 +177,34 @@ class SearchLanguageSetting(EnumStringSetting):
class MapSetting(Setting): class MapSetting(Setting):
"""Setting of a value that has to be translated in order to be storable""" """Setting of a value that has to be translated in order to be storable"""
def _post_init(self): def __init__(self, default_value, map: Dict[str, object], locked=False): # pylint: disable=redefined-builtin
if not hasattr(self, 'map'): super().__init__(default_value, locked)
raise MissingArgumentException('missing argument: map') self.map = map
if self.value not in self.map.values(): # pylint: disable=no-member
if self.value not in self.map.values():
raise ValidationException('Invalid default value') raise ValidationException('Invalid default value')
def parse(self, data): def parse(self, data: str):
"""Parse and validate ``data`` and store the result at ``self.value``""" """Parse and validate ``data`` and store the result at ``self.value``"""
# pylint: disable=no-member
if data not in self.map: if data not in self.map:
raise ValidationException('Invalid choice: {0}'.format(data)) raise ValidationException('Invalid choice: {0}'.format(data))
self.value = self.map[data] self.value = self.map[data]
self.key = data # pylint: disable=attribute-defined-outside-init self.key = data # pylint: disable=attribute-defined-outside-init
def save(self, name, resp): def save(self, name: str, resp: flask.Response):
"""Save cookie ``name`` in the HTTP reponse obect""" """Save cookie ``name`` in the HTTP reponse obect"""
if hasattr(self, 'key'): if hasattr(self, 'key'):
resp.set_cookie(name, self.key, max_age=COOKIE_MAX_AGE) resp.set_cookie(name, self.key, max_age=COOKIE_MAX_AGE)
class SwitchableSetting(Setting): class BooleanChoices:
"""Base class for settings that can be turned on && off""" """Maps strings to booleans that are either true or false."""
def _post_init(self): def __init__(self, name: str, choices: Dict[str, bool], locked: bool = False):
self.disabled = set() self.name = name
self.enabled = set() self.choices = choices
if not hasattr(self, 'choices'): self.locked = locked
raise MissingArgumentException('missing argument: choices')
def transform_form_items(self, items): def transform_form_items(self, items):
# pylint: disable=no-self-use # pylint: disable=no-self-use
@ -222,63 +214,54 @@ class SwitchableSetting(Setting):
# pylint: disable=no-self-use # pylint: disable=no-self-use
return values return values
def parse_cookie(self, data): def parse_cookie(self, data_disabled: str, data_enabled: str):
# pylint: disable=attribute-defined-outside-init for disabled in data_disabled.split(','):
if data[DISABLED] != '': if disabled in self.choices:
self.disabled = set(data[DISABLED].split(',')) self.choices[disabled] = False
if data[ENABLED] != '':
self.enabled = set(data[ENABLED].split(','))
def parse_form(self, items): for enabled in data_enabled.split(','):
if enabled in self.choices:
self.choices[enabled] = True
def parse_form(self, items: List[str]):
if self.locked: if self.locked:
return return
items = self.transform_form_items(items) disabled = self.transform_form_items(items)
self.disabled = set() # pylint: disable=attribute-defined-outside-init for setting in self.choices:
self.enabled = set() # pylint: disable=attribute-defined-outside-init self.choices[setting] = setting not in disabled
for choice in self.choices: # pylint: disable=no-member
if choice['default_on']:
if choice['id'] in items:
self.disabled.add(choice['id'])
else:
if choice['id'] not in items:
self.enabled.add(choice['id'])
def save(self, resp): # pylint: disable=arguments-differ @property
def enabled(self):
return (k for k, v in self.choices.items() if v)
@property
def disabled(self):
return (k for k, v in self.choices.items() if not v)
def save(self, resp: flask.Response):
"""Save cookie in the HTTP reponse obect""" """Save cookie in the HTTP reponse obect"""
resp.set_cookie('disabled_{0}'.format(self.value), ','.join(self.disabled), max_age=COOKIE_MAX_AGE) resp.set_cookie('disabled_{0}'.format(self.name), ','.join(self.disabled), max_age=COOKIE_MAX_AGE)
resp.set_cookie('enabled_{0}'.format(self.value), ','.join(self.enabled), max_age=COOKIE_MAX_AGE) resp.set_cookie('enabled_{0}'.format(self.name), ','.join(self.enabled), max_age=COOKIE_MAX_AGE)
def get_disabled(self): def get_disabled(self):
disabled = self.disabled return self.transform_values(list(self.disabled))
for choice in self.choices: # pylint: disable=no-member
if not choice['default_on'] and choice['id'] not in self.enabled:
disabled.add(choice['id'])
return self.transform_values(disabled)
def get_enabled(self): def get_enabled(self):
enabled = self.enabled return self.transform_values(list(self.enabled))
for choice in self.choices: # pylint: disable=no-member
if choice['default_on'] and choice['id'] not in self.disabled:
enabled.add(choice['id'])
return self.transform_values(enabled)
class EnginesSetting(SwitchableSetting): class EnginesSetting(BooleanChoices):
"""Engine settings""" """Engine settings"""
def _post_init(self): def __init__(self, default_value, engines: Iterable[Engine]):
super()._post_init() choices = {}
transformed_choices = [] for engine in engines:
for engine_name, engine in self.choices.items(): # pylint: disable=no-member,access-member-before-definition
for category in engine.categories: for category in engine.categories:
if not category in list(settings['categories_as_tabs'].keys()) + [OTHER_CATEGORY]: if not category in list(settings['categories_as_tabs'].keys()) + [OTHER_CATEGORY]:
continue continue
transformed_choice = {} choices['{}__{}'.format(engine.name, category)] = not engine.disabled
transformed_choice['default_on'] = not engine.disabled super().__init__(default_value, choices)
transformed_choice['id'] = '{}__{}'.format(engine_name, category)
transformed_choices.append(transformed_choice)
self.choices = transformed_choices
def transform_form_items(self, items): def transform_form_items(self, items):
return [item[len('engine_') :].replace('_', ' ').replace(' ', '__') for item in items] return [item[len('engine_') :].replace('_', ' ').replace(' ', '__') for item in items]
@ -293,18 +276,11 @@ class EnginesSetting(SwitchableSetting):
return transformed_values return transformed_values
class PluginsSetting(SwitchableSetting): class PluginsSetting(BooleanChoices):
"""Plugin settings""" """Plugin settings"""
def _post_init(self): def __init__(self, default_value, plugins: Iterable[Plugin]):
super()._post_init() super().__init__(default_value, {plugin.id: plugin.default_on for plugin in plugins})
transformed_choices = []
for plugin in self.choices: # pylint: disable=access-member-before-definition
transformed_choice = {}
transformed_choice['default_on'] = plugin.default_on
transformed_choice['id'] = plugin.id
transformed_choices.append(transformed_choice)
self.choices = transformed_choices
def transform_form_items(self, items): def transform_form_items(self, items):
return [item[len('plugin_') :] for item in items] return [item[len('plugin_') :] for item in items]
@ -313,34 +289,34 @@ class PluginsSetting(SwitchableSetting):
class Preferences: class Preferences:
"""Validates and saves preferences to cookies""" """Validates and saves preferences to cookies"""
def __init__(self, themes, categories, engines, plugins): def __init__(self, themes: List[str], categories: List[str], engines: Dict[str, Engine], plugins: Iterable[Plugin]):
super().__init__() super().__init__()
self.key_value_settings = { self.key_value_settings: Dict[str, Setting] = {
# fmt: off # fmt: off
'categories': MultipleChoiceSetting( 'categories': MultipleChoiceSetting(
['general'], ['general'],
is_locked('categories'), locked=is_locked('categories'),
choices=categories + ['none'] choices=categories + ['none']
), ),
'language': SearchLanguageSetting( 'language': SearchLanguageSetting(
settings['search']['default_lang'], settings['search']['default_lang'],
is_locked('language'), locked=is_locked('language'),
choices=settings['search']['languages'] + [''] choices=settings['search']['languages'] + ['']
), ),
'locale': EnumStringSetting( 'locale': EnumStringSetting(
settings['ui']['default_locale'], settings['ui']['default_locale'],
is_locked('locale'), locked=is_locked('locale'),
choices=list(LOCALE_NAMES.keys()) + [''] choices=list(LOCALE_NAMES.keys()) + ['']
), ),
'autocomplete': EnumStringSetting( 'autocomplete': EnumStringSetting(
settings['search']['autocomplete'], settings['search']['autocomplete'],
is_locked('autocomplete'), locked=is_locked('autocomplete'),
choices=list(autocomplete.backends.keys()) + [''] choices=list(autocomplete.backends.keys()) + ['']
), ),
'image_proxy': MapSetting( 'image_proxy': MapSetting(
settings['server']['image_proxy'], settings['server']['image_proxy'],
is_locked('image_proxy'), locked=is_locked('image_proxy'),
map={ map={
'': settings['server']['image_proxy'], '': settings['server']['image_proxy'],
'0': False, '0': False,
@ -351,12 +327,12 @@ class Preferences:
), ),
'method': EnumStringSetting( 'method': EnumStringSetting(
settings['server']['method'], settings['server']['method'],
is_locked('method'), locked=is_locked('method'),
choices=('GET', 'POST') choices=('GET', 'POST')
), ),
'safesearch': MapSetting( 'safesearch': MapSetting(
settings['search']['safe_search'], settings['search']['safe_search'],
is_locked('safesearch'), locked=is_locked('safesearch'),
map={ map={
'0': 0, '0': 0,
'1': 1, '1': 1,
@ -365,12 +341,12 @@ class Preferences:
), ),
'theme': EnumStringSetting( 'theme': EnumStringSetting(
settings['ui']['default_theme'], settings['ui']['default_theme'],
is_locked('theme'), locked=is_locked('theme'),
choices=themes choices=themes
), ),
'results_on_new_tab': MapSetting( 'results_on_new_tab': MapSetting(
settings['ui']['results_on_new_tab'], settings['ui']['results_on_new_tab'],
is_locked('results_on_new_tab'), locked=is_locked('results_on_new_tab'),
map={ map={
'0': False, '0': False,
'1': True, '1': True,
@ -380,22 +356,22 @@ class Preferences:
), ),
'doi_resolver': MultipleChoiceSetting( 'doi_resolver': MultipleChoiceSetting(
[settings['default_doi_resolver'], ], [settings['default_doi_resolver'], ],
is_locked('doi_resolver'), locked=is_locked('doi_resolver'),
choices=DOI_RESOLVERS choices=DOI_RESOLVERS
), ),
'oscar-style': EnumStringSetting( 'oscar-style': EnumStringSetting(
settings['ui']['theme_args']['oscar_style'], settings['ui']['theme_args']['oscar_style'],
is_locked('oscar-style'), locked=is_locked('oscar-style'),
choices=['', 'logicodev', 'logicodev-dark', 'pointhi'] choices=['', 'logicodev', 'logicodev-dark', 'pointhi']
), ),
'simple_style': EnumStringSetting( 'simple_style': EnumStringSetting(
settings['ui']['theme_args']['simple_style'], settings['ui']['theme_args']['simple_style'],
is_locked('simple_style'), locked=is_locked('simple_style'),
choices=['', 'auto', 'light', 'dark'] choices=['', 'auto', 'light', 'dark']
), ),
'advanced_search': MapSetting( 'advanced_search': MapSetting(
settings['ui']['advanced_search'], settings['ui']['advanced_search'],
is_locked('advanced_search'), locked=is_locked('advanced_search'),
map={ map={
'0': False, '0': False,
'1': True, '1': True,
@ -406,7 +382,7 @@ class Preferences:
), ),
'query_in_title': MapSetting( 'query_in_title': MapSetting(
settings['ui']['query_in_title'], settings['ui']['query_in_title'],
is_locked('query_in_title'), locked=is_locked('query_in_title'),
map={ map={
'': settings['ui']['query_in_title'], '': settings['ui']['query_in_title'],
'0': False, '0': False,
@ -418,10 +394,10 @@ class Preferences:
# fmt: on # fmt: on
} }
self.engines = EnginesSetting('engines', choices=engines) self.engines = EnginesSetting('engines', engines=engines.values())
self.plugins = PluginsSetting('plugins', choices=plugins) self.plugins = PluginsSetting('plugins', plugins=plugins)
self.tokens = SetSetting('tokens') self.tokens = SetSetting('tokens')
self.unknown_params = {} self.unknown_params: Dict[str, str] = {}
def get_as_url_params(self): def get_as_url_params(self):
"""Return preferences as URL parameters""" """Return preferences as URL parameters"""
@ -444,7 +420,7 @@ class Preferences:
return urlsafe_b64encode(compress(urlencode(settings_kv).encode())).decode() return urlsafe_b64encode(compress(urlencode(settings_kv).encode())).decode()
def parse_encoded_data(self, input_data): def parse_encoded_data(self, input_data: str):
"""parse (base64) preferences from request (``flask.request.form['preferences']``)""" """parse (base64) preferences from request (``flask.request.form['preferences']``)"""
bin_data = decompress(urlsafe_b64decode(input_data)) bin_data = decompress(urlsafe_b64decode(input_data))
dict_data = {} dict_data = {}
@ -452,7 +428,7 @@ class Preferences:
dict_data[x] = y[0] dict_data[x] = y[0]
self.parse_dict(dict_data) self.parse_dict(dict_data)
def parse_dict(self, input_data): def parse_dict(self, input_data: Dict[str, str]):
"""parse preferences from request (``flask.request.form``)""" """parse preferences from request (``flask.request.form``)"""
for user_setting_name, user_setting in input_data.items(): for user_setting_name, user_setting in input_data.items():
if user_setting_name in self.key_value_settings: if user_setting_name in self.key_value_settings:
@ -460,13 +436,9 @@ class Preferences:
continue continue
self.key_value_settings[user_setting_name].parse(user_setting) self.key_value_settings[user_setting_name].parse(user_setting)
elif user_setting_name == 'disabled_engines': elif user_setting_name == 'disabled_engines':
self.engines.parse_cookie( self.engines.parse_cookie(input_data.get('disabled_engines', ''), input_data.get('enabled_engines', ''))
(input_data.get('disabled_engines', ''), input_data.get('enabled_engines', ''))
)
elif user_setting_name == 'disabled_plugins': elif user_setting_name == 'disabled_plugins':
self.plugins.parse_cookie( self.plugins.parse_cookie(input_data.get('disabled_plugins', ''), input_data.get('enabled_plugins', ''))
(input_data.get('disabled_plugins', ''), input_data.get('enabled_plugins', ''))
)
elif user_setting_name == 'tokens': elif user_setting_name == 'tokens':
self.tokens.parse(user_setting) self.tokens.parse(user_setting)
elif not any( elif not any(
@ -474,7 +446,7 @@ class Preferences:
): ):
self.unknown_params[user_setting_name] = user_setting self.unknown_params[user_setting_name] = user_setting
def parse_form(self, input_data): def parse_form(self, input_data: Dict[str, str]):
"""Parse formular (``<input>``) data from a ``flask.request.form``""" """Parse formular (``<input>``) data from a ``flask.request.form``"""
disabled_engines = [] disabled_engines = []
enabled_categories = [] enabled_categories = []
@ -497,7 +469,7 @@ class Preferences:
self.plugins.parse_form(disabled_plugins) self.plugins.parse_form(disabled_plugins)
# cannot be used in case of engines or plugins # cannot be used in case of engines or plugins
def get_value(self, user_setting_name): def get_value(self, user_setting_name: str):
"""Returns the value for ``user_setting_name``""" """Returns the value for ``user_setting_name``"""
ret_val = None ret_val = None
if user_setting_name in self.key_value_settings: if user_setting_name in self.key_value_settings:
@ -506,7 +478,7 @@ class Preferences:
ret_val = self.unknown_params[user_setting_name] ret_val = self.unknown_params[user_setting_name]
return ret_val return ret_val
def save(self, resp): def save(self, resp: flask.Response):
"""Save cookie in the HTTP reponse obect""" """Save cookie in the HTTP reponse obect"""
for user_setting_name, user_setting in self.key_value_settings.items(): for user_setting_name, user_setting in self.key_value_settings.items():
# pylint: disable=unnecessary-dict-index-lookup # pylint: disable=unnecessary-dict-index-lookup
@ -532,7 +504,7 @@ class Preferences:
return valid return valid
def is_locked(setting_name): def is_locked(setting_name: str):
"""Checks if a given setting name is locked by settings.yml""" """Checks if a given setting name is locked by settings.yml"""
if 'preferences' not in settings: if 'preferences' not in settings:
return False return False

View File

@ -1,7 +1,6 @@
from searx.preferences import ( from searx.preferences import (
EnumStringSetting, EnumStringSetting,
MapSetting, MapSetting,
MissingArgumentException,
SearchLanguageSetting, SearchLanguageSetting,
MultipleChoiceSetting, MultipleChoiceSetting,
PluginsSetting, PluginsSetting,
@ -19,10 +18,6 @@ class PluginStub:
class TestSettings(SearxTestCase): class TestSettings(SearxTestCase):
# map settings # map settings
def test_map_setting_invalid_initialization(self):
with self.assertRaises(MissingArgumentException):
MapSetting(3, wrong_argument={'0': 0})
def test_map_setting_invalid_default_value(self): def test_map_setting_invalid_default_value(self):
with self.assertRaises(ValidationException): with self.assertRaises(ValidationException):
MapSetting(3, map={'dog': 1, 'bat': 2}) MapSetting(3, map={'dog': 1, 'bat': 2})
@ -43,9 +38,6 @@ class TestSettings(SearxTestCase):
self.assertEqual(setting.get_value(), 2) self.assertEqual(setting.get_value(), 2)
# enum settings # enum settings
def test_enum_setting_invalid_initialization(self):
with self.assertRaises(MissingArgumentException):
EnumStringSetting('cat', wrong_argument=[0, 1, 2])
def test_enum_setting_invalid_default_value(self): def test_enum_setting_invalid_default_value(self):
with self.assertRaises(ValidationException): with self.assertRaises(ValidationException):
@ -67,9 +59,6 @@ class TestSettings(SearxTestCase):
self.assertEqual(setting.get_value(), 2) self.assertEqual(setting.get_value(), 2)
# multiple choice settings # multiple choice settings
def test_multiple_setting_invalid_initialization(self):
with self.assertRaises(MissingArgumentException):
MultipleChoiceSetting(['2'], wrong_argument=['0', '1', '2'])
def test_multiple_setting_invalid_default_value(self): def test_multiple_setting_invalid_default_value(self):
with self.assertRaises(ValidationException): with self.assertRaises(ValidationException):
@ -115,15 +104,15 @@ class TestSettings(SearxTestCase):
def test_plugins_setting_all_default_enabled(self): def test_plugins_setting_all_default_enabled(self):
plugin1 = PluginStub('plugin1', True) plugin1 = PluginStub('plugin1', True)
plugin2 = PluginStub('plugin2', True) plugin2 = PluginStub('plugin2', True)
setting = PluginsSetting(['3'], choices=[plugin1, plugin2]) setting = PluginsSetting(['3'], plugins=[plugin1, plugin2])
self.assertEqual(setting.get_enabled(), set(['plugin1', 'plugin2'])) self.assertEqual(set(setting.get_enabled()), set(['plugin1', 'plugin2']))
def test_plugins_setting_few_default_enabled(self): def test_plugins_setting_few_default_enabled(self):
plugin1 = PluginStub('plugin1', True) plugin1 = PluginStub('plugin1', True)
plugin2 = PluginStub('plugin2', False) plugin2 = PluginStub('plugin2', False)
plugin3 = PluginStub('plugin3', True) plugin3 = PluginStub('plugin3', True)
setting = PluginsSetting('name', choices=[plugin1, plugin2, plugin3]) setting = PluginsSetting('name', plugins=[plugin1, plugin2, plugin3])
self.assertEqual(setting.get_enabled(), set(['plugin1', 'plugin3'])) self.assertEqual(set(setting.get_enabled()), set(['plugin1', 'plugin3']))
class TestPreferences(SearxTestCase): class TestPreferences(SearxTestCase):