settings_loader.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. # SPDX-License-Identifier: AGPL-3.0-or-later
  2. from os import environ
  3. from os.path import dirname, join, abspath, isfile
  4. from collections.abc import Mapping
  5. from itertools import filterfalse
  6. import yaml
  7. from searx.exceptions import SearxSettingsException
  8. searx_dir = abspath(dirname(__file__))
  9. def check_settings_yml(file_name):
  10. if isfile(file_name):
  11. return file_name
  12. return None
  13. def load_yaml(file_name):
  14. try:
  15. with open(file_name, 'r', encoding='utf-8') as settings_yaml:
  16. return yaml.safe_load(settings_yaml)
  17. except IOError as e:
  18. raise SearxSettingsException(e, file_name) from e
  19. except yaml.YAMLError as e:
  20. raise SearxSettingsException(e, file_name) from e
  21. def get_default_settings_path():
  22. return check_settings_yml(join(searx_dir, 'settings.yml'))
  23. def get_user_settings_path():
  24. # find location of settings.yml
  25. if 'SEARX_SETTINGS_PATH' in environ:
  26. # if possible set path to settings using the
  27. # environment variable SEARX_SETTINGS_PATH
  28. return check_settings_yml(environ['SEARX_SETTINGS_PATH'])
  29. # if not, get it from /etc/searx, or last resort the codebase
  30. return check_settings_yml('/etc/searx/settings.yml') or check_settings_yml('settings.yml')
  31. def update_dict(default_dict, user_dict):
  32. for k, v in user_dict.items():
  33. if isinstance(v, Mapping):
  34. default_dict[k] = update_dict(default_dict.get(k, {}), v)
  35. else:
  36. default_dict[k] = v
  37. return default_dict
  38. def update_settings(default_settings, user_settings):
  39. # merge everything except the engines
  40. for k, v in user_settings.items():
  41. if k not in ('use_default_settings', 'engines'):
  42. if k in default_settings and isinstance(v, Mapping):
  43. update_dict(default_settings[k], v)
  44. else:
  45. default_settings[k] = v
  46. # parse the engines
  47. remove_engines = None
  48. keep_only_engines = None
  49. use_default_settings = user_settings.get('use_default_settings')
  50. if isinstance(use_default_settings, dict):
  51. remove_engines = use_default_settings.get('engines', {}).get('remove')
  52. keep_only_engines = use_default_settings.get('engines', {}).get('keep_only')
  53. if 'engines' in user_settings or remove_engines is not None or keep_only_engines is not None:
  54. engines = default_settings['engines']
  55. # parse "use_default_settings.engines.remove"
  56. if remove_engines is not None:
  57. engines = list(filterfalse(lambda engine: (engine.get('name')) in remove_engines, engines))
  58. # parse "use_default_settings.engines.keep_only"
  59. if keep_only_engines is not None:
  60. engines = list(filter(lambda engine: (engine.get('name')) in keep_only_engines, engines))
  61. # parse "engines"
  62. user_engines = user_settings.get('engines')
  63. if user_engines:
  64. engines_dict = dict((definition['name'], definition) for definition in engines)
  65. for user_engine in user_engines:
  66. default_engine = engines_dict.get(user_engine['name'])
  67. if default_engine:
  68. update_dict(default_engine, user_engine)
  69. else:
  70. engines.append(user_engine)
  71. # store the result
  72. default_settings['engines'] = engines
  73. return default_settings
  74. def is_use_default_settings(user_settings):
  75. use_default_settings = user_settings.get('use_default_settings')
  76. if use_default_settings is True:
  77. return True
  78. if isinstance(use_default_settings, dict):
  79. return True
  80. if use_default_settings is False or use_default_settings is None:
  81. return False
  82. raise ValueError('Invalid value for use_default_settings')
  83. def load_settings(load_user_setttings=True):
  84. default_settings_path = get_default_settings_path()
  85. user_settings_path = get_user_settings_path()
  86. # no user settings
  87. if user_settings_path is None or not load_user_setttings:
  88. if default_settings_path is None:
  89. raise SearxSettingsException(
  90. 'missing default settings.yml file and there is no user configured file.\n'
  91. 'Please create a configuration file and put it under the root of searx or in /etc/searx or'
  92. 'configure the path in SEARX_SETTINGS_PATH.',
  93. None,
  94. )
  95. return (load_yaml(default_settings_path),
  96. 'load the default settings from {}'.format(default_settings_path))
  97. # user settings
  98. user_settings = load_yaml(user_settings_path)
  99. if is_use_default_settings(user_settings):
  100. # the user settings are merged with the default configuration
  101. default_settings = load_yaml(default_settings_path)
  102. update_settings(default_settings, user_settings)
  103. return (default_settings,
  104. 'merge the default settings ( {} ) and the user settings ( {} )'
  105. .format(default_settings_path, user_settings_path))
  106. # the user settings, fully replace the default configuration
  107. return (user_settings,
  108. 'load the user settings from {}'.format(user_settings_path))