From 520c77cc1c9498587feab13b0d151af16133b295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Thu, 19 Oct 2023 11:49:14 +0200 Subject: [PATCH] feat: added settings endpoints (#147) --- Makefile | 2 ++ aw_server/api.py | 11 +++++++++++ aw_server/rest.py | 17 +++++++++++++++++ aw_server/settings.py | 41 +++++++++++++++++++++++++++++++++++++++++ tests/conftest.py | 16 ++++++++++++++++ tests/test_client.py | 28 +++++++++------------------- 6 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 aw_server/settings.py diff --git a/Makefile b/Makefile index 7268f247..8c99ab3d 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,8 @@ install: cp misc/aw-server.service /usr/lib/systemd/user/aw-server.service test: + @# Note that extensive integration tests are also run in the bundle repo, + @# for both aw-server and aw-server-rust, but without code coverage. python -c 'import aw_server' python -m pytest tests/test_server.py diff --git a/aw_server/api.py b/aw_server/api.py index 177d2d53..bd7178ed 100644 --- a/aw_server/api.py +++ b/aw_server/api.py @@ -21,6 +21,7 @@ from .__about__ import __version__ from .exceptions import NotFound +from .settings import Settings logger = logging.getLogger(__name__) @@ -50,6 +51,7 @@ def g(self, bucket_id, *args, **kwargs): class ServerAPI: def __init__(self, db, testing) -> None: self.db = db + self.settings = Settings(testing) self.testing = testing self.last_event = {} # type: dict @@ -354,3 +356,12 @@ def get_log(self): for line in log_file.readlines()[::-1]: payload.append(json.loads(line)) return payload, 200 + + def get_setting(self, key): + """Get a setting""" + return self.settings.get(key, None) + + def set_setting(self, key, value): + """Set a setting""" + self.settings[key] = value + return value diff --git a/aw_server/rest.py b/aw_server/rest.py index 10dd9652..8b4b6ff9 100644 --- a/aw_server/rest.py +++ b/aw_server/rest.py @@ -387,3 +387,20 @@ class LogResource(Resource): @copy_doc(ServerAPI.get_log) def get(self): return current_app.api.get_log(), 200 + + +# SETTINGS + + +@api.route("/0/settings", defaults={"key": ""}) +@api.route("/0/settings/") +class SettingsResource(Resource): + def get(self, key: str): + data = current_app.api.get_setting(key) + return jsonify(data) + + def post(self, key: str): + if not key: + raise BadRequest("MissingParameter", "Missing required parameter key") + data = current_app.api.set_setting(key, request.get_json()) + return data diff --git a/aw_server/settings.py b/aw_server/settings.py new file mode 100644 index 00000000..3e07b569 --- /dev/null +++ b/aw_server/settings.py @@ -0,0 +1,41 @@ +import json +from pathlib import Path + +from aw_core.dirs import get_config_dir + + +class Settings: + def __init__(self, testing: bool): + filename = "settings.json" if not testing else "settings-testing.json" + self.config_file = Path(get_config_dir("aw-server")) / filename + self.load() + + def __getitem__(self, key): + return self.get(key) + + def __setitem__(self, key, value): + return self.set(key, value) + + def load(self): + if self.config_file.exists(): + with open(self.config_file) as f: + self.data = json.load(f) + else: + self.data = {} + + def save(self): + with open(self.config_file, "w") as f: + json.dump(self.data, f, indent=4) + + def get(self, key: str, default=None): + if not key: + return self.data + return self.data.get(key, default) + + def set(self, key, value): + if value: + self.data[key] = value + else: + if key in self.data: + del self.data[key] + self.save() diff --git a/tests/conftest.py b/tests/conftest.py index 141bab1d..ff106870 100755 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ import logging import pytest +from aw_client import ActivityWatchClient from aw_server.server import AWFlask logging.basicConfig(level=logging.WARN) @@ -14,3 +15,18 @@ def app(): @pytest.fixture(scope="session") def flask_client(app): yield app.test_client() + + +@pytest.fixture(scope="session") +def aw_client(): + # TODO: Could it be possible to write a sisterclass of ActivityWatchClient + # which calls aw_server.api directly? Would it be of use? Would add another + # layer of integration tests that are actually more like unit tests. + c = ActivityWatchClient("aw-client-test", testing=True) + yield c + + # Delete test buckets after all tests needing the fixture have been run + buckets = c.get_buckets() + for bucket_id in buckets: + if bucket_id.startswith("test-"): + c.delete_bucket(bucket_id) diff --git a/tests/test_client.py b/tests/test_client.py index c9b3f795..76a5aac1 100755 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,13 +1,11 @@ import logging -from datetime import datetime, timezone, timedelta import random -from time import sleep +from datetime import datetime, timedelta, timezone from pprint import pprint +from time import sleep import pytest - from aw_core.models import Event -from aw_client import ActivityWatchClient logging.basicConfig(level=logging.WARN) @@ -16,21 +14,6 @@ # layer of integration tests that are actually more like unit tests. -@pytest.fixture(scope="session") -def aw_client(): - # TODO: Could it be possible to write a sisterclass of ActivityWatchClient - # which calls aw_server.api directly? Would it be of use? Would add another - # layer of integration tests that are actually more like unit tests. - c = ActivityWatchClient("client-test", testing=True) - yield c - - # Delete test buckets after all tests needing the fixture have been run - buckets = c.get_buckets() - for bucket_id in buckets: - if bucket_id.startswith("test-"): - c.delete_bucket(bucket_id) - - @pytest.fixture(scope="function") def bucket(aw_client): bucket_id = "test-" + str(random.randint(0, 10**5)) @@ -285,3 +268,10 @@ def test_midnight_heartbeats(aw_client, bucket): ) pprint(recv_events_after_midnight) assert len(recv_events_after_midnight) == int(len(recv_events_merged) / 2) + + +def test_settings(aw_client): + aw_client.set_setting("test", "test") + assert aw_client.get_setting("test") == "test" + assert aw_client.get_setting("test2") is None + assert aw_client.get_setting() == {"test": "test"}