Browse Source

[mod] move all default settings into searx.settings_defaults

Alexandre Flament 3 years ago
parent
commit
4b07df62e5

+ 11 - 29
searx/__init__.py

@@ -17,37 +17,18 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >.
 
 
 import logging
 import logging
 import searx.settings_loader
 import searx.settings_loader
-from os import environ
-from os.path import realpath, dirname, join, abspath, isfile
+from searx.settings_defaults import settings_set_defaults
+from os.path import dirname, abspath
 
 
 
 
 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']:
-    static_path = settings['ui']['static_path']
-
-'''
-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')
+if settings is not None:
+    settings = settings_set_defaults(settings)
 
 
+searx_debug = settings['general']['debug']
 if searx_debug:
 if searx_debug:
     logging.basicConfig(level=logging.DEBUG)
     logging.basicConfig(level=logging.DEBUG)
 else:
 else:
@@ -55,12 +36,13 @@ 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:
-    settings['server']['secret_key'] = environ['SEARX_SECRET']
-if 'SEARX_BIND_ADDRESS' in environ:
-    settings['server']['bind_address'] = environ['SEARX_BIND_ADDRESS']
+# log max_request_timeout
+max_request_timeout = settings['outgoing']['max_request_timeout']
+if max_request_timeout is None:
+    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:

+ 1 - 1
searx/engines/__init__.py

@@ -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)

+ 11 - 17
searx/network/network.py

@@ -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_outgoing = settings_outgoing or settings.get('outgoing')
+    settings_engines = settings_engines or settings['engines']
+    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),
-        # Magic number kept from previous code
-        'max_connections': settings_outgoing.get('pool_connections', 100),
-        # Picked from constructor
-        'max_keepalive_connections': settings_outgoing.get('pool_maxsize', 10),
-        #
-        'keepalive_expiry': settings_outgoing.get('keepalive_expiry', 5.0),
-        'local_addresses': settings_outgoing.get('source_ips'),
-        '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),
+        'enable_http2': settings_outgoing['enable_http2'],
+        'max_connections': settings_outgoing['pool_connections'],
+        'max_keepalive_connections': settings_outgoing['pool_maxsize'],
+        'keepalive_expiry': settings_outgoing['keepalive_expiry'],
+        'local_addresses': settings_outgoing['source_ips'],
+        'proxies': settings_outgoing['proxies'],
+        'max_redirects': settings_outgoing['max_redirects'],
+        'retries': settings_outgoing['retries'],
         '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)

+ 5 - 5
searx/plugins/__init__.py

@@ -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)

+ 11 - 11
searx/preferences.py

@@ -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,

+ 194 - 0
searx/settings_defaults.py

@@ -0,0 +1,194 @@
+# SPDX-License-Identifier: AGPL-3.0-or-later
+# lint: pylint
+# pylint: disable=missing-function-docstring, missing-module-docstring
+
+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

+ 1 - 54
searx/utils.py

@@ -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
 
 

+ 15 - 31
searx/webapp.py

@@ -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', static_path)
-static_files = get_static_files(static_path)
+logger.debug('static directory is %s', settings['ui']['static_path'])
+static_files = get_static_files(settings['ui']['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'])
-logger.debug('templates directory is %s', templates_path)
+templates_path = settings['ui']['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(
-            settings, 'search', 'formats', default=OUTPUT_FORMATS)
+        x for x in settings['search']['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 = []
-    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 = list(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'),

+ 0 - 8
searx/webutils.py

@@ -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)