[mod] typification of the preference settings

no functional change / except the missing online doc which is now available::

    $ make docs.live
    $ xdg-open "http://127.0.0.1:8000/admin/settings/settings_preferences.html"

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
This commit is contained in:
Markus Heiser
2026-05-30 14:43:50 +02:00
committed by Brock Vojkovic
parent e1d25c5078
commit 4ac822fd7f
11 changed files with 124 additions and 88 deletions
-1
View File
@@ -5,7 +5,6 @@ import type { KeyBindingLayout } from "./main/keyboard.ts";
// synced with searx/webapp.py get_client_settings // synced with searx/webapp.py get_client_settings
type Settings = { type Settings = {
plugins?: string[]; plugins?: string[];
advanced_search?: boolean;
autocomplete?: string; autocomplete?: string;
autocomplete_min?: number; autocomplete_min?: number;
doi_resolver?: string; doi_resolver?: string;
+1
View File
@@ -19,6 +19,7 @@ Settings
settings_search settings_search
settings_server settings_server
settings_ui settings_ui
settings_preferences
settings_redis settings_redis
settings_valkey settings_valkey
settings_outgoing settings_outgoing
@@ -0,0 +1,8 @@
.. _settings preferences:
================
``preferences:``
================
.. autoclass:: searx._settings.SettingsPref
:members:
+7 -6
View File
@@ -10,6 +10,7 @@ from os.path import dirname, abspath
import logging import logging
import msgspec import msgspec
from ._settings import SettingsPref
# Debug # Debug
LOG_FORMAT_DEBUG: str = '%(levelname)-7s %(name)-30.30s: %(message)s' LOG_FORMAT_DEBUG: str = '%(levelname)-7s %(name)-30.30s: %(message)s'
@@ -44,15 +45,15 @@ def init_settings():
cfg = cfg or {} cfg = cfg or {}
apply_schema(cfg, SCHEMA, []) apply_schema(cfg, SCHEMA, [])
if cfg['server']['public_instance']:
cfg['server']['image_proxy'] = True
lock = cfg.setdefault('preferences', {}).setdefault('lock', [])
if 'image_proxy' not in lock:
lock.append('image_proxy')
settings.clear() settings.clear()
settings.update(cfg) settings.update(cfg)
if get_setting("server.public_instance"):
# enable image proxy for public instances #6125
settings["server"]["image_proxy"] = True
pref: SettingsPref = get_setting("preferences")
pref.lock.add("image_proxy")
sxng_debug = get_setting("general.debug") sxng_debug = get_setting("general.debug")
if sxng_debug: if sxng_debug:
_logging_config_debug() _logging_config_debug()
+42
View File
@@ -0,0 +1,42 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Implementation of the :py:obj:`preference <searx.preference>` settings."""
# pylint: disable = too-few-public-methods
import typing as t
import msgspec
class SettingsPref(msgspec.Struct, kw_only=True, forbid_unknown_fields=True):
"""Options for configuring the preferences
.. code:: yaml
preferences:
lock:
- favicon_resolver
- image_proxy
- method
# ...
"""
lock: set[
t.Literal[
"categories",
"language",
"locale",
"autocomplete",
"favicon_resolver",
"image_proxy",
"method",
"safesearch",
"theme",
"results_on_new_tab",
"doi_resolver",
"simple_style",
"center_alignment",
"query_in_title",
"search_on_category_select",
]
] = set()
"""Lock arbitrary settings on the preferences page."""
+52 -63
View File
@@ -17,13 +17,14 @@ import babel.core
import searx.plugins import searx.plugins
from searx import settings, autocomplete, favicons from searx import get_setting, settings, autocomplete, favicons
from searx.enginelib import Engine from searx.enginelib import Engine
from searx.engines import DEFAULT_CATEGORY from searx.engines import DEFAULT_CATEGORY
from searx.extended_types import SXNG_Request from searx.extended_types import SXNG_Request
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 ._settings import SettingsPref
COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 5 # 5 years COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 5 # 5 years
DOI_RESOLVERS = list(settings['doi_resolvers']) DOI_RESOLVERS = list(settings['doi_resolvers'])
@@ -386,6 +387,7 @@ class ClientPref:
return cls(locale=locale) return cls(locale=locale)
@t.final
class Preferences: class Preferences:
"""Validates and saves preferences to cookies""" """Validates and saves preferences to cookies"""
@@ -400,95 +402,91 @@ class Preferences:
super().__init__() super().__init__()
self.cfg: SettingsPref = get_setting("preferences")
self.key_value_settings: dict[str, Setting] = { self.key_value_settings: dict[str, Setting] = {
# fmt: off
'categories': MultipleChoiceSetting( 'categories': MultipleChoiceSetting(
['general'], ["general"],
locked=is_locked('categories'), locked="categories" in self.cfg.lock,
choices=categories + ['none'] choices=categories + ["none"],
), ),
'language': SearchLanguageSetting( 'language': SearchLanguageSetting(
settings['search']['default_lang'], get_setting("search.default_lang"),
locked=is_locked('language'), locked="language" in self.cfg.lock,
choices=settings['search']['languages'] + [''] choices=get_setting("search.languages") + [""],
), ),
'locale': EnumStringSetting( 'locale': EnumStringSetting(
settings['ui']['default_locale'], get_setting("ui.default_locale"),
locked=is_locked('locale'), locked="locale" in self.cfg.lock,
choices=list(LOCALE_NAMES.keys()) + [''] choices=list(LOCALE_NAMES.keys()) + [""],
), ),
'autocomplete': EnumStringSetting( 'autocomplete': EnumStringSetting(
settings['search']['autocomplete'], get_setting("search.autocomplete"),
locked=is_locked('autocomplete'), locked="autocomplete" in self.cfg.lock,
choices=list(autocomplete.backends.keys()) + [''] choices=list(autocomplete.backends.keys()) + [""],
), ),
'favicon_resolver': EnumStringSetting( 'favicon_resolver': EnumStringSetting(
settings['search']['favicon_resolver'], get_setting("search.favicon_resolver"),
locked=is_locked('favicon_resolver'), locked="favicon_resolver" in self.cfg.lock,
choices=list(favicons.proxy.CFG.resolver_map.keys()) + [''] choices=list(favicons.proxy.CFG.resolver_map.keys()) + [''],
), ),
'image_proxy': BooleanSetting( 'image_proxy': BooleanSetting(
settings['server']['image_proxy'], get_setting("server.image_proxy"),
locked=is_locked('image_proxy') locked="image_proxy" in self.cfg.lock,
), ),
'method': EnumStringSetting( 'method': EnumStringSetting(
settings['server']['method'], get_setting("server.method"),
locked=is_locked('method'), locked="method" in self.cfg.lock,
choices=('GET', 'POST') choices=("GET", "POST"),
), ),
'safesearch': MapSetting( 'safesearch': MapSetting(
settings['search']['safe_search'], get_setting("search.safe_search"),
locked=is_locked('safesearch'), locked="safesearch" in self.cfg.lock,
map={ map={
'0': 0, "0": 0,
'1': 1, "1": 1,
'2': 2 "2": 2,
} },
), ),
'theme': EnumStringSetting( 'theme': EnumStringSetting(
settings['ui']['default_theme'], get_setting("ui.default_theme"),
locked=is_locked('theme'), locked="theme" in self.cfg.lock,
choices=themes choices=themes,
), ),
'results_on_new_tab': BooleanSetting( 'results_on_new_tab': BooleanSetting(
settings['ui']['results_on_new_tab'], get_setting("ui.results_on_new_tab"),
locked=is_locked('results_on_new_tab') locked="results_on_new_tab" in self.cfg.lock,
), ),
'doi_resolver': MultipleChoiceSetting( 'doi_resolver': MultipleChoiceSetting(
[settings['default_doi_resolver'], ], [get_setting("default_doi_resolver")],
locked=is_locked('doi_resolver'), locked="doi_resolver" in self.cfg.lock,
choices=DOI_RESOLVERS choices=DOI_RESOLVERS,
), ),
'simple_style': EnumStringSetting( 'simple_style': EnumStringSetting(
settings['ui']['theme_args']['simple_style'], get_setting("ui.theme_args.simple_style"),
locked=is_locked('simple_style'), locked="simple_style" in self.cfg.lock,
choices=['', 'auto', 'light', 'dark', 'black'] choices=["", "auto", "light", "dark", "black"],
), ),
'center_alignment': BooleanSetting( 'center_alignment': BooleanSetting(
settings['ui']['center_alignment'], get_setting("ui.center_alignment"),
locked=is_locked('center_alignment') locked="center_alignment" in self.cfg.lock,
),
'advanced_search': BooleanSetting(
settings['ui']['advanced_search'],
locked=is_locked('advanced_search')
), ),
'query_in_title': BooleanSetting( 'query_in_title': BooleanSetting(
settings['ui']['query_in_title'], get_setting("ui.query_in_title"),
locked=is_locked('query_in_title') locked="query_in_title" in self.cfg.lock,
), ),
'search_on_category_select': BooleanSetting( 'search_on_category_select': BooleanSetting(
settings['ui']['search_on_category_select'], get_setting("ui.search_on_category_select"),
locked=is_locked('search_on_category_select') locked="search_on_category_select" in self.cfg.lock,
), ),
'hotkeys': EnumStringSetting( 'hotkeys': EnumStringSetting(
settings['ui']['hotkeys'], get_setting("ui.hotkeys"),
choices=['default', 'vim'] choices=["default", "vim"],
), ),
'url_formatting': EnumStringSetting( 'url_formatting': EnumStringSetting(
settings['ui']['url_formatting'], get_setting("ui.url_formatting"),
choices=['pretty', 'full', 'host'] choices=["pretty", "full", "host"],
), ),
# fmt: on
} }
self.engines = EnginesSetting('engines', engines=engines.values()) self.engines = EnginesSetting('engines', engines=engines.values())
@@ -597,12 +595,3 @@ class Preferences:
break break
return valid return valid
def is_locked(setting_name: str):
"""Checks if a given setting name is locked by settings.yml"""
if 'preferences' not in settings:
return False
if 'lock' not in settings['preferences']:
return False
return setting_name in settings['preferences']['lock']
+4 -5
View File
@@ -154,14 +154,13 @@ ui:
# URL formatting: pretty, full or host # URL formatting: pretty, full or host
url_formatting: pretty url_formatting: pretty
# Lock arbitrary settings on the preferences page. preferences:
# # Lock arbitrary settings on the preferences page.
# preferences: lock: []
# lock:
# - categories # - categories
# - language # - language
# - autocomplete # - autocomplete
# - favicon # - favicon_resolver
# - safesearch # - safesearch
# - method # - method
# - doi_resolver # - doi_resolver
+2 -4
View File
@@ -15,6 +15,7 @@ import msgspec
from typing_extensions import override from typing_extensions import override
from .brand import SettingsBrand from .brand import SettingsBrand
from .sxng_locales import sxng_locales from .sxng_locales import sxng_locales
from ._settings import SettingsPref
searx_dir = abspath(dirname(__file__)) searx_dir = abspath(dirname(__file__))
@@ -236,16 +237,13 @@ SCHEMA: dict[str, t.Any] = {
}, },
'center_alignment': SettingsValue(bool, False), 'center_alignment': SettingsValue(bool, False),
'results_on_new_tab': SettingsValue(bool, False), 'results_on_new_tab': SettingsValue(bool, False),
'advanced_search': SettingsValue(bool, False),
'query_in_title': SettingsValue(bool, False), 'query_in_title': SettingsValue(bool, False),
'cache_url': SettingsValue(str, 'https://web.archive.org/web/'), 'cache_url': SettingsValue(str, 'https://web.archive.org/web/'),
'search_on_category_select': SettingsValue(bool, True), 'search_on_category_select': SettingsValue(bool, True),
'hotkeys': SettingsValue(('default', 'vim'), 'default'), 'hotkeys': SettingsValue(('default', 'vim'), 'default'),
'url_formatting': SettingsValue(('pretty', 'full', 'host'), 'pretty'), 'url_formatting': SettingsValue(('pretty', 'full', 'host'), 'pretty'),
}, },
'preferences': { "preferences": SettingsPref,
'lock': SettingsValue(list, []),
},
'outgoing': { 'outgoing': {
'useragent_suffix': SettingsValue(str, ''), 'useragent_suffix': SettingsValue(str, ''),
'request_timeout': SettingsValue(numbers.Real, 3.0), 'request_timeout': SettingsValue(numbers.Real, 3.0),
+1 -1
View File
@@ -180,7 +180,7 @@
{%- if 'autocomplete' not in locked_preferences -%} {%- if 'autocomplete' not in locked_preferences -%}
{%- include 'simple/preferences/autocomplete.html' -%} {%- include 'simple/preferences/autocomplete.html' -%}
{%- endif -%} {%- endif -%}
{%- if 'favicon' not in locked_preferences -%} {%- if 'favicon_resolver' not in locked_preferences -%}
{%- include 'simple/preferences/favicon.html' -%} {%- include 'simple/preferences/favicon.html' -%}
{%- endif -%} {%- endif -%}
{% if 'safesearch' not in locked_preferences %} {% if 'safesearch' not in locked_preferences %}
+6 -6
View File
@@ -8,7 +8,7 @@ from searx.webutils import VALID_LANGUAGE_CODE
from searx.query import RawTextQuery from searx.query import RawTextQuery
from searx.engines import categories, engines from searx.engines import categories, engines
from searx.search.models import SearchQuery, EngineRef from searx.search.models import SearchQuery, EngineRef
from searx.preferences import Preferences, is_locked from searx.preferences import Preferences
# remove duplicate queries. # remove duplicate queries.
@@ -53,7 +53,7 @@ def parse_pageno(form: Dict[str, str]) -> int:
def parse_lang(preferences: Preferences, form: Dict[str, str], raw_text_query: RawTextQuery) -> str: def parse_lang(preferences: Preferences, form: Dict[str, str], raw_text_query: RawTextQuery) -> str:
if is_locked('language'): if "language" in preferences.cfg.lock:
return preferences.get_value('language') return preferences.get_value('language')
# get language # get language
# set specific language if set on request, query or preferences # set specific language if set on request, query or preferences
@@ -73,7 +73,7 @@ def parse_lang(preferences: Preferences, form: Dict[str, str], raw_text_query: R
def parse_safesearch(preferences: Preferences, form: Dict[str, str]) -> int: def parse_safesearch(preferences: Preferences, form: Dict[str, str]) -> int:
if is_locked('safesearch'): if "safesearch" in preferences.cfg.lock:
return preferences.get_value('safesearch') return preferences.get_value('safesearch')
if 'safesearch' in form: if 'safesearch' in form:
@@ -135,7 +135,7 @@ def parse_category_form(query_categories: List[str], name: str, value: str) -> N
def get_selected_categories(preferences: Preferences, form: Optional[Dict[str, str]]) -> List[str]: def get_selected_categories(preferences: Preferences, form: Optional[Dict[str, str]]) -> List[str]:
selected_categories = [] selected_categories = []
if not is_locked('categories') and form is not None: if not "categories" in preferences.cfg.lock and form is not None:
for name, value in form.items(): for name, value in form.items():
parse_category_form(selected_categories, name, value) parse_category_form(selected_categories, name, value)
@@ -175,7 +175,7 @@ def parse_generic(preferences: Preferences, form: Dict[str, str], disabled_engin
# set categories/engines # set categories/engines
explicit_engine_list = False explicit_engine_list = False
if not is_locked('categories'): if not "categories" in preferences.cfg.lock:
# parse the form only if the categories are not locked # parse the form only if the categories are not locked
for pd_name, pd in form.items(): # pylint: disable=invalid-name for pd_name, pd in form.items(): # pylint: disable=invalid-name
if pd_name == 'engines': if pd_name == 'engines':
@@ -266,7 +266,7 @@ def get_search_query_from_webapp(
if query_lang == 'auto': if query_lang == 'auto':
query_lang = preferences.client.locale_tag or 'all' query_lang = preferences.client.locale_tag or 'all'
if not is_locked('categories') and raw_text_query.specific: if not "categories" in preferences.cfg.lock and raw_text_query.specific:
# if engines are calculated from query, # if engines are calculated from query,
# set categories by using that information # set categories by using that information
query_engineref_list = raw_text_query.enginerefs query_engineref_list = raw_text_query.enginerefs
+1 -2
View File
@@ -377,7 +377,6 @@ def get_client_settings():
'theme_static_path': custom_url_for('static', filename='themes/simple'), 'theme_static_path': custom_url_for('static', filename='themes/simple'),
'results_on_new_tab': req_pref.get_value('results_on_new_tab'), 'results_on_new_tab': req_pref.get_value('results_on_new_tab'),
'favicon_resolver': req_pref.get_value('favicon_resolver'), 'favicon_resolver': req_pref.get_value('favicon_resolver'),
'advanced_search': req_pref.get_value('advanced_search'),
'query_in_title': req_pref.get_value('query_in_title'), 'query_in_title': req_pref.get_value('query_in_title'),
'safesearch': req_pref.get_value('safesearch'), 'safesearch': req_pref.get_value('safesearch'),
'theme': req_pref.get_value('theme'), 'theme': req_pref.get_value('theme'),
@@ -977,7 +976,7 @@ def preferences():
current_doi_resolver = get_doi_resolver(), current_doi_resolver = get_doi_resolver(),
allowed_plugins = allowed_plugins, allowed_plugins = allowed_plugins,
preferences_url_params = sxng_request.preferences.get_as_url_params(), preferences_url_params = sxng_request.preferences.get_as_url_params(),
locked_preferences = get_setting("preferences.lock", []), locked_preferences = get_setting("preferences").lock,
doi_resolvers = get_setting("doi_resolvers", {}), doi_resolvers = get_setting("doi_resolvers", {}),
# fmt: on # fmt: on
) )