settings_loader.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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 'SEARXNG_SETTINGS_PATH' in environ:
  26. # if possible set path to settings using the
  27. # enviroment variable SEARXNG_SETTINGS_PATH
  28. return check_settings_yml(environ['SEARXNG_SETTINGS_PATH'])
  29. if environ.get('SEARXNG_DISABLE_ETC_SETTINGS', '').lower() in ('1', 'true'):
  30. return None
  31. # if not, get it from searx code base or last solution from /etc/searxng
  32. try:
  33. return check_settings_yml('/etc/searxng/settings.yml')
  34. except SearxSettingsException as e:
  35. # fall back to searx settings
  36. try:
  37. return check_settings_yml('/etc/searx/settings.yml')
  38. except SearxSettingsException:
  39. # if none are found, raise the exception about SearXNG
  40. raise e # pylint: disable=raise-missing-from
  41. def update_dict(default_dict, user_dict):
  42. for k, v in user_dict.items():
  43. if isinstance(v, Mapping):
  44. default_dict[k] = update_dict(default_dict.get(k, {}), v)
  45. else:
  46. default_dict[k] = v
  47. return default_dict
  48. def update_settings(default_settings, user_settings):
  49. # merge everything except the engines
  50. for k, v in user_settings.items():
  51. if k not in ('use_default_settings', 'engines'):
  52. if k in default_settings and isinstance(v, Mapping):
  53. update_dict(default_settings[k], v)
  54. else:
  55. default_settings[k] = v
  56. # parse the engines
  57. remove_engines = None
  58. keep_only_engines = None
  59. use_default_settings = user_settings.get('use_default_settings')
  60. if isinstance(use_default_settings, dict):
  61. remove_engines = use_default_settings.get('engines', {}).get('remove')
  62. keep_only_engines = use_default_settings.get('engines', {}).get('keep_only')
  63. if 'engines' in user_settings or remove_engines is not None or keep_only_engines is not None:
  64. engines = default_settings['engines']
  65. # parse "use_default_settings.engines.remove"
  66. if remove_engines is not None:
  67. engines = list(filterfalse(lambda engine: (engine.get('name')) in remove_engines, engines))
  68. # parse "use_default_settings.engines.keep_only"
  69. if keep_only_engines is not None:
  70. engines = list(filter(lambda engine: (engine.get('name')) in keep_only_engines, engines))
  71. # parse "engines"
  72. user_engines = user_settings.get('engines')
  73. if user_engines:
  74. engines_dict = dict((definition['name'], definition) for definition in engines)
  75. for user_engine in user_engines:
  76. default_engine = engines_dict.get(user_engine['name'])
  77. if default_engine:
  78. update_dict(default_engine, user_engine)
  79. else:
  80. engines.append(user_engine)
  81. # store the result
  82. default_settings['engines'] = engines
  83. return default_settings
  84. def is_use_default_settings(user_settings):
  85. use_default_settings = user_settings.get('use_default_settings')
  86. if use_default_settings is True:
  87. return True
  88. if isinstance(use_default_settings, dict):
  89. return True
  90. if use_default_settings is False or use_default_settings is None:
  91. return False
  92. raise ValueError('Invalid value for use_default_settings')
  93. def load_settings(load_user_setttings=True):
  94. default_settings_path = get_default_settings_path()
  95. user_settings_path = get_user_settings_path()
  96. if user_settings_path is None or not load_user_setttings:
  97. # no user settings
  98. return (load_yaml(default_settings_path),
  99. 'load the default settings from {}'.format(default_settings_path))
  100. # user settings
  101. user_settings = load_yaml(user_settings_path)
  102. if is_use_default_settings(user_settings):
  103. # the user settings are merged with the default configuration
  104. default_settings = load_yaml(default_settings_path)
  105. update_settings(default_settings, user_settings)
  106. return (default_settings,
  107. 'merge the default settings ( {} ) and the user setttings ( {} )'
  108. .format(default_settings_path, user_settings_path))
  109. # the user settings, fully replace the default configuration
  110. return (user_settings,
  111. 'load the user settings from {}'.format(user_settings_path))