forked from zaclys/searxng
Merge pull request #110 from searxng/mod-default-settings
[mod] move all default settings into searx.settings_defaults
This commit is contained in:
commit
e3f4a77311
|
@ -1,53 +1,21 @@
|
||||||
'''
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
searx is free software: you can redistribute it and/or modify
|
# lint: pylint
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
# pylint: disable=missing-function-docstring, missing-module-docstring
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
searx is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with searx. If not, see < http://www.gnu.org/licenses/ >.
|
|
||||||
|
|
||||||
(C) 2013- by Adam Tauber, <asciimoo@gmail.com>
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
from os.path import dirname, abspath
|
||||||
import logging
|
import logging
|
||||||
import searx.settings_loader
|
|
||||||
from os import environ
|
|
||||||
from os.path import realpath, dirname, join, abspath, isfile
|
|
||||||
|
|
||||||
|
import searx.settings_loader
|
||||||
|
from searx.settings_defaults import settings_set_defaults
|
||||||
|
|
||||||
searx_dir = abspath(dirname(__file__))
|
searx_dir = abspath(dirname(__file__))
|
||||||
searx_parent_dir = abspath(dirname(dirname(__file__)))
|
searx_parent_dir = abspath(dirname(dirname(__file__)))
|
||||||
engine_dir = dirname(realpath(__file__))
|
|
||||||
static_path = abspath(join(dirname(__file__), 'static'))
|
|
||||||
settings, settings_load_message = searx.settings_loader.load_settings()
|
settings, settings_load_message = searx.settings_loader.load_settings()
|
||||||
|
|
||||||
if settings['ui']['static_path']:
|
if settings is not None:
|
||||||
static_path = settings['ui']['static_path']
|
settings = settings_set_defaults(settings)
|
||||||
|
|
||||||
'''
|
|
||||||
enable debug if
|
|
||||||
the environnement variable SEARX_DEBUG is 1 or true
|
|
||||||
(whatever the value in settings.yml)
|
|
||||||
or general.debug=True in settings.yml
|
|
||||||
disable debug if
|
|
||||||
the environnement variable SEARX_DEBUG is 0 or false
|
|
||||||
(whatever the value in settings.yml)
|
|
||||||
or general.debug=False in settings.yml
|
|
||||||
'''
|
|
||||||
searx_debug_env = environ.get('SEARX_DEBUG', '').lower()
|
|
||||||
if searx_debug_env == 'true' or searx_debug_env == '1':
|
|
||||||
searx_debug = True
|
|
||||||
elif searx_debug_env == 'false' or searx_debug_env == '0':
|
|
||||||
searx_debug = False
|
|
||||||
else:
|
|
||||||
searx_debug = settings.get('general', {}).get('debug')
|
|
||||||
|
|
||||||
|
searx_debug = settings['general']['debug']
|
||||||
if searx_debug:
|
if searx_debug:
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
else:
|
else:
|
||||||
|
@ -55,15 +23,16 @@ else:
|
||||||
|
|
||||||
logger = logging.getLogger('searx')
|
logger = logging.getLogger('searx')
|
||||||
logger.info(settings_load_message)
|
logger.info(settings_load_message)
|
||||||
logger.info('Initialisation done')
|
|
||||||
|
|
||||||
if 'SEARX_SECRET' in environ:
|
# log max_request_timeout
|
||||||
settings['server']['secret_key'] = environ['SEARX_SECRET']
|
max_request_timeout = settings['outgoing']['max_request_timeout']
|
||||||
if 'SEARX_BIND_ADDRESS' in environ:
|
if max_request_timeout is None:
|
||||||
settings['server']['bind_address'] = environ['SEARX_BIND_ADDRESS']
|
logger.info('max_request_timeout=%s', repr(max_request_timeout))
|
||||||
|
else:
|
||||||
|
logger.info('max_request_timeout=%i second(s)', max_request_timeout)
|
||||||
|
|
||||||
|
|
||||||
class _brand_namespace:
|
class _brand_namespace: # pylint: disable=invalid-name
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_val(cls, group, name, default=''):
|
def get_val(cls, group, name, default=''):
|
||||||
|
|
|
@ -144,7 +144,7 @@ def load_engine(engine_data):
|
||||||
# exclude onion engines if not using tor.
|
# exclude onion engines if not using tor.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
engine.timeout += settings['outgoing'].get('extra_proxy_timeout', 0)
|
engine.timeout += settings['outgoing']['extra_proxy_timeout']
|
||||||
|
|
||||||
for category_name in engine.categories:
|
for category_name in engine.categories:
|
||||||
categories.setdefault(category_name, []).append(engine)
|
categories.setdefault(category_name, []).append(engine)
|
||||||
|
|
|
@ -224,28 +224,22 @@ def initialize(settings_engines=None, settings_outgoing=None):
|
||||||
|
|
||||||
global NETWORKS
|
global NETWORKS
|
||||||
|
|
||||||
settings_engines = settings_engines or settings.get('engines')
|
settings_engines = settings_engines or settings['engines']
|
||||||
settings_outgoing = settings_outgoing or settings.get('outgoing')
|
settings_outgoing = settings_outgoing or settings['outgoing']
|
||||||
|
|
||||||
# default parameters for AsyncHTTPTransport
|
# default parameters for AsyncHTTPTransport
|
||||||
# see https://github.com/encode/httpx/blob/e05a5372eb6172287458b37447c30f650047e1b8/httpx/_transports/default.py#L108-L121 # pylint: disable=line-too-long
|
# see https://github.com/encode/httpx/blob/e05a5372eb6172287458b37447c30f650047e1b8/httpx/_transports/default.py#L108-L121 # pylint: disable=line-too-long
|
||||||
default_params = {
|
default_params = {
|
||||||
'enable_http': False,
|
'enable_http': False,
|
||||||
'verify': True,
|
'verify': True,
|
||||||
'enable_http2': settings_outgoing.get('enable_http2', True),
|
'enable_http2': settings_outgoing['enable_http2'],
|
||||||
# Magic number kept from previous code
|
'max_connections': settings_outgoing['pool_connections'],
|
||||||
'max_connections': settings_outgoing.get('pool_connections', 100),
|
'max_keepalive_connections': settings_outgoing['pool_maxsize'],
|
||||||
# Picked from constructor
|
'keepalive_expiry': settings_outgoing['keepalive_expiry'],
|
||||||
'max_keepalive_connections': settings_outgoing.get('pool_maxsize', 10),
|
'local_addresses': settings_outgoing['source_ips'],
|
||||||
#
|
'proxies': settings_outgoing['proxies'],
|
||||||
'keepalive_expiry': settings_outgoing.get('keepalive_expiry', 5.0),
|
'max_redirects': settings_outgoing['max_redirects'],
|
||||||
'local_addresses': settings_outgoing.get('source_ips'),
|
'retries': settings_outgoing['retries'],
|
||||||
'proxies': settings_outgoing.get('proxies'),
|
|
||||||
# default maximum redirect
|
|
||||||
# from https://github.com/psf/requests/blob/8c211a96cdbe9fe320d63d9e1ae15c5c07e179f8/requests/models.py#L55
|
|
||||||
'max_redirects': settings_outgoing.get('max_redirects', 30),
|
|
||||||
#
|
|
||||||
'retries': settings_outgoing.get('retries', 0),
|
|
||||||
'retry_on_http_error': None,
|
'retry_on_http_error': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,7 +268,7 @@ def initialize(settings_engines=None, settings_outgoing=None):
|
||||||
NETWORKS['ipv6'] = new_network({'local_addresses': '::'})
|
NETWORKS['ipv6'] = new_network({'local_addresses': '::'})
|
||||||
|
|
||||||
# define networks from outgoing.networks
|
# define networks from outgoing.networks
|
||||||
for network_name, network in settings_outgoing.get('networks', {}).items():
|
for network_name, network in settings_outgoing['networks'].items():
|
||||||
NETWORKS[network_name] = new_network(network)
|
NETWORKS[network_name] = new_network(network)
|
||||||
|
|
||||||
# define networks from engines.[i].network (except references)
|
# define networks from engines.[i].network (except references)
|
||||||
|
|
|
@ -21,7 +21,7 @@ from os import listdir, makedirs, remove, stat, utime
|
||||||
from os.path import abspath, basename, dirname, exists, join
|
from os.path import abspath, basename, dirname, exists, join
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
|
|
||||||
from searx import logger, settings, static_path
|
from searx import logger, settings
|
||||||
|
|
||||||
|
|
||||||
logger = logger.getChild('plugins')
|
logger = logger.getChild('plugins')
|
||||||
|
@ -123,7 +123,7 @@ def sync_resource(base_path, resource_path, name, target_dir, plugin_dir):
|
||||||
|
|
||||||
def prepare_package_resources(pkg, name):
|
def prepare_package_resources(pkg, name):
|
||||||
plugin_dir = 'plugin_' + name
|
plugin_dir = 'plugin_' + name
|
||||||
target_dir = join(static_path, 'plugins/external_plugins', plugin_dir)
|
target_dir = join(settings['ui']['static_path'], 'plugins/external_plugins', plugin_dir)
|
||||||
try:
|
try:
|
||||||
makedirs(target_dir, exist_ok=True)
|
makedirs(target_dir, exist_ok=True)
|
||||||
except:
|
except:
|
||||||
|
@ -170,10 +170,10 @@ plugins.register(search_on_category_select)
|
||||||
plugins.register(tracker_url_remover)
|
plugins.register(tracker_url_remover)
|
||||||
plugins.register(vim_hotkeys)
|
plugins.register(vim_hotkeys)
|
||||||
# load external plugins
|
# load external plugins
|
||||||
if 'plugins' in settings:
|
if settings['plugins']:
|
||||||
plugins.register(*settings['plugins'], external=True)
|
plugins.register(*settings['plugins'], external=True)
|
||||||
|
|
||||||
if 'enabled_plugins' in settings:
|
if settings['enabled_plugins']:
|
||||||
for plugin in plugins:
|
for plugin in plugins:
|
||||||
if plugin.name in settings['enabled_plugins']:
|
if plugin.name in settings['enabled_plugins']:
|
||||||
plugin.default_on = True
|
plugin.default_on = True
|
||||||
|
@ -181,5 +181,5 @@ if 'enabled_plugins' in settings:
|
||||||
plugin.default_on = False
|
plugin.default_on = False
|
||||||
|
|
||||||
# load tor specific plugins
|
# load tor specific plugins
|
||||||
if settings['outgoing'].get('using_tor_proxy'):
|
if settings['outgoing']['using_tor_proxy']:
|
||||||
plugins.register(ahmia_filter)
|
plugins.register(ahmia_filter)
|
||||||
|
|
|
@ -333,25 +333,25 @@ class Preferences:
|
||||||
choices=categories + ['none']
|
choices=categories + ['none']
|
||||||
),
|
),
|
||||||
'language': SearchLanguageSetting(
|
'language': SearchLanguageSetting(
|
||||||
settings['search'].get('default_lang', ''),
|
settings['search']['default_lang'],
|
||||||
is_locked('language'),
|
is_locked('language'),
|
||||||
choices=list(LANGUAGE_CODES) + ['']
|
choices=list(LANGUAGE_CODES) + ['']
|
||||||
),
|
),
|
||||||
'locale': EnumStringSetting(
|
'locale': EnumStringSetting(
|
||||||
settings['ui'].get('default_locale', ''),
|
settings['ui']['default_locale'],
|
||||||
is_locked('locale'),
|
is_locked('locale'),
|
||||||
choices=list(settings['locales'].keys()) + ['']
|
choices=list(settings['locales'].keys()) + ['']
|
||||||
),
|
),
|
||||||
'autocomplete': EnumStringSetting(
|
'autocomplete': EnumStringSetting(
|
||||||
settings['search'].get('autocomplete', ''),
|
settings['search']['autocomplete'],
|
||||||
is_locked('autocomplete'),
|
is_locked('autocomplete'),
|
||||||
choices=list(autocomplete.backends.keys()) + ['']
|
choices=list(autocomplete.backends.keys()) + ['']
|
||||||
),
|
),
|
||||||
'image_proxy': MapSetting(
|
'image_proxy': MapSetting(
|
||||||
settings['server'].get('image_proxy', False),
|
settings['server']['image_proxy'],
|
||||||
is_locked('image_proxy'),
|
is_locked('image_proxy'),
|
||||||
map={
|
map={
|
||||||
'': settings['server'].get('image_proxy', 0),
|
'': settings['server']['image_proxy'],
|
||||||
'0': False,
|
'0': False,
|
||||||
'1': True,
|
'1': True,
|
||||||
'True': True,
|
'True': True,
|
||||||
|
@ -359,12 +359,12 @@ class Preferences:
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
'method': EnumStringSetting(
|
'method': EnumStringSetting(
|
||||||
settings['server'].get('method', 'POST'),
|
settings['server']['method'],
|
||||||
is_locked('method'),
|
is_locked('method'),
|
||||||
choices=('GET', 'POST')
|
choices=('GET', 'POST')
|
||||||
),
|
),
|
||||||
'safesearch': MapSetting(
|
'safesearch': MapSetting(
|
||||||
settings['search'].get('safe_search', 0),
|
settings['search']['safe_search'],
|
||||||
is_locked('safesearch'),
|
is_locked('safesearch'),
|
||||||
map={
|
map={
|
||||||
'0': 0,
|
'0': 0,
|
||||||
|
@ -373,12 +373,12 @@ class Preferences:
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
'theme': EnumStringSetting(
|
'theme': EnumStringSetting(
|
||||||
settings['ui'].get('default_theme', 'oscar'),
|
settings['ui']['default_theme'],
|
||||||
is_locked('theme'),
|
is_locked('theme'),
|
||||||
choices=themes
|
choices=themes
|
||||||
),
|
),
|
||||||
'results_on_new_tab': MapSetting(
|
'results_on_new_tab': MapSetting(
|
||||||
settings['ui'].get('results_on_new_tab', False),
|
settings['ui']['results_on_new_tab'],
|
||||||
is_locked('results_on_new_tab'),
|
is_locked('results_on_new_tab'),
|
||||||
map={
|
map={
|
||||||
'0': False,
|
'0': False,
|
||||||
|
@ -393,11 +393,11 @@ class Preferences:
|
||||||
choices=DOI_RESOLVERS
|
choices=DOI_RESOLVERS
|
||||||
),
|
),
|
||||||
'oscar-style': EnumStringSetting(
|
'oscar-style': EnumStringSetting(
|
||||||
settings['ui'].get('theme_args', {}).get('oscar_style', 'logicodev'),
|
settings['ui']['theme_args']['oscar_style'],
|
||||||
is_locked('oscar-style'),
|
is_locked('oscar-style'),
|
||||||
choices=['', 'logicodev', 'logicodev-dark', 'pointhi']),
|
choices=['', 'logicodev', 'logicodev-dark', 'pointhi']),
|
||||||
'advanced_search': MapSetting(
|
'advanced_search': MapSetting(
|
||||||
settings['ui'].get('advanced_search', False),
|
settings['ui']['advanced_search'],
|
||||||
is_locked('advanced_search'),
|
is_locked('advanced_search'),
|
||||||
map={
|
map={
|
||||||
'0': False,
|
'0': False,
|
||||||
|
|
|
@ -23,17 +23,6 @@ from searx.search.checker import initialize as initialize_checker
|
||||||
|
|
||||||
logger = logger.getChild('search')
|
logger = logger.getChild('search')
|
||||||
|
|
||||||
max_request_timeout = settings.get('outgoing', {}).get('max_request_timeout' or None)
|
|
||||||
if max_request_timeout is None:
|
|
||||||
logger.info('max_request_timeout={0}'.format(max_request_timeout))
|
|
||||||
else:
|
|
||||||
if isinstance(max_request_timeout, float):
|
|
||||||
logger.info('max_request_timeout={0} second(s)'.format(max_request_timeout))
|
|
||||||
else:
|
|
||||||
logger.critical('outgoing.max_request_timeout if defined has to be float')
|
|
||||||
import sys
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def initialize(settings_engines=None, enable_checker=False):
|
def initialize(settings_engines=None, enable_checker=False):
|
||||||
settings_engines = settings_engines or settings['engines']
|
settings_engines = settings_engines or settings['engines']
|
||||||
|
@ -115,6 +104,7 @@ class Search:
|
||||||
default_timeout = max(default_timeout, processor.engine.timeout)
|
default_timeout = max(default_timeout, processor.engine.timeout)
|
||||||
|
|
||||||
# adjust timeout
|
# adjust timeout
|
||||||
|
max_request_timeout = settings['outgoing']['max_request_timeout']
|
||||||
actual_timeout = default_timeout
|
actual_timeout = default_timeout
|
||||||
query_timeout = self.search_query.timeout_limit
|
query_timeout = self.search_query.timeout_limit
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
# lint: pylint
|
||||||
|
# pylint: disable=missing-function-docstring
|
||||||
|
"""Implementation of the default settings.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import typing
|
||||||
|
import numbers
|
||||||
|
import errno
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from os.path import dirname, abspath
|
||||||
|
|
||||||
|
from searx.languages import language_codes as languages
|
||||||
|
|
||||||
|
searx_dir = abspath(dirname(__file__))
|
||||||
|
|
||||||
|
logger = logging.getLogger('searx')
|
||||||
|
OUTPUT_FORMATS = ['html', 'csv', 'json', 'rss']
|
||||||
|
LANGUAGE_CODES = ('', 'all') + tuple(l[0] for l in languages)
|
||||||
|
OSCAR_STYLE = ('logicodev', 'logicodev-dark', 'pointhi')
|
||||||
|
CATEGORY_ORDER = [
|
||||||
|
'general',
|
||||||
|
'images',
|
||||||
|
'videos',
|
||||||
|
'news',
|
||||||
|
'map',
|
||||||
|
'music',
|
||||||
|
'it',
|
||||||
|
'science',
|
||||||
|
'files',
|
||||||
|
'social medias',
|
||||||
|
]
|
||||||
|
STR_TO_BOOL = {
|
||||||
|
'0': False,
|
||||||
|
'false': False,
|
||||||
|
'off': False,
|
||||||
|
'1': True,
|
||||||
|
'true': True,
|
||||||
|
'on': True,
|
||||||
|
}
|
||||||
|
_UNDEFINED = object()
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsValue:
|
||||||
|
"""Check and update a setting value
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
type_definition: typing.Union[None, typing.Any, typing.Tuple[typing.Any]]=None,
|
||||||
|
default: typing.Any=None,
|
||||||
|
environ_name: str=None):
|
||||||
|
self.type_definition = (
|
||||||
|
type_definition
|
||||||
|
if type_definition is None or isinstance(type_definition, tuple)
|
||||||
|
else (type_definition,)
|
||||||
|
)
|
||||||
|
self.default = default
|
||||||
|
self.environ_name = environ_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_definition_repr(self):
|
||||||
|
types_str = [
|
||||||
|
t.__name__ if isinstance(t, type) else repr(t)
|
||||||
|
for t in self.type_definition
|
||||||
|
]
|
||||||
|
return ', '.join(types_str)
|
||||||
|
|
||||||
|
def check_type_definition(self, value: typing.Any) -> None:
|
||||||
|
if value in self.type_definition:
|
||||||
|
return
|
||||||
|
type_list = tuple(t for t in self.type_definition if isinstance(t, type))
|
||||||
|
if not isinstance(value, type_list):
|
||||||
|
raise ValueError(
|
||||||
|
'The value has to be one of these types/values: {}'.format(
|
||||||
|
self.type_definition_repr))
|
||||||
|
|
||||||
|
def __call__(self, value: typing.Any) -> typing.Any:
|
||||||
|
if value == _UNDEFINED:
|
||||||
|
value = self.default
|
||||||
|
# override existing value with environ
|
||||||
|
if self.environ_name and self.environ_name in os.environ:
|
||||||
|
value = os.environ[self.environ_name]
|
||||||
|
if self.type_definition == (bool,):
|
||||||
|
value = STR_TO_BOOL[value.lower()]
|
||||||
|
|
||||||
|
self.check_type_definition(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsDirectoryValue(SettingsValue):
|
||||||
|
"""Check and update a setting value that is a directory path
|
||||||
|
"""
|
||||||
|
|
||||||
|
def check_type_definition(self, value: typing.Any) -> typing.Any:
|
||||||
|
super().check_type_definition(value)
|
||||||
|
if not os.path.isdir(value):
|
||||||
|
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), value)
|
||||||
|
|
||||||
|
def __call__(self, value: typing.Any) -> typing.Any:
|
||||||
|
if value == '':
|
||||||
|
value = self.default
|
||||||
|
return super().__call__(value)
|
||||||
|
|
||||||
|
|
||||||
|
def apply_schema(settings, schema, path_list):
|
||||||
|
error = False
|
||||||
|
for key, value in schema.items():
|
||||||
|
if isinstance(value, SettingsValue):
|
||||||
|
try:
|
||||||
|
settings[key] = value(settings.get(key, _UNDEFINED))
|
||||||
|
except Exception as e: # pylint: disable=broad-except
|
||||||
|
# don't stop now: check other values
|
||||||
|
logger.error('%s: %s', '.'.join([*path_list, key]), e)
|
||||||
|
error = True
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
error = error or apply_schema(settings.setdefault(key, {}), schema[key], [*path_list, key])
|
||||||
|
else:
|
||||||
|
settings.setdefault(key, value)
|
||||||
|
if len(path_list) == 0 and error:
|
||||||
|
raise ValueError('Invalid settings.yml')
|
||||||
|
return error
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA = {
|
||||||
|
'general': {
|
||||||
|
'debug': SettingsValue(bool, False, 'SEARX_DEBUG'),
|
||||||
|
'instance_name': SettingsValue(str, 'searxng'),
|
||||||
|
'contact_url': SettingsValue((None, False, str), None),
|
||||||
|
},
|
||||||
|
'brand': {
|
||||||
|
},
|
||||||
|
'search': {
|
||||||
|
'safe_search': SettingsValue((0,1,2), 0),
|
||||||
|
'autocomplete': SettingsValue(str, ''),
|
||||||
|
'default_lang': SettingsValue(LANGUAGE_CODES, ''),
|
||||||
|
'ban_time_on_fail': SettingsValue(numbers.Real, 5),
|
||||||
|
'max_ban_time_on_fail': SettingsValue(numbers.Real, 120),
|
||||||
|
'formats': SettingsValue(list, OUTPUT_FORMATS),
|
||||||
|
},
|
||||||
|
'server': {
|
||||||
|
'port': SettingsValue(int, 8888),
|
||||||
|
'bind_address': SettingsValue(str, '127.0.0.1', 'SEARX_BIND_ADDRESS'),
|
||||||
|
'secret_key': SettingsValue(str, environ_name='SEARX_SECRET'),
|
||||||
|
'base_url': SettingsValue((False, str), False),
|
||||||
|
'image_proxy': SettingsValue(bool, False),
|
||||||
|
'http_protocol_version': SettingsValue(('1.0', '1.1'), '1.0'),
|
||||||
|
'method': SettingsValue(('POST', 'GET'), 'POST'),
|
||||||
|
'default_http_headers': SettingsValue(dict, {}),
|
||||||
|
},
|
||||||
|
'ui': {
|
||||||
|
'static_path': SettingsDirectoryValue(str, os.path.join(searx_dir, 'static')),
|
||||||
|
'templates_path': SettingsDirectoryValue(str, os.path.join(searx_dir, 'templates')),
|
||||||
|
'default_theme': SettingsValue(str, 'oscar'),
|
||||||
|
'default_locale': SettingsValue(str, ''),
|
||||||
|
'theme_args': {
|
||||||
|
'oscar_style': SettingsValue(OSCAR_STYLE, 'logicodev'),
|
||||||
|
},
|
||||||
|
'results_on_new_tab': SettingsValue(bool, False),
|
||||||
|
'advanced_search': SettingsValue(bool, False),
|
||||||
|
'categories_order': SettingsValue(list, CATEGORY_ORDER),
|
||||||
|
},
|
||||||
|
'preferences': {
|
||||||
|
'lock': SettingsValue(list, []),
|
||||||
|
},
|
||||||
|
'outgoing': {
|
||||||
|
'useragent_suffix': SettingsValue(str, ''),
|
||||||
|
'request_timeout': SettingsValue(numbers.Real, 3.0),
|
||||||
|
'enable_http2': SettingsValue(bool, True),
|
||||||
|
'max_request_timeout': SettingsValue((None, numbers.Real), None),
|
||||||
|
# Magic number kept from previous code
|
||||||
|
'pool_connections': SettingsValue(int, 100),
|
||||||
|
# Picked from constructor
|
||||||
|
'pool_maxsize': SettingsValue(int, 10),
|
||||||
|
'keepalive_expiry': SettingsValue(numbers.Real, 5.0),
|
||||||
|
# default maximum redirect
|
||||||
|
# from https://github.com/psf/requests/blob/8c211a96cdbe9fe320d63d9e1ae15c5c07e179f8/requests/models.py#L55
|
||||||
|
'max_redirects': SettingsValue(int, 30),
|
||||||
|
'retries': SettingsValue(int, 0),
|
||||||
|
'proxies': SettingsValue((None, str, dict), None),
|
||||||
|
'source_ips': SettingsValue((None, str, list), None),
|
||||||
|
# Tor configuration
|
||||||
|
'using_tor_proxy': SettingsValue(bool, False),
|
||||||
|
'extra_proxy_timeout': SettingsValue(int, 0),
|
||||||
|
'networks': {
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'plugins': SettingsValue((None, list), None),
|
||||||
|
'enabled_plugins': SettingsValue(list, []),
|
||||||
|
'checker': {
|
||||||
|
'off_when_debug': SettingsValue(bool, True),
|
||||||
|
},
|
||||||
|
'engines': SettingsValue(list, []),
|
||||||
|
'locales': SettingsValue(dict, {'en': 'English'}),
|
||||||
|
'doi_resolvers': {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def settings_set_defaults(settings):
|
||||||
|
apply_schema(settings, SCHEMA, [])
|
||||||
|
return settings
|
|
@ -8,7 +8,6 @@ from os.path import splitext, join
|
||||||
from random import choice
|
from random import choice
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
from urllib.parse import urljoin, urlparse
|
from urllib.parse import urljoin, urlparse
|
||||||
from collections.abc import Mapping
|
|
||||||
|
|
||||||
from lxml import html
|
from lxml import html
|
||||||
from lxml.etree import ElementBase, XPath, XPathError, XPathSyntaxError, _ElementStringResult, _ElementUnicodeResult
|
from lxml.etree import ElementBase, XPath, XPathError, XPathSyntaxError, _ElementStringResult, _ElementUnicodeResult
|
||||||
|
@ -46,7 +45,7 @@ def searx_useragent():
|
||||||
"""Return the searx User Agent"""
|
"""Return the searx User Agent"""
|
||||||
return 'searx/{searx_version} {suffix}'.format(
|
return 'searx/{searx_version} {suffix}'.format(
|
||||||
searx_version=VERSION_STRING,
|
searx_version=VERSION_STRING,
|
||||||
suffix=settings['outgoing'].get('useragent_suffix', '')).strip()
|
suffix=settings['outgoing']['useragent_suffix'].strip())
|
||||||
|
|
||||||
|
|
||||||
def gen_useragent(os=None):
|
def gen_useragent(os=None):
|
||||||
|
@ -501,58 +500,6 @@ def get_engine_from_settings(name):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
NOT_EXISTS = object()
|
|
||||||
"""Singleton used by :py:obj:`get_value` if a key does not exists."""
|
|
||||||
|
|
||||||
|
|
||||||
def get_value(dictionary, *keys, default=NOT_EXISTS):
|
|
||||||
"""Return the value from a *deep* mapping type (e.g. the ``settings`` object
|
|
||||||
from yaml). If the path to the *key* does not exists a :py:obj:`NOT_EXISTS`
|
|
||||||
is returned (non ``KeyError`` exception is raised).
|
|
||||||
|
|
||||||
.. code: python
|
|
||||||
|
|
||||||
>>> from searx import settings
|
|
||||||
>>> from searx.utils import get_value, NOT_EXISTS
|
|
||||||
>>> get_value(settings, 'checker', 'additional_tests', 'rosebud', 'result_container')
|
|
||||||
['not_empty', ['one_title_contains', 'citizen kane']]
|
|
||||||
|
|
||||||
>>> get_value(settings, 'search', 'xxx') is NOT_EXISTS
|
|
||||||
True
|
|
||||||
>>> get_value(settings, 'search', 'formats')
|
|
||||||
['html', 'csv', 'json', 'rss']
|
|
||||||
|
|
||||||
The list returned from the ``search.format`` key is not a mapping type, you
|
|
||||||
can't traverse along non-mapping types. If you try it, you will get a
|
|
||||||
:py:ref:`NOT_EXISTS`:
|
|
||||||
|
|
||||||
.. code: python
|
|
||||||
|
|
||||||
>>> get_value(settings, 'search', 'format', 'csv') is NOT_EXISTS
|
|
||||||
True
|
|
||||||
>>> get_value(settings, 'search', 'formats')[1]
|
|
||||||
'csv'
|
|
||||||
|
|
||||||
For convenience you can replace :py:ref:`NOT_EXISTS` by a default value of
|
|
||||||
your choice:
|
|
||||||
|
|
||||||
.. code: python
|
|
||||||
|
|
||||||
if 'csv' in get_value(settings, 'search', 'formats', default=[]):
|
|
||||||
print("csv format is denied")
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
obj = dictionary
|
|
||||||
for k in keys:
|
|
||||||
if not isinstance(obj, Mapping):
|
|
||||||
raise TypeError("expected mapping type, got %s" % type(obj))
|
|
||||||
obj = obj.get(k, default)
|
|
||||||
if obj is default:
|
|
||||||
return obj
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def get_xpath(xpath_spec):
|
def get_xpath(xpath_spec):
|
||||||
"""Return cached compiled XPath
|
"""Return cached compiled XPath
|
||||||
|
|
||||||
|
|
|
@ -56,12 +56,12 @@ from flask_babel import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from searx import logger
|
from searx import logger
|
||||||
from searx import brand, static_path
|
from searx import brand
|
||||||
from searx import (
|
from searx import (
|
||||||
settings,
|
settings,
|
||||||
searx_dir,
|
|
||||||
searx_debug,
|
searx_debug,
|
||||||
)
|
)
|
||||||
|
from searx.settings_defaults import OUTPUT_FORMATS
|
||||||
from searx.exceptions import SearxParameterException
|
from searx.exceptions import SearxParameterException
|
||||||
from searx.engines import (
|
from searx.engines import (
|
||||||
categories,
|
categories,
|
||||||
|
@ -71,7 +71,6 @@ from searx.engines import (
|
||||||
from searx.webutils import (
|
from searx.webutils import (
|
||||||
UnicodeWriter,
|
UnicodeWriter,
|
||||||
highlight_content,
|
highlight_content,
|
||||||
get_resources_directory,
|
|
||||||
get_static_files,
|
get_static_files,
|
||||||
get_result_templates,
|
get_result_templates,
|
||||||
get_themes,
|
get_themes,
|
||||||
|
@ -88,7 +87,6 @@ from searx.utils import (
|
||||||
gen_useragent,
|
gen_useragent,
|
||||||
dict_subset,
|
dict_subset,
|
||||||
match_language,
|
match_language,
|
||||||
get_value,
|
|
||||||
)
|
)
|
||||||
from searx.version import VERSION_STRING
|
from searx.version import VERSION_STRING
|
||||||
from searx.query import RawTextQuery
|
from searx.query import RawTextQuery
|
||||||
|
@ -139,7 +137,7 @@ if sys.version_info[0] < 3:
|
||||||
logger = logger.getChild('webapp')
|
logger = logger.getChild('webapp')
|
||||||
|
|
||||||
# serve pages with HTTP/1.1
|
# serve pages with HTTP/1.1
|
||||||
WSGIRequestHandler.protocol_version = "HTTP/{}".format(settings['server'].get('http_protocol_version', '1.0'))
|
WSGIRequestHandler.protocol_version = "HTTP/{}".format(settings['server']['http_protocol_version'])
|
||||||
|
|
||||||
# check secret_key
|
# check secret_key
|
||||||
if not searx_debug and settings['server']['secret_key'] == 'ultrasecretkey':
|
if not searx_debug and settings['server']['secret_key'] == 'ultrasecretkey':
|
||||||
|
@ -147,25 +145,22 @@ if not searx_debug and settings['server']['secret_key'] == 'ultrasecretkey':
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# about static
|
# about static
|
||||||
static_path = get_resources_directory(searx_dir, 'static', settings['ui']['static_path'])
|
logger.debug('static directory is %s', settings['ui']['static_path'])
|
||||||
logger.debug('static directory is %s', static_path)
|
static_files = get_static_files(settings['ui']['static_path'])
|
||||||
static_files = get_static_files(static_path)
|
|
||||||
|
|
||||||
# about templates
|
# about templates
|
||||||
|
logger.debug('templates directory is %s', settings['ui']['templates_path'])
|
||||||
default_theme = settings['ui']['default_theme']
|
default_theme = settings['ui']['default_theme']
|
||||||
templates_path = get_resources_directory(searx_dir, 'templates', settings['ui']['templates_path'])
|
templates_path = settings['ui']['templates_path']
|
||||||
logger.debug('templates directory is %s', templates_path)
|
|
||||||
themes = get_themes(templates_path)
|
themes = get_themes(templates_path)
|
||||||
result_templates = get_result_templates(templates_path)
|
result_templates = get_result_templates(templates_path)
|
||||||
global_favicons = []
|
global_favicons = []
|
||||||
for indice, theme in enumerate(themes):
|
for indice, theme in enumerate(themes):
|
||||||
global_favicons.append([])
|
global_favicons.append([])
|
||||||
theme_img_path = os.path.join(static_path, 'themes', theme, 'img', 'icons')
|
theme_img_path = os.path.join(settings['ui']['static_path'], 'themes', theme, 'img', 'icons')
|
||||||
for (dirpath, dirnames, filenames) in os.walk(theme_img_path):
|
for (dirpath, dirnames, filenames) in os.walk(theme_img_path):
|
||||||
global_favicons[indice].extend(filenames)
|
global_favicons[indice].extend(filenames)
|
||||||
|
|
||||||
OUTPUT_FORMATS = ['html', 'csv', 'json', 'rss']
|
|
||||||
|
|
||||||
STATS_SORT_PARAMETERS = {
|
STATS_SORT_PARAMETERS = {
|
||||||
'name': (False, 'name', ''),
|
'name': (False, 'name', ''),
|
||||||
'score': (True, 'score', 0),
|
'score': (True, 'score', 0),
|
||||||
|
@ -177,7 +172,7 @@ STATS_SORT_PARAMETERS = {
|
||||||
# Flask app
|
# Flask app
|
||||||
app = Flask(
|
app = Flask(
|
||||||
__name__,
|
__name__,
|
||||||
static_folder=static_path,
|
static_folder=settings['ui']['static_path'],
|
||||||
template_folder=templates_path
|
template_folder=templates_path
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -517,8 +512,7 @@ def render(template_name, override_theme=None, **kwargs):
|
||||||
kwargs['preferences'] = request.preferences
|
kwargs['preferences'] = request.preferences
|
||||||
|
|
||||||
kwargs['search_formats'] = [
|
kwargs['search_formats'] = [
|
||||||
x for x in get_value(
|
x for x in settings['search']['formats']
|
||||||
settings, 'search', 'formats', default=OUTPUT_FORMATS)
|
|
||||||
if x != 'html']
|
if x != 'html']
|
||||||
|
|
||||||
kwargs['brand'] = brand
|
kwargs['brand'] = brand
|
||||||
|
@ -545,12 +539,7 @@ def render(template_name, override_theme=None, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
def _get_ordered_categories():
|
def _get_ordered_categories():
|
||||||
ordered_categories = []
|
ordered_categories = list(settings['ui']['categories_order'])
|
||||||
if 'categories_order' not in settings['ui']:
|
|
||||||
ordered_categories = ['general']
|
|
||||||
ordered_categories.extend(x for x in sorted(categories.keys()) if x != 'general')
|
|
||||||
return ordered_categories
|
|
||||||
ordered_categories = settings['ui']['categories_order']
|
|
||||||
ordered_categories.extend(x for x in sorted(categories.keys()) if x not in ordered_categories)
|
ordered_categories.extend(x for x in sorted(categories.keys()) if x not in ordered_categories)
|
||||||
return ordered_categories
|
return ordered_categories
|
||||||
|
|
||||||
|
@ -610,7 +599,7 @@ def pre_request():
|
||||||
@app.after_request
|
@app.after_request
|
||||||
def add_default_headers(response):
|
def add_default_headers(response):
|
||||||
# set default http headers
|
# set default http headers
|
||||||
for header, value in settings['server'].get('default_http_headers', {}).items():
|
for header, value in settings['server']['default_http_headers'].items():
|
||||||
if header in response.headers:
|
if header in response.headers:
|
||||||
continue
|
continue
|
||||||
response.headers[header] = value
|
response.headers[header] = value
|
||||||
|
@ -696,7 +685,7 @@ def search():
|
||||||
if output_format not in OUTPUT_FORMATS:
|
if output_format not in OUTPUT_FORMATS:
|
||||||
output_format = 'html'
|
output_format = 'html'
|
||||||
|
|
||||||
if output_format not in get_value(settings, 'search', 'formats', default=OUTPUT_FORMATS):
|
if output_format not in settings['search']['formats']:
|
||||||
flask.abort(403)
|
flask.abort(403)
|
||||||
|
|
||||||
# check if there is query (not None and not an empty string)
|
# check if there is query (not None and not an empty string)
|
||||||
|
@ -1069,11 +1058,6 @@ def preferences():
|
||||||
'time_range_support': time_range_support,
|
'time_range_support': time_range_support,
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
|
||||||
locked_preferences = list()
|
|
||||||
if 'preferences' in settings and 'lock' in settings['preferences']:
|
|
||||||
locked_preferences = settings['preferences']['lock']
|
|
||||||
|
|
||||||
#
|
#
|
||||||
return render('preferences.html',
|
return render('preferences.html',
|
||||||
selected_categories=get_selected_categories(request.preferences, request.form),
|
selected_categories=get_selected_categories(request.preferences, request.form),
|
||||||
|
@ -1098,7 +1082,7 @@ def preferences():
|
||||||
theme=get_current_theme_name(),
|
theme=get_current_theme_name(),
|
||||||
preferences_url_params=request.preferences.get_as_url_params(),
|
preferences_url_params=request.preferences.get_as_url_params(),
|
||||||
base_url=get_base_url(),
|
base_url=get_base_url(),
|
||||||
locked_preferences=locked_preferences,
|
locked_preferences=settings['preferences']['lock'],
|
||||||
preferences=True)
|
preferences=True)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1271,7 +1255,7 @@ def favicon():
|
||||||
return send_from_directory(
|
return send_from_directory(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
app.root_path,
|
app.root_path,
|
||||||
static_path,
|
settings['ui']['static_path'],
|
||||||
'themes',
|
'themes',
|
||||||
get_current_theme_name(),
|
get_current_theme_name(),
|
||||||
'img'),
|
'img'),
|
||||||
|
|
|
@ -47,14 +47,6 @@ class UnicodeWriter:
|
||||||
self.writerow(row)
|
self.writerow(row)
|
||||||
|
|
||||||
|
|
||||||
def get_resources_directory(searx_directory, subdirectory, resources_directory):
|
|
||||||
if not resources_directory:
|
|
||||||
resources_directory = os.path.join(searx_directory, subdirectory)
|
|
||||||
if not os.path.isdir(resources_directory):
|
|
||||||
raise Exception(resources_directory + " is not a directory")
|
|
||||||
return resources_directory
|
|
||||||
|
|
||||||
|
|
||||||
def get_themes(templates_path):
|
def get_themes(templates_path):
|
||||||
"""Returns available themes list."""
|
"""Returns available themes list."""
|
||||||
themes = os.listdir(templates_path)
|
themes = os.listdir(templates_path)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
from searx.testing import SearxTestCase
|
from searx.testing import SearxTestCase
|
||||||
from searx.search import SearchQuery, EngineRef
|
from searx.search import SearchQuery, EngineRef
|
||||||
|
from searx import settings
|
||||||
import searx.search
|
import searx.search
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ class SearchTestCase(SearxTestCase):
|
||||||
searx.search.initialize(TEST_ENGINES)
|
searx.search.initialize(TEST_ENGINES)
|
||||||
|
|
||||||
def test_timeout_simple(self):
|
def test_timeout_simple(self):
|
||||||
searx.search.max_request_timeout = None
|
settings['outgoing']['max_request_timeout'] = None
|
||||||
search_query = SearchQuery('test', [EngineRef(PUBLIC_ENGINE_NAME, 'general')],
|
search_query = SearchQuery('test', [EngineRef(PUBLIC_ENGINE_NAME, 'general')],
|
||||||
'en-US', SAFESEARCH, PAGENO, None, None)
|
'en-US', SAFESEARCH, PAGENO, None, None)
|
||||||
search = searx.search.Search(search_query)
|
search = searx.search.Search(search_query)
|
||||||
|
@ -49,7 +50,7 @@ class SearchTestCase(SearxTestCase):
|
||||||
self.assertEqual(search.actual_timeout, 3.0)
|
self.assertEqual(search.actual_timeout, 3.0)
|
||||||
|
|
||||||
def test_timeout_query_above_default_nomax(self):
|
def test_timeout_query_above_default_nomax(self):
|
||||||
searx.search.max_request_timeout = None
|
settings['outgoing']['max_request_timeout'] = None
|
||||||
search_query = SearchQuery('test', [EngineRef(PUBLIC_ENGINE_NAME, 'general')],
|
search_query = SearchQuery('test', [EngineRef(PUBLIC_ENGINE_NAME, 'general')],
|
||||||
'en-US', SAFESEARCH, PAGENO, None, 5.0)
|
'en-US', SAFESEARCH, PAGENO, None, 5.0)
|
||||||
search = searx.search.Search(search_query)
|
search = searx.search.Search(search_query)
|
||||||
|
@ -57,7 +58,7 @@ class SearchTestCase(SearxTestCase):
|
||||||
self.assertEqual(search.actual_timeout, 3.0)
|
self.assertEqual(search.actual_timeout, 3.0)
|
||||||
|
|
||||||
def test_timeout_query_below_default_nomax(self):
|
def test_timeout_query_below_default_nomax(self):
|
||||||
searx.search.max_request_timeout = None
|
settings['outgoing']['max_request_timeout'] = None
|
||||||
search_query = SearchQuery('test', [EngineRef(PUBLIC_ENGINE_NAME, 'general')],
|
search_query = SearchQuery('test', [EngineRef(PUBLIC_ENGINE_NAME, 'general')],
|
||||||
'en-US', SAFESEARCH, PAGENO, None, 1.0)
|
'en-US', SAFESEARCH, PAGENO, None, 1.0)
|
||||||
search = searx.search.Search(search_query)
|
search = searx.search.Search(search_query)
|
||||||
|
@ -65,7 +66,7 @@ class SearchTestCase(SearxTestCase):
|
||||||
self.assertEqual(search.actual_timeout, 1.0)
|
self.assertEqual(search.actual_timeout, 1.0)
|
||||||
|
|
||||||
def test_timeout_query_below_max(self):
|
def test_timeout_query_below_max(self):
|
||||||
searx.search.max_request_timeout = 10.0
|
settings['outgoing']['max_request_timeout'] = 10.0
|
||||||
search_query = SearchQuery('test', [EngineRef(PUBLIC_ENGINE_NAME, 'general')],
|
search_query = SearchQuery('test', [EngineRef(PUBLIC_ENGINE_NAME, 'general')],
|
||||||
'en-US', SAFESEARCH, PAGENO, None, 5.0)
|
'en-US', SAFESEARCH, PAGENO, None, 5.0)
|
||||||
search = searx.search.Search(search_query)
|
search = searx.search.Search(search_query)
|
||||||
|
@ -73,7 +74,7 @@ class SearchTestCase(SearxTestCase):
|
||||||
self.assertEqual(search.actual_timeout, 5.0)
|
self.assertEqual(search.actual_timeout, 5.0)
|
||||||
|
|
||||||
def test_timeout_query_above_max(self):
|
def test_timeout_query_above_max(self):
|
||||||
searx.search.max_request_timeout = 10.0
|
settings['outgoing']['max_request_timeout'] = 10.0
|
||||||
search_query = SearchQuery('test', [EngineRef(PUBLIC_ENGINE_NAME, 'general')],
|
search_query = SearchQuery('test', [EngineRef(PUBLIC_ENGINE_NAME, 'general')],
|
||||||
'en-US', SAFESEARCH, PAGENO, None, 15.0)
|
'en-US', SAFESEARCH, PAGENO, None, 15.0)
|
||||||
search = searx.search.Search(search_query)
|
search = searx.search.Search(search_query)
|
||||||
|
|
Loading…
Reference in New Issue