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': []}
55engines:
"dict[str, Engine | types.ModuleType]" = {}
56"""Global registered engine instances."""
59"""Simple map of registered *shortcuts* to name of the engine (or ``None``).
63 engine_shortcuts[engine.shortcut] = engine.name
74 obj = getattr(module,
'network',
None)
75 if obj
and inspect.ismodule(obj):
76 msg = f
'type of {module.__name__}.network is a module ({obj.__name__}), expected a string'
81def load_engine(engine_data: dict[str, t.Any]) ->
"Engine | types.ModuleType | None":
82 """Load engine from ``engine_data``.
84 :param dict engine_data: Attributes from YAML ``settings:engines/<engine>``
85 :return: initialized namespace of the ``<engine>``.
87 1. create a namespace and load module of the ``<engine>``
88 2. update namespace with the defaults from :py:obj:`ENGINE_DEFAULT_ARGS`
89 3. update namespace with values from ``engine_data``
91 If engine *is active*, return namespace of the engine, otherwise return
94 This function also returns ``None`` if initialization of the namespace fails
95 for one of the following reasons:
97 - engine name contains underscore
98 - engine name is not lowercase
99 - required attribute is not set :py:func:`is_missing_required_attributes`
104 engine_name = engine_data.get(
'name')
105 if engine_name
is None:
106 logger.error(
'An engine does not have a "name" field')
108 if '_' in engine_name:
109 logger.error(
'Engine name contains underscore: "{}"'.format(engine_name))
112 if engine_name.lower() != engine_name:
113 logger.warning(
'Engine name is not lowercase: "{}", converting to lowercase'.format(engine_name))
114 engine_name = engine_name.lower()
115 engine_data[
'name'] = engine_name
118 module_name = engine_data.get(
'engine')
119 if module_name
is None:
120 logger.error(
'The "engine" field is missing for the engine named "{}"'.format(engine_name))
123 engine = load_module(module_name +
'.py', ENGINE_DIR)
124 except (SyntaxError, KeyboardInterrupt, SystemExit, SystemError, ImportError, RuntimeError):
125 logger.exception(
'Fatal exception in engine "{}"'.format(module_name))
127 except BaseException:
128 logger.exception(
'Cannot load engine "{}"'.format(module_name))
139 trait_map = EngineTraitsMap.from_data()
140 trait_map.set_traits(engine)
153 if not any(cat
in settings[
'categories_as_tabs']
for cat
in engine.categories):
154 engine.categories.append(DEFAULT_CATEGORY)
159def set_loggers(engine:
"Engine|types.ModuleType", engine_name: str):
161 engine.logger = logger.getChild(engine_name)
167 modules = sys.modules.copy()
168 for module_name, module
in modules.items():
170 module_name.startswith(
"searx.engines")
171 and module_name !=
"searx.engines.__init__"
172 and not hasattr(module,
"logger")
174 module_engine_name = module_name.split(
".")[-1]
175 module.logger = logger.getChild(module_engine_name)
180 for param_name, param_value
in engine_data.items():
181 if param_name ==
'categories':
182 if isinstance(param_value, str):
183 param_value = list(map(str.strip, param_value.split(
',')))
184 engine.categories = param_value
185 elif hasattr(engine,
'about')
and param_name ==
'about':
186 engine.about = {**engine.about, **engine_data[
'about']}
188 setattr(engine, param_name, param_value)
191 for arg_name, arg_value
in ENGINE_DEFAULT_ARGS.items():
192 if not hasattr(engine, arg_name):
193 setattr(engine, arg_name, copy.deepcopy(arg_value))
198 engine.search_url = engine.onion_url + getattr(engine,
'search_path',
'')
199 engine.timeout += settings[
'outgoing'].get(
'extra_proxy_timeout', 0)
203 """An attribute is required when its name doesn't start with ``_`` (underline).
204 Required attributes must not be ``None``.
208 for engine_attr
in dir(engine):
209 if not engine_attr.startswith(
'_')
and getattr(engine, engine_attr)
is None:
210 logger.error(
'Missing engine config attribute: "{0}.{1}"'.format(engine.name, engine_attr))
216 """Return True if the engine configuration declares to use Tor."""
217 return settings[
'outgoing'].get(
'using_tor_proxy')
or getattr(engine,
'using_tor_proxy',
False)
222 if engine.inactive
is True:
232def call_engine_setup(engine:
"Engine | types.ModuleType", engine_data: dict[str, t.Any]) -> bool:
234 setup_func = getattr(engine,
"setup",
None)
236 if setup_func
is None:
238 elif not callable(setup_func):
239 logger.error(
"engine's setup method isn't a callable (is of type: %s)", type(setup_func))
242 setup_ok = engine.setup(engine_data)
243 except Exception
as e:
244 logger.exception(
'exception : {0}'.format(e))
247 logger.error(
"%s: Engine setup was not successful, engine is set to inactive.", engine.name)
252 if engine.name
in engines:
253 logger.error(
'Engine config error: ambiguous name: {0}'.format(engine.name))
255 engines[engine.name] = engine
257 if engine.shortcut
in engine_shortcuts:
258 logger.error(
'Engine config error: ambiguous shortcut: {0}'.format(engine.shortcut))
260 engine_shortcuts[engine.shortcut] = engine.name
262 for category_name
in engine.categories:
263 categories.setdefault(category_name, []).append(engine)
267 """usage: ``engine_list = settings['engines']``"""
269 engine_shortcuts.clear()
271 categories[
'general'] = []
272 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)
bool call_engine_setup("Engine | types.ModuleType" engine, dict[str, t.Any] engine_data)
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)