.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
__init__.py
Go to the documentation of this file.
1# SPDX-License-Identifier: AGPL-3.0-or-later
2# pylint: disable=missing-module-docstring, too-few-public-methods
3
4# the public namespace has not yet been finally defined ..
5# __all__ = ["EngineRef", "SearchQuery"]
6
7import threading
8from timeit import default_timer
9from uuid import uuid4
10
11from flask import copy_current_request_context
12
13from searx import logger
14from searx import settings
15import searx.answerers
16import searx.plugins
17from searx.engines import load_engines
18from searx.extended_types import SXNG_Request
19from searx.external_bang import get_bang_url
20from searx.metrics import initialize as initialize_metrics, counter_inc, histogram_observe_time
21from searx.network import initialize as initialize_network, check_network_configuration
22from searx.results import ResultContainer
23from searx.search.checker import initialize as initialize_checker
24from searx.search.models import SearchQuery
25from searx.search.processors import PROCESSORS, initialize as initialize_processors
26
27from .models import EngineRef, SearchQuery
28
29logger = logger.getChild('search')
30
31
32def initialize(settings_engines=None, enable_checker=False, check_network=False, enable_metrics=True):
33 settings_engines = settings_engines or settings['engines']
34 load_engines(settings_engines)
35 initialize_network(settings_engines, settings['outgoing'])
36 if check_network:
37 check_network_configuration()
38 initialize_metrics([engine['name'] for engine in settings_engines], enable_metrics)
39 initialize_processors(settings_engines)
40 if enable_checker:
41 initialize_checker()
42
43
44class Search:
45 """Search information container"""
46
47 __slots__ = "search_query", "result_container", "start_time", "actual_timeout"
48
49 def __init__(self, search_query: SearchQuery):
50 """Initialize the Search"""
51 # init vars
52 super().__init__()
53 self.search_query = search_query
54 self.result_container = ResultContainer()
55 self.start_time = None
56 self.actual_timeout = None
57
58 def search_external_bang(self):
59 """
60 Check if there is a external bang.
61 If yes, update self.result_container and return True
62 """
63 if self.search_query.external_bang:
64 self.result_container.redirect_url = get_bang_url(self.search_query)
65
66 # This means there was a valid bang and the
67 # rest of the search does not need to be continued
68 if isinstance(self.result_container.redirect_url, str):
69 return True
70 return False
71
72 def search_answerers(self):
73
74 results = searx.answerers.STORAGE.ask(self.search_query.query)
75 self.result_container.extend(None, results)
76 return bool(results)
77
78 # do search-request
79 def _get_requests(self):
80 # init vars
81 requests = []
82
83 # max of all selected engine timeout
84 default_timeout = 0
85
86 # start search-request for all selected engines
87 for engineref in self.search_query.engineref_list:
88 processor = PROCESSORS[engineref.name]
89
90 # stop the request now if the engine is suspend
91 if processor.extend_container_if_suspended(self.result_container):
92 continue
93
94 # set default request parameters
95 request_params = processor.get_params(self.search_query, engineref.category)
96 if request_params is None:
97 continue
98
99 counter_inc('engine', engineref.name, 'search', 'count', 'sent')
100
101 # append request to list
102 requests.append((engineref.name, self.search_query.query, request_params))
103
104 # update default_timeout
105 default_timeout = max(default_timeout, processor.engine.timeout)
106
107 # adjust timeout
108 max_request_timeout = settings['outgoing']['max_request_timeout']
109 actual_timeout = default_timeout
110 query_timeout = self.search_query.timeout_limit
111
112 if max_request_timeout is None and query_timeout is None:
113 # No max, no user query: default_timeout
114 pass
115 elif max_request_timeout is None and query_timeout is not None:
116 # No max, but user query: From user query except if above default
117 actual_timeout = min(default_timeout, query_timeout)
118 elif max_request_timeout is not None and query_timeout is None:
119 # Max, no user query: Default except if above max
120 actual_timeout = min(default_timeout, max_request_timeout)
121 elif max_request_timeout is not None and query_timeout is not None:
122 # Max & user query: From user query except if above max
123 actual_timeout = min(query_timeout, max_request_timeout)
124
125 logger.debug(
126 "actual_timeout={0} (default_timeout={1}, ?timeout_limit={2}, max_request_timeout={3})".format(
127 actual_timeout, default_timeout, query_timeout, max_request_timeout
128 )
129 )
130
131 return requests, actual_timeout
132
133 def search_multiple_requests(self, requests):
134 # pylint: disable=protected-access
135 search_id = str(uuid4())
136
137 for engine_name, query, request_params in requests:
138 _search = copy_current_request_context(PROCESSORS[engine_name].search)
139 th = threading.Thread( # pylint: disable=invalid-name
140 target=_search,
141 args=(query, request_params, self.result_container, self.start_time, self.actual_timeout),
142 name=search_id,
143 )
144 th._timeout = False
145 th._engine_name = engine_name
146 th.start()
147
148 for th in threading.enumerate(): # pylint: disable=invalid-name
149 if th.name == search_id:
150 remaining_time = max(0.0, self.actual_timeout - (default_timer() - self.start_time))
151 th.join(remaining_time)
152 if th.is_alive():
153 th._timeout = True
154 self.result_container.add_unresponsive_engine(th._engine_name, 'timeout')
155 PROCESSORS[th._engine_name].logger.error('engine timeout')
156
157 def search_standard(self):
158 """
159 Update self.result_container, self.actual_timeout
160 """
161 requests, self.actual_timeout = self._get_requests()
162
163 # send all search-request
164 if requests:
165 self.search_multiple_requests(requests)
166
167 # return results, suggestions, answers and infoboxes
168 return True
169
170 # do search-request
171 def search(self) -> ResultContainer:
172 self.start_time = default_timer()
173 if not self.search_external_bang():
174 if not self.search_answerers():
175 self.search_standard()
176 return self.result_container
177
178
179class SearchWithPlugins(Search):
180 """Inherit from the Search class, add calls to the plugins."""
181
182 __slots__ = 'user_plugins', 'request'
183
184 def __init__(self, search_query: SearchQuery, request: SXNG_Request, user_plugins: list[str]):
185 super().__init__(search_query)
186 self.user_plugins = user_plugins
187 self.result_container.on_result = self._on_result
188 # pylint: disable=line-too-long
189 # get the "real" request to use it outside the Flask context.
190 # see
191 # * https://github.com/pallets/flask/blob/d01d26e5210e3ee4cbbdef12f05c886e08e92852/src/flask/globals.py#L55
192 # * https://github.com/pallets/werkzeug/blob/3c5d3c9bd0d9ce64590f0af8997a38f3823b368d/src/werkzeug/local.py#L548-L559
193 # * https://werkzeug.palletsprojects.com/en/2.0.x/local/#werkzeug.local.LocalProxy._get_current_object
194 # pylint: enable=line-too-long
195 self.request = request._get_current_object()
196
197 def _on_result(self, result):
198 return searx.plugins.STORAGE.on_result(self.request, self, result)
199
200 def search(self) -> ResultContainer:
201
202 if searx.plugins.STORAGE.pre_search(self.request, self):
203 super().search()
204
205 searx.plugins.STORAGE.post_search(self.request, self)
206 self.result_container.close()
207
208 return self.result_container
::1337x
Definition 1337x.py:1
initialize(settings_engines=None, enable_checker=False, check_network=False, enable_metrics=True)
Definition __init__.py:32