mirror of
https://github.com/searxng/searxng.git
synced 2025-10-25 07:49:02 -04:00
[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>
This commit is contained in:
parent
f0dfe3cc0e
commit
21d0428cf2
@ -4,22 +4,5 @@
|
|||||||
``brand:``
|
``brand:``
|
||||||
==========
|
==========
|
||||||
|
|
||||||
.. code:: yaml
|
.. autoclass:: searx.brand.SettingsBrand
|
||||||
|
:members:
|
||||||
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
|
|
||||||
|
|
||||||
``issue_url`` :
|
|
||||||
If you host your own issue tracker change this URL.
|
|
||||||
|
|
||||||
``docs_url`` :
|
|
||||||
If you host your own documentation change this URL.
|
|
||||||
|
|
||||||
``public_instances`` :
|
|
||||||
If you host your own https://searx.space change this URL.
|
|
||||||
|
|
||||||
``wiki_url`` :
|
|
||||||
Link to your wiki (or ``false``)
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ from os.path import dirname, abspath
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import msgspec
|
||||||
import searx.unixthreadname # pylint: disable=unused-import
|
import searx.unixthreadname # pylint: disable=unused-import
|
||||||
|
|
||||||
# Debug
|
# 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.
|
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('.'):
|
for a in name.split('.'):
|
||||||
if isinstance(value, dict):
|
if isinstance(value, msgspec.Struct):
|
||||||
value = value.get(a, _unset)
|
value = getattr(value, a, _unset)
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
value = value.get(a, _unset) # pyright: ignore
|
||||||
else:
|
else:
|
||||||
value = _unset # type: ignore
|
value = _unset
|
||||||
|
|
||||||
if value is _unset:
|
if value is _unset:
|
||||||
if default is _unset:
|
if default is _unset:
|
||||||
raise KeyError(name)
|
raise KeyError(name)
|
||||||
value = default # type: ignore
|
value = default
|
||||||
break
|
break
|
||||||
|
|
||||||
return value
|
return value # pyright: ignore
|
||||||
|
|
||||||
|
|
||||||
def _is_color_terminal():
|
def _is_color_terminal():
|
||||||
|
|||||||
68
searx/brand.py
Normal file
68
searx/brand.py
Normal file
@ -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.
|
||||||
|
"""
|
||||||
@ -24,7 +24,6 @@ brand:
|
|||||||
wiki_url: https://github.com/searxng/searxng/wiki
|
wiki_url: https://github.com/searxng/searxng/wiki
|
||||||
issue_url: https://github.com/searxng/searxng/issues
|
issue_url: https://github.com/searxng/searxng/issues
|
||||||
# custom:
|
# custom:
|
||||||
# maintainer: "Jon Doe"
|
|
||||||
# # Custom entries in the footer: [title]: [link]
|
# # Custom entries in the footer: [title]: [link]
|
||||||
# links:
|
# links:
|
||||||
# Uptime: https://uptime.searxng.org/history/darmarit-org
|
# Uptime: https://uptime.searxng.org/history/darmarit-org
|
||||||
|
|||||||
@ -10,7 +10,10 @@ import logging
|
|||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
from os.path import dirname, abspath
|
from os.path import dirname, abspath
|
||||||
|
|
||||||
|
import msgspec
|
||||||
|
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
from .brand import SettingsBrand
|
||||||
from .sxng_locales import sxng_locales
|
from .sxng_locales import sxng_locales
|
||||||
|
|
||||||
searx_dir = abspath(dirname(__file__))
|
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]):
|
def apply_schema(settings: dict[str, t.Any], schema: dict[str, t.Any], path_list: list[str]):
|
||||||
error = False
|
error = False
|
||||||
for key, value in schema.items():
|
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:
|
try:
|
||||||
settings[key] = value(settings.get(key, _UNDEFINED))
|
settings[key] = value(settings.get(key, _UNDEFINED))
|
||||||
except Exception as e: # pylint: disable=broad-except
|
except Exception as e: # pylint: disable=broad-except
|
||||||
# don't stop now: check other values
|
# 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
|
error = True
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
error = error or apply_schema(settings.setdefault(key, {}), schema[key], [*path_list, key])
|
error = error or apply_schema(settings.setdefault(key, {}), schema[key], [*path_list, key])
|
||||||
else:
|
else:
|
||||||
settings.setdefault(key, value)
|
settings.setdefault(key, value)
|
||||||
if len(path_list) == 0 and error:
|
if len(path_list) == 0 and error:
|
||||||
raise ValueError('Invalid settings.yml')
|
raise ValueError("Invalid settings.yml")
|
||||||
return error
|
return error
|
||||||
|
|
||||||
|
|
||||||
@ -164,14 +186,7 @@ SCHEMA: dict[str, t.Any] = {
|
|||||||
'enable_metrics': SettingsValue(bool, True),
|
'enable_metrics': SettingsValue(bool, True),
|
||||||
'open_metrics': SettingsValue(str, ''),
|
'open_metrics': SettingsValue(str, ''),
|
||||||
},
|
},
|
||||||
'brand': {
|
'brand': SettingsBrand,
|
||||||
'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': {}}),
|
|
||||||
},
|
|
||||||
'search': {
|
'search': {
|
||||||
'safe_search': SettingsValue((0, 1, 2), 0),
|
'safe_search': SettingsValue((0, 1, 2), 0),
|
||||||
'autocomplete': SettingsValue(str, ''),
|
'autocomplete': SettingsValue(str, ''),
|
||||||
|
|||||||
@ -3,8 +3,6 @@ general:
|
|||||||
instance_name: "searx_test"
|
instance_name: "searx_test"
|
||||||
|
|
||||||
brand:
|
brand:
|
||||||
git_url: https://github.com/searxng/searxng
|
|
||||||
git_branch: master
|
|
||||||
issue_url: https://github.com/searxng/searxng/issues
|
issue_url: https://github.com/searxng/searxng/issues
|
||||||
new_issue_url: https://github.com/searxng/searxng/issues/new
|
new_issue_url: https://github.com/searxng/searxng/issues/new
|
||||||
docs_url: https://docs.searxng.org
|
docs_url: https://docs.searxng.org
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user