2"""Load and initialize the ``engines``, see :py:func:`load_engines` and register
3:py:obj:`engine_shortcuts`.
7 load_engines( settings['engines'] )
15from os.path
import realpath, dirname
20from searx
import logger, settings
26logger = logger.getChild(
'engines')
27ENGINE_DIR = dirname(realpath(__file__))
30ENGINE_DEFAULT_ARGS: dict[str, int | str | list[t.Any] | dict[str, t.Any] | bool] = {
32 "engine_type":
"online",
34 "time_range_support":
False,
37 "categories": [
"general"],
40 "timeout": settings[
"outgoing"][
"request_timeout"],
41 "display_error_messages":
True,
45 "using_tor_proxy":
False,
46 "send_accept_language_header":
False,
51DEFAULT_CATEGORY =
'other'
53categories:
"dict[str, list[Engine|types.ModuleType]]" = {
'general': []}
54engines:
"dict[str, Engine | types.ModuleType]" = {}
56"""Simple map of registered *shortcuts* to name of the engine (or ``None``).
60 engine_shortcuts[engine.shortcut] = engine.name
71 obj = getattr(module,
'network',
None)
72 if obj
and inspect.ismodule(obj):
73 msg = f
'type of {module.__name__}.network is a module ({obj.__name__}), expected a string'
78def load_engine(engine_data: dict[str, t.Any]) ->
"Engine | types.ModuleType | None":
79 """Load engine from ``engine_data``.
81 :param dict engine_data: Attributes from YAML ``settings:engines/<engine>``
82 :return: initialized namespace of the ``<engine>``.
84 1. create a namespace and load module of the ``<engine>``
85 2. update namespace with the defaults from :py:obj:`ENGINE_DEFAULT_ARGS`
86 3. update namespace with values from ``engine_data``
88 If engine *is active*, return namespace of the engine, otherwise return
91 This function also returns ``None`` if initialization of the namespace fails
92 for one of the following reasons:
94 - engine name contains underscore
95 - engine name is not lowercase
96 - required attribute is not set :py:func:`is_missing_required_attributes`
101 engine_name = engine_data.get(
'name')
102 if engine_name
is None:
103 logger.error(
'An engine does not have a "name" field')
105 if '_' in engine_name:
106 logger.error(
'Engine name contains underscore: "{}"'.format(engine_name))
109 if engine_name.lower() != engine_name:
110 logger.warning(
'Engine name is not lowercase: "{}", converting to lowercase'.format(engine_name))
111 engine_name = engine_name.lower()
112 engine_data[
'name'] = engine_name
115 module_name = engine_data.get(
'engine')
116 if module_name
is None:
117 logger.error(
'The "engine" field is missing for the engine named "{}"'.format(engine_name))
120 engine = load_module(module_name +
'.py', ENGINE_DIR)
121 except (SyntaxError, KeyboardInterrupt, SystemExit, SystemError, ImportError, RuntimeError):
122 logger.exception(
'Fatal exception in engine "{}"'.format(module_name))
124 except BaseException:
125 logger.exception(
'Cannot load engine "{}"'.format(module_name))
136 trait_map = EngineTraitsMap.from_data()
137 trait_map.set_traits(engine)
147 if not any(cat
in settings[
'categories_as_tabs']
for cat
in engine.categories):
148 engine.categories.append(DEFAULT_CATEGORY)
153def set_loggers(engine:
"Engine|types.ModuleType", engine_name: str):
155 engine.logger = logger.getChild(engine_name)
161 modules = sys.modules.copy()
162 for module_name, module
in modules.items():
164 module_name.startswith(
"searx.engines")
165 and module_name !=
"searx.engines.__init__"
166 and not hasattr(module,
"logger")
168 module_engine_name = module_name.split(
".")[-1]
169 module.logger = logger.getChild(module_engine_name)
174 for param_name, param_value
in engine_data.items():
175 if param_name ==
'categories':
176 if isinstance(param_value, str):
177 param_value = list(map(str.strip, param_value.split(
',')))
178 engine.categories = param_value
179 elif hasattr(engine,
'about')
and param_name ==
'about':
180 engine.about = {**engine.about, **engine_data[
'about']}
182 setattr(engine, param_name, param_value)
185 for arg_name, arg_value
in ENGINE_DEFAULT_ARGS.items():
186 if not hasattr(engine, arg_name):
187 setattr(engine, arg_name, copy.deepcopy(arg_value))
192 engine.search_url = engine.onion_url + getattr(engine,
'search_path',
'')
193 engine.timeout += settings[
'outgoing'].get(
'extra_proxy_timeout', 0)
197 """An attribute is required when its name doesn't start with ``_`` (underline).
198 Required attributes must not be ``None``.
202 for engine_attr
in dir(engine):
203 if not engine_attr.startswith(
'_')
and getattr(engine, engine_attr)
is None:
204 logger.error(
'Missing engine config attribute: "{0}.{1}"'.format(engine.name, engine_attr))
210 """Return True if the engine configuration declares to use Tor."""
211 return settings[
'outgoing'].get(
'using_tor_proxy')
or getattr(engine,
'using_tor_proxy',
False)
216 if engine.inactive
is True:
227 if engine.name
in engines:
228 logger.error(
'Engine config error: ambiguous name: {0}'.format(engine.name))
230 engines[engine.name] = engine
232 if engine.shortcut
in engine_shortcuts:
233 logger.error(
'Engine config error: ambiguous shortcut: {0}'.format(engine.shortcut))
235 engine_shortcuts[engine.shortcut] = engine.name
237 for category_name
in engine.categories:
238 categories.setdefault(category_name, []).append(engine)
242 """usage: ``engine_list = settings['engines']``"""
244 engine_shortcuts.clear()
246 categories[
'general'] = []
247 for engine_data
in engine_list:
using_tor_proxy("Engine | types.ModuleType" engine)
is_engine_active("Engine | types.ModuleType" engine)
"Engine | types.ModuleType | None" load_engine(dict[str, t.Any] engine_data)
update_engine_attributes("Engine | types.ModuleType" engine, dict[str, t.Any] engine_data)
is_missing_required_attributes("Engine | types.ModuleType" engine)
update_attributes_for_tor("Engine | types.ModuleType" engine)
check_engine_module(types.ModuleType module)
load_engines(list[dict[str, t.Any]] engine_list)
register_engine("Engine | types.ModuleType" engine)
set_loggers("Engine|types.ModuleType" engine, str engine_name)