2"""Engine's traits are fetched from the origin engines and stored in a JSON file
3in the *data folder*. Most often traits are languages and region codes and
4their mapping from SearXNG's representation to the representation in the origin
5search engine. For new traits new properties can be added to the class
6:py:class:`EngineTraits`.
8To load traits from the persistence :py:obj:`EngineTraitsMap.from_data` can be
19from searx
import locales
27 """Encodes :class:`EngineTraits` to a serializable object, see
28 :class:`json.JSONEncoder`."""
31 """Return dictionary of a :class:`EngineTraits` object."""
32 if isinstance(o, EngineTraits):
39 """The class is intended to be instantiated for each engine."""
41 regions: dict[str, str] = dataclasses.field(default_factory=dict)
42 """Maps SearXNG's internal representation of a region to the one of the engine.
44 SearXNG's internal representation can be parsed by babel and the value is
50 'fr-BE' : <engine's region name>,
53 for key, egnine_region regions.items():
54 searxng_region = babel.Locale.parse(key, sep='-')
58 languages: dict[str, str] = dataclasses.field(default_factory=dict)
59 """Maps SearXNG's internal representation of a language to the one of the engine.
61 SearXNG's internal representation can be parsed by babel and the value is
67 'ca' : <engine's language name>,
70 for key, egnine_lang in languages.items():
71 searxng_lang = babel.Locale.parse(key)
75 all_locale: str |
None =
None
76 """To which locale value SearXNG's ``all`` language is mapped (shown a "Default
80 data_type: t.Literal[
'traits_v1'] =
'traits_v1'
81 """Data type, default is 'traits_v1'.
84 custom: dict[str, t.Any] = dataclasses.field(default_factory=dict)
85 """A place to store engine's custom traits, not related to the SearXNG core.
88 def get_language(self, searxng_locale: str, default: t.Any =
None):
89 """Return engine's language string that *best fits* to SearXNG's locale.
91 :param searxng_locale: SearXNG's internal representation of locale
94 :param default: engine's default language
96 The *best fits* rules are implemented in
97 :py:obj:`searx.locales.get_engine_locale`. Except for the special value ``all``
98 which is determined from :py:obj:`EngineTraits.all_locale`.
100 if searxng_locale ==
'all' and self.
all_locale is not None:
102 return locales.get_engine_locale(searxng_locale, self.
languages, default=default)
104 def get_region(self, searxng_locale: str, default: t.Any =
None) -> t.Any:
105 """Return engine's region string that best fits to SearXNG's locale.
107 :param searxng_locale: SearXNG's internal representation of locale
108 selected by the user.
110 :param default: engine's default region
112 The *best fits* rules are implemented in
113 :py:obj:`searx.locales.get_engine_locale`. Except for the special value ``all``
114 which is determined from :py:obj:`EngineTraits.all_locale`.
116 if searxng_locale ==
'all' and self.
all_locale is not None:
118 return locales.get_engine_locale(searxng_locale, self.
regions, default=default)
121 """A *locale* (SearXNG's internal representation) is considered to be
122 supported by the engine if the *region* or the *language* is supported
125 For verification the functions :py:func:`EngineTraits.get_region` and
126 :py:func:`EngineTraits.get_language` are used.
131 raise TypeError(
'engine traits of type %s is unknown' % self.
data_type)
134 """Create a copy of the dataclass object."""
138 def fetch_traits(cls, engine:
"Engine | types.ModuleType") ->
"EngineTraits | None":
139 """Call a function ``fetch_traits(engine_traits)`` from engines namespace to fetch
140 and set properties from the origin engine in the object ``engine_traits``. If
141 function does not exists, ``None`` is returned.
144 fetch_traits = getattr(engine,
'fetch_traits',
None)
148 engine_traits = cls()
153 """Set traits from self object in a :py:obj:`.Engine` namespace.
155 :param engine: engine instance build by :py:func:`searx.engines.load_engine`
161 raise TypeError(
'engine traits of type %s is unknown' % self.
data_type)
174 _msg =
"settings.yml - engine: '%s' / %s: '%s' not supported"
176 languages = traits.languages
177 if hasattr(engine,
'language'):
178 if engine.language
not in languages:
179 raise ValueError(_msg % (engine.name,
'language', engine.language))
180 traits.languages = {engine.language: languages[engine.language]}
182 regions = traits.regions
183 if hasattr(engine,
'region'):
184 if engine.region
not in regions:
185 raise ValueError(_msg % (engine.name,
'region', engine.region))
186 traits.regions = {engine.region: regions[engine.region]}
188 engine.language_support = bool(traits.languages
or traits.regions)
191 engine.traits = traits
195 """A python dictionary to map :class:`EngineTraits` by engine name."""
197 ENGINE_TRAITS_FILE: pathlib.Path = (data_dir /
'engine_traits.json').resolve()
198 """File with persistence of the :py:obj:`EngineTraitsMap`."""
201 """Store EngineTraitsMap in in file :py:obj:`self.ENGINE_TRAITS_FILE`"""
203 json.dump(self, f, indent=2, sort_keys=
True, cls=EngineTraitsEncoder)
207 """Instantiate :class:`EngineTraitsMap` object from :py:obj:`ENGINE_TRAITS`"""
209 for k, v
in ENGINE_TRAITS.items():
214 def fetch_traits(cls, log: t.Callable[[str],
None]) ->
'EngineTraitsMap':
215 from searx
import engines
217 names = list(engines.engines)
221 for engine_name
in names:
222 engine: Engine | types.ModuleType = engines.engines[engine_name]
227 traits = EngineTraits.fetch_traits(engine)
228 except Exception
as exc:
229 log(
"FATAL: while fetch_traits %s: %s" % (engine_name, exc))
230 if os.environ.get(
'FORCE',
'').lower()
not in [
'on',
'true',
'1']:
232 v = ENGINE_TRAITS.get(engine_name)
234 log(
"FORCE: re-use old values from fetch_traits - ENGINE_TRAITS[%s]" % engine_name)
237 if traits
is not None:
238 log(
"%-20s: SearXNG languages --> %s " % (engine_name, len(traits.languages)))
239 log(
"%-20s: SearXNG regions --> %s" % (engine_name, len(traits.regions)))
240 obj[engine_name] = traits
245 """Set traits in a :py:obj:`Engine` namespace.
247 :param engine: engine instance build by :py:func:`searx.engines.load_engine`
251 if engine.name
in self.keys():
252 engine_traits = self[engine.name]
254 elif engine.engine
in self.keys():
261 engine_traits = self[engine.engine]
263 engine_traits.set_traits(engine)
t.Any default(self, t.Any o)
'EngineTraitsMap' fetch_traits(cls, t.Callable[[str], None] log)
set_traits(self, "Engine | types.ModuleType" engine)
'EngineTraitsMap' from_data(cls)
"EngineTraits | None" fetch_traits(cls, "Engine | types.ModuleType" engine)
t.Any get_region(self, str searxng_locale, t.Any default=None)
bool is_locale_supported(self, str searxng_locale)
_set_traits_v1(self, "Engine | types.ModuleType" engine)
get_language(self, str searxng_locale, t.Any default=None)
set_traits(self, "Engine | types.ModuleType" engine)