diff --git a/swh/storage/api/server.py b/swh/storage/api/server.py
index d72723fb..38032aaa 100644
--- a/swh/storage/api/server.py
+++ b/swh/storage/api/server.py
@@ -1,129 +1,128 @@
-# Copyright (C) 2015-2019 The Software Heritage developers
+# Copyright (C) 2015-2020 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
import logging
import os
+from typing import Any, Dict
from swh.core import config
from swh.core.api import RPCServerApp
from swh.core.api import encode_data_server as encode_data
from swh.core.api import error_handler
from swh.storage import get_storage as get_swhstorage
from ..exc import StorageArgumentException
from ..interface import StorageInterface
from ..metrics import timed
from .serializers import DECODERS, ENCODERS
def get_storage():
global storage
if not storage:
storage = get_swhstorage(**app.config["storage"])
return storage
class StorageServerApp(RPCServerApp):
extra_type_decoders = DECODERS
extra_type_encoders = ENCODERS
app = StorageServerApp(
__name__, backend_class=StorageInterface, backend_factory=get_storage
)
storage = None
@app.errorhandler(StorageArgumentException)
def argument_error_handler(exception):
return error_handler(exception, encode_data, status_code=400)
@app.errorhandler(Exception)
def my_error_handler(exception):
return error_handler(exception, encode_data)
@app.route("/")
@timed
def index():
return """
Software Heritage storage server
You have reached the
Software Heritage
storage server.
See its
documentation
and API for more information
"""
@app.route("/stat/counters", methods=["GET"])
@timed
def stat_counters():
return encode_data(get_storage().stat_counters())
@app.route("/stat/refresh", methods=["GET"])
@timed
def refresh_stat_counters():
return encode_data(get_storage().refresh_stat_counters())
api_cfg = None
-def load_and_check_config(config_file, type="local"):
+def load_and_check_config(config_path: str) -> Dict[str, Any]:
"""Check the minimal configuration is set to run the api or raise an
error explanation.
Args:
- config_file (str): Path to the configuration file to load
- type (str): configuration type. For 'local' type, more
- checks are done.
+ config_path: Path to the configuration file to load
Raises:
Error if the setup is not as expected
Returns:
configuration as a dict
"""
- if not config_file:
+ if not config_path:
raise EnvironmentError("Configuration file must be defined")
- if not os.path.exists(config_file):
- raise FileNotFoundError("Configuration file %s does not exist" % (config_file,))
+ if not os.path.exists(config_path):
+ raise FileNotFoundError(f"Configuration file {config_path} does not exist")
- cfg = config.read(config_file)
+ cfg = config.read(config_path)
if "storage" not in cfg:
raise KeyError("Missing '%storage' configuration")
return cfg
def make_app_from_configfile():
"""Run the WSGI app from the webserver, loading the configuration from
a configuration file.
SWH_CONFIG_FILENAME environment variable defines the
configuration path to load.
"""
global api_cfg
if not api_cfg:
- config_file = os.environ.get("SWH_CONFIG_FILENAME")
- api_cfg = load_and_check_config(config_file)
+ config_path = os.environ.get("SWH_CONFIG_FILENAME")
+ api_cfg = load_and_check_config(config_path)
app.config.update(api_cfg)
handler = logging.StreamHandler()
app.logger.addHandler(handler)
return app
if __name__ == "__main__":
print("Deprecated. Use swh-storage")
diff --git a/swh/storage/tests/test_server.py b/swh/storage/tests/test_server.py
index b2d0392d..dd446dfe 100644
--- a/swh/storage/tests/test_server.py
+++ b/swh/storage/tests/test_server.py
@@ -1,73 +1,60 @@
# Copyright (C) 2019-2020 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
import pytest
import yaml
from swh.storage.api.server import load_and_check_config
def prepare_config_file(tmpdir, content, name="config.yml"):
"""Prepare configuration file in `$tmpdir/name` with content `content`.
Args:
tmpdir (LocalPath): root directory
content (str/dict): Content of the file either as string or as a dict.
If a dict, converts the dict into a yaml string.
name (str): configuration filename
Returns
path (str) of the configuration file prepared.
"""
config_path = tmpdir / name
if isinstance(content, dict): # convert if needed
content = yaml.dump(content)
config_path.write_text(content, encoding="utf-8")
# pytest on python3.5 does not support LocalPath manipulation, so
# convert path to string
return str(config_path)
-def test_load_and_check_config_no_configuration():
+@pytest.mark.parametrize("storage_class", [None, ""])
+def test_load_and_check_config_no_configuration(storage_class):
"""Inexistent configuration files raises"""
- with pytest.raises(EnvironmentError) as e:
- load_and_check_config(None)
+ with pytest.raises(EnvironmentError, match="Configuration file must be defined"):
+ load_and_check_config(storage_class)
- assert e.value.args[0] == "Configuration file must be defined"
+def test_load_and_check_config_inexistent_file():
config_path = "/some/inexistent/config.yml"
- with pytest.raises(FileNotFoundError) as e:
+ expected_error = f"Configuration file {config_path} does not exist"
+ with pytest.raises(FileNotFoundError, match=expected_error):
load_and_check_config(config_path)
- assert e.value.args[0] == "Configuration file %s does not exist" % (config_path,)
-
def test_load_and_check_config_wrong_configuration(tmpdir):
"""Wrong configuration raises"""
config_path = prepare_config_file(tmpdir, "something: useless")
- with pytest.raises(KeyError) as e:
+ with pytest.raises(KeyError, match="Missing '%storage' configuration"):
load_and_check_config(config_path)
- assert e.value.args[0] == "Missing '%storage' configuration"
-
def test_load_and_check_config_local_config_fine(tmpdir):
- """'Remote configuration is fine"""
- config = {
- "storage": {"cls": "local", "args": {"db": "db", "objstorage": "something",}}
- }
+ """'local' complete configuration is fine"""
+ config = {"storage": {"cls": "local", "db": "db", "objstorage": "something",}}
config_path = prepare_config_file(tmpdir, config)
- cfg = load_and_check_config(config_path, type="local")
- assert cfg == config
-
-
-def test_load_and_check_config_remote_config_fine(tmpdir):
- """'Remote configuration is fine"""
- config = {"storage": {"cls": "remote", "args": {}}}
- config_path = prepare_config_file(tmpdir, config)
- cfg = load_and_check_config(config_path, type="any")
-
+ cfg = load_and_check_config(config_path)
assert cfg == config