@ -1,68 +1,116 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# pylint: disable=missing-module-docstring, too-many-branches
""" Implementations for loading configurations from YAML files. This essentially
includes the configuration of the ( : ref : ` SearXNG appl < searxng settings . yml > ` )
server . The default configuration for the application server is loaded from the
: origin : ` DEFAULT_SETTINGS_FILE < searx / settings . yml > ` . This default
configuration can be completely replaced or : ref : ` customized individually
< use_default_settings . yml > ` and the ` ` SEARXNG_SETTINGS_PATH ` ` environment
variable can be used to set the location from which the local customizations are
to be loaded . The rules used for this can be found in the
: py : obj : ` get_user_cfg_folder ` function .
from typing import Optional
from os import environ
from os . path import dirname , join , abspath , isfile
- By default , local configurations are expected in folder ` ` / etc / searxng ` ` from
where applications can load them with the : py : obj : ` get_yaml_cfg ` function .
- By default , customized : ref : ` SearXNG appl < searxng settings . yml > ` settings are
expected in a file named ` ` settings . yml ` ` .
"""
from __future__ import annotations
import os . path
from collections . abc import Mapping
from itertools import filterfalse
from pathlib import Path
import yaml
from searx . exceptions import SearxSettingsException
searx_dir = os . path . abspath ( os . path . dirname ( __file__ ) )
searx_dir = abspath ( dirname ( __file__ ) )
def existing_filename_or_none ( file_name : str ) - > Optional [ str ] :
if isfile ( file_name ) :
return file_name
return None
SETTINGS_YAML = Path ( " settings.yml " )
DEFAULT_SETTINGS_FILE = Path ( searx_dir ) / SETTINGS_YAML
""" The :origin:`searx/settings.yml` file with all the default settings. """
def load_yaml ( file_name ) :
def load_yaml ( file_name : str | Path ) :
""" Load YAML config from a file. """
try :
with open ( file_name , ' r ' , encoding = ' utf-8 ' ) as settings_yaml :
return yaml . safe_load ( settings_yaml )
return yaml . safe_load ( settings_yaml ) or { }
except IOError as e :
raise SearxSettingsException ( e , file_name ) from e
raise SearxSettingsException ( e , str ( file_name ) ) from e
except yaml . YAMLError as e :
raise SearxSettingsException ( e , file_name ) from e
raise SearxSettingsException ( e , str ( file_name ) ) from e
def get_yaml_cfg ( file_name : str | Path ) - > dict :
""" Shortcut to load a YAML config from a file, located in the
- : py : obj : ` get_user_cfg_folder ` or
- in the ` ` searx ` ` folder of the SearXNG installation
"""
folder = get_user_cfg_folder ( ) or Path ( searx_dir )
fname = folder / file_name
if not fname . is_file ( ) :
raise FileNotFoundError ( f " File { fname } does not exist! " )
return load_yaml ( fname )
def get_yaml_file ( file_name ) :
path = existing_filename_or_none ( join ( searx_dir , file_name ) )
if path is None :
raise FileNotFoundError ( f " File { file_name } does not exist! " )
def get_user_cfg_folder ( ) - > Path | None :
""" Returns folder where the local configurations are located.
return load_yaml ( path )
1. If the ` ` SEARXNG_SETTINGS_PATH ` ` environment is set and points to a
folder ( e . g . ` ` / etc / mysxng / ` ` ) , all local configurations are expected in
this folder . The settings of the : ref : ` SearXNG appl < searxng
settings . yml > ` then expected in ` ` settings . yml ` `
( e . g . ` ` / etc / mysxng / settings . yml ` ` ) .
2. If the ` ` SEARXNG_SETTINGS_PATH ` ` environment is set and points to a file
( e . g . ` ` / etc / mysxng / myinstance . yml ` ` ) , this file contains the settings of
the : ref : ` SearXNG appl < searxng settings . yml > ` and the folder
( e . g . ` ` / etc / mysxng / ` ` ) is used for all other configurations .
def get_default_settings_path ( ) :
return existing_filename_or_none ( join ( searx_dir , ' settings.yml ' ) )
This type ( ` ` SEARXNG_SETTINGS_PATH ` ` points to a file ) is suitable for
use cases in which different profiles of the : ref : ` SearXNG appl < searxng
settings . yml > ` are to be managed , such as in test scenarios .
3. If folder ` ` / etc / searxng ` ` exists , it is used .
In case none of the above path exists , ` ` None ` ` is returned . In case of
environment ` ` SEARXNG_SETTINGS_PATH ` ` is set , but the ( folder or file ) does
not exists , a : py : obj : ` EnvironmentError ` is raised .
def get_user_settings_path ( ) - > Optional [ str ] :
""" Get an user settings file.
By descending priority :
1. ` ` environ [ ' SEARXNG_SETTINGS_PATH ' ] ` `
2. ` ` / etc / searxng / settings . yml ` ` except if ` ` SEARXNG_DISABLE_ETC_SETTINGS ` ` is ` ` true ` ` or ` ` 1 ` `
3. ` ` None ` `
"""
# check the environment variable SEARXNG_SETTINGS_PATH
# if the environment variable is defined, this is the last check
if ' SEARXNG_SETTINGS_PATH ' in environ :
return existing_filename_or_none ( environ [ ' SEARXNG_SETTINGS_PATH ' ] )
folder = None
settings_path = os . environ . get ( " SEARXNG_SETTINGS_PATH " )
# Disable default /etc/searxng is intended exclusively for internal testing purposes
# and is therefore not documented!
disable_etc = os . environ . get ( ' SEARXNG_DISABLE_ETC_SETTINGS ' , ' ' ) . lower ( ) in ( ' 1 ' , ' true ' )
if settings_path :
# rule 1. and 2.
settings_path = Path ( settings_path )
if settings_path . is_dir ( ) :
folder = settings_path
elif settings_path . is_file ( ) :
folder = settings_path . parent
else :
raise EnvironmentError ( 1 , f " { settings_path } not exists! " , settings_path )
# if SEARXNG_DISABLE_ETC_SETTINGS don't look any further
if environ . get ( ' SEARXNG_DISABLE_ETC_SETTINGS ' , ' ' ) . lower ( ) in ( ' 1 ' , ' true ' ) :
return None
if not folder and not disable_etc :
# default: rule 3.
folder = Path ( " /etc/searxng " )
if not folder . is_dir ( ) :
folder = None
# check /etc/searxng/settings.yml
# (continue with other locations if the file is not found)
return existing_filename_or_none ( ' /etc/searxng/settings.yml ' )
return folder
def update_dict ( default_dict , user_dict ) :
@ -74,7 +122,9 @@ def update_dict(default_dict, user_dict):
return default_dict
def update_settings ( default_settings , user_settings ) :
def update_settings ( default_settings : dict , user_settings : dict ) :
# pylint: disable=too-many-branches
# merge everything except the engines
for k , v in user_settings . items ( ) :
if k not in ( ' use_default_settings ' , ' engines ' ) :
@ -124,6 +174,7 @@ def update_settings(default_settings, user_settings):
def is_use_default_settings ( user_settings ) :
use_default_settings = user_settings . get ( ' use_default_settings ' )
if use_default_settings is True :
return True
@ -134,25 +185,37 @@ def is_use_default_settings(user_settings):
raise ValueError ( ' Invalid value for use_default_settings ' )
def load_settings ( load_user_settings = True ) :
default_settings_path = get_default_settings_path ( )
user_settings_path = get_user_settings_path ( )
if user_settings_path is None or not load_user_settings :
# no user settings
return ( load_yaml ( default_settings_path ) , ' load the default settings from {} ' . format ( default_settings_path ) )
def load_settings ( load_user_settings = True ) - > tuple [ dict , str ] :
""" Function for loading the settings of the SearXNG application
( : ref : ` settings . yml < searxng settings . yml > ` ) . """
# user settings
user_settings = load_yaml ( user_settings_path )
if is_use_default_settings ( user_settings ) :
msg = f " load the default settings from { DEFAULT_SETTINGS_FILE } "
cfg = load_yaml ( DEFAULT_SETTINGS_FILE )
cfg_folder = get_user_cfg_folder ( )
if not load_user_settings or not cfg_folder :
return cfg , msg
settings_yml = os . environ . get ( " SEARXNG_SETTINGS_PATH " )
if settings_yml and Path ( settings_yml ) . is_file ( ) :
# see get_user_cfg_folder() --> SEARXNG_SETTINGS_PATH points to a file
settings_yml = Path ( settings_yml ) . name
else :
# see get_user_cfg_folder() --> SEARXNG_SETTINGS_PATH points to a folder
settings_yml = SETTINGS_YAML
cfg_file = cfg_folder / settings_yml
if not cfg_file . exists ( ) :
return cfg , msg
msg = f " load the user settings from { cfg_file } "
user_cfg = load_yaml ( cfg_file )
if is_use_default_settings ( user_cfg ) :
# the user settings are merged with the default configuration
default_settings = load_yaml ( default_settings_path )
update_settings ( default_settings , user_settings )
return (
default_settings ,
' merge the default settings ( {} ) and the user settings ( {} ) ' . format (
default_settings_path , user_settings_path
) ,
)
# the user settings, fully replace the default configuration
return ( user_settings , ' load the user settings from {} ' . format ( user_settings_path ) )
msg = f " merge the default settings ( { DEFAULT_SETTINGS_FILE } ) and the user settings ( { cfg_file } ) "
update_settings ( cfg , user_cfg )
else :
cfg = user_cfg
return cfg , msg