summaryrefslogtreecommitdiff
path: root/searx
diff options
context:
space:
mode:
authorMarkus Heiser <markus.heiser@darmarIT.de>2025-10-10 16:14:29 +0200
committerGitHub <noreply@github.com>2025-10-10 16:14:29 +0200
commit21d0428cf2dd5c1e3f8ae1702494bbaedf90c2fc (patch)
tree0e093ddfb89ce4ee7f9b58eb4f2fde9dcbedae17 /searx
parentf0dfe3cc0e2b9fce3e69a7525ab5fa073dbd459e (diff)
[mod] brand - partial migration of settings to msgspec.Struct (#5280)
The settings are currently an untyped key/value structure, whose types are dynamically built at runtime. The construction process of this structure is *hand-crafted*. In the long term, we want a static typing of this structure, based on a standard tool. The ``msgspec.Struct`` structures are suitable as a standard tool. This patch makes a first step towards static typing and implements the "brand" section using ``msgspec.Struct`` structures. BTW: searx/settings_defaults.py - ``git_url`` and ``git_branch`` had been removed in aee613d256, this is a leftover. Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
Diffstat (limited to 'searx')
-rw-r--r--searx/__init__.py15
-rw-r--r--searx/brand.py68
-rw-r--r--searx/settings.yml1
-rw-r--r--searx/settings_defaults.py37
4 files changed, 103 insertions, 18 deletions
diff --git a/searx/__init__.py b/searx/__init__.py
index 045affab0..5ee367d70 100644
--- a/searx/__init__.py
+++ b/searx/__init__.py
@@ -9,6 +9,7 @@ from os.path import dirname, abspath
import logging
+import msgspec
import searx.unixthreadname # pylint: disable=unused-import
# Debug
@@ -76,20 +77,22 @@ def get_setting(name: str, default: t.Any = _unset) -> t.Any:
settings and the ``default`` is unset, a :py:obj:`KeyError` is raised.
"""
- value: dict[str, t.Any] = settings
+ value = settings
for a in name.split('.'):
- if isinstance(value, dict):
- value = value.get(a, _unset)
+ if isinstance(value, msgspec.Struct):
+ value = getattr(value, a, _unset)
+ elif isinstance(value, dict):
+ value = value.get(a, _unset) # pyright: ignore
else:
- value = _unset # type: ignore
+ value = _unset
if value is _unset:
if default is _unset:
raise KeyError(name)
- value = default # type: ignore
+ value = default
break
- return value
+ return value # pyright: ignore
def _is_color_terminal():
diff --git a/searx/brand.py b/searx/brand.py
new file mode 100644
index 000000000..9627da07d
--- /dev/null
+++ b/searx/brand.py
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: AGPL-3.0-or-later
+"""Implementations needed for a branding of SearXNG."""
+# pylint: disable=too-few-public-methods
+
+# Struct fields aren't discovered in Python 3.14
+# - https://github.com/searxng/searxng/issues/5284
+from __future__ import annotations
+
+__all__ = ["SettingsBrand"]
+
+import msgspec
+
+
+class BrandCustom(msgspec.Struct, kw_only=True, forbid_unknown_fields=True):
+ """Custom settings in the brand section."""
+
+ links: dict[str, str] = {}
+ """Custom entries in the footer of the WEB page: ``[title]: [link]``"""
+
+
+class SettingsBrand(msgspec.Struct, kw_only=True, forbid_unknown_fields=True):
+ """Options for configuring brand properties.
+
+ .. code:: yaml
+
+ brand:
+ issue_url: https://github.com/searxng/searxng/issues
+ docs_url: https://docs.searxng.org
+ public_instances: https://searx.space
+ wiki_url: https://github.com/searxng/searxng/wiki
+
+ custom:
+ links:
+ Uptime: https://uptime.searxng.org/history/example-org
+ About: https://example.org/user/about.html
+ """
+
+ issue_url: str = "https://github.com/searxng/searxng/issues"
+ """If you host your own issue tracker change this URL."""
+
+ docs_url: str = "https://docs.searxng.org"
+ """If you host your own documentation change this URL."""
+
+ public_instances: str = "https://searx.space"
+ """If you host your own https://searx.space change this URL."""
+
+ wiki_url: str = "https://github.com/searxng/searxng/wiki"
+ """Link to your wiki (or ``false``)"""
+
+ custom: BrandCustom = msgspec.field(default_factory=BrandCustom)
+ """Optional customizing.
+
+ .. autoclass:: searx.brand.BrandCustom
+ :members:
+ """
+
+ # new_issue_url is a hackish solution tailored for only one hoster (GH). As
+ # long as we don't have a more general solution, we should support it in the
+ # given function, but it should not be expanded further.
+
+ new_issue_url: str = "https://github.com/searxng/searxng/issues/new"
+ """If you host your own issue tracker not on GitHub, then unset this URL.
+
+ Note: This URL will create a pre-filled GitHub bug report form for an
+ engine. Since this feature is implemented only for GH (and limited to
+ engines), it will probably be replaced by another solution in the near
+ future.
+ """
diff --git a/searx/settings.yml b/searx/settings.yml
index a455e8cad..95202707f 100644
--- a/searx/settings.yml
+++ b/searx/settings.yml
@@ -24,7 +24,6 @@ brand:
wiki_url: https://github.com/searxng/searxng/wiki
issue_url: https://github.com/searxng/searxng/issues
# custom:
- # maintainer: "Jon Doe"
# # Custom entries in the footer: [title]: [link]
# links:
# Uptime: https://uptime.searxng.org/history/darmarit-org
diff --git a/searx/settings_defaults.py b/searx/settings_defaults.py
index dba5ffc20..c71103a4c 100644
--- a/searx/settings_defaults.py
+++ b/searx/settings_defaults.py
@@ -10,7 +10,10 @@ import logging
from base64 import b64decode
from os.path import dirname, abspath
+import msgspec
+
from typing_extensions import override
+from .brand import SettingsBrand
from .sxng_locales import sxng_locales
searx_dir = abspath(dirname(__file__))
@@ -138,19 +141,38 @@ class SettingsBytesValue(SettingsValue):
def apply_schema(settings: dict[str, t.Any], schema: dict[str, t.Any], path_list: list[str]):
error = False
for key, value in schema.items():
- if isinstance(value, SettingsValue):
+ if isinstance(value, type) and issubclass(value, msgspec.Struct):
+ try:
+ # Type Validation at runtime:
+ # https://jcristharif.com/msgspec/structs.html#type-validation
+ cfg_dict = settings.get(key)
+ cfg_json = msgspec.json.encode(cfg_dict)
+ settings[key] = msgspec.json.decode(cfg_json, type=value)
+ except msgspec.ValidationError as e:
+ # To get a more meaningful error message, we need to replace the
+ # `$` by the (doted) name space. For example if ValidationError
+ # was raised for the field `name` in structure at `foo.bar`:
+ # Expected `str`, got `int` - at `$.name`
+ # is converted to:
+ # Expected `str`, got `int` - at `foo.bar.name`
+ msg = str(e)
+ msg = msg.replace("`$.", "`" + ".".join([*path_list, key]) + ".")
+ logger.error(msg)
+ error = True
+ elif isinstance(value, SettingsValue):
try:
settings[key] = value(settings.get(key, _UNDEFINED))
except Exception as e: # pylint: disable=broad-except
# don't stop now: check other values
- logger.error('%s: %s', '.'.join([*path_list, key]), e)
+ msg = ".".join([*path_list, key]) + f": {e}"
+ logger.error(msg)
error = True
elif isinstance(value, dict):
error = error or apply_schema(settings.setdefault(key, {}), schema[key], [*path_list, key])
else:
settings.setdefault(key, value)
if len(path_list) == 0 and error:
- raise ValueError('Invalid settings.yml')
+ raise ValueError("Invalid settings.yml")
return error
@@ -164,14 +186,7 @@ SCHEMA: dict[str, t.Any] = {
'enable_metrics': SettingsValue(bool, True),
'open_metrics': SettingsValue(str, ''),
},
- 'brand': {
- 'issue_url': SettingsValue(str, 'https://github.com/searxng/searxng/issues'),
- 'new_issue_url': SettingsValue(str, 'https://github.com/searxng/searxng/issues/new'),
- 'docs_url': SettingsValue(str, 'https://docs.searxng.org'),
- 'public_instances': SettingsValue((False, str), 'https://searx.space'),
- 'wiki_url': SettingsValue((False, str), 'https://github.com/searxng/searxng/wiki'),
- 'custom': SettingsValue(dict, {'links': {}}),
- },
+ 'brand': SettingsBrand,
'search': {
'safe_search': SettingsValue((0, 1, 2), 0),
'autocomplete': SettingsValue(str, ''),