diff --git a/default.nix b/default.nix index 1af935e..a85e00c 100644 --- a/default.nix +++ b/default.nix @@ -20,6 +20,7 @@ pythonPackages.buildPythonPackage rec { typing-extensions uvicorn websockets + httpx ]; pythonImportsCheck = [ "selfprivacy_api" ]; doCheck = false; diff --git a/flake.lock b/flake.lock index ba47e51..df8af1e 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1719957072, - "narHash": "sha256-gvFhEf5nszouwLAkT9nWsDzocUTqLWHuL++dvNjMp9I=", + "lastModified": 1721949857, + "narHash": "sha256-DID446r8KsmJhbCzx4el8d9SnPiE8qa6+eEQOJ40vR0=", "owner": "nixos", "repo": "nixpkgs", - "rev": "7144d6241f02d171d25fba3edeaf15e0f2592105", + "rev": "a1cc729dcbc31d9b0d11d86dc7436163548a9665", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 4c8880e..d77dfde 100644 --- a/flake.nix +++ b/flake.nix @@ -8,7 +8,7 @@ system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages.${system}; selfprivacy-graphql-api = pkgs.callPackage ./default.nix { - pythonPackages = pkgs.python310Packages; + pythonPackages = pkgs.python312Packages; rev = self.shortRev or self.dirtyShortRev or "dirty"; }; python = self.packages.${system}.default.pythonModule; @@ -85,7 +85,7 @@ packages = with pkgs; [ nixpkgs-fmt rclone - redis + valkey restic self.packages.${system}.pytest-vm # FIXME consider loading this explicitly only after ArchLinux issue is solved @@ -134,6 +134,7 @@ boot.consoleLogLevel = lib.mkForce 3; documentation.enable = false; services.journald.extraConfig = lib.mkForce ""; + services.redis.package = pkgs.valkey; services.redis.servers.sp-api = { enable = true; save = [ ]; diff --git a/nixos/module.nix b/nixos/module.nix index 7790e18..0203bf0 100644 --- a/nixos/module.nix +++ b/nixos/module.nix @@ -61,7 +61,7 @@ in HOME = "/root"; PYTHONUNBUFFERED = "1"; PYTHONPATH = - pkgs.python310Packages.makePythonPath [ selfprivacy-graphql-api ]; + pkgs.python312Packages.makePythonPath [ selfprivacy-graphql-api ]; } // config.networking.proxy.envVars; path = [ "/var/" @@ -82,7 +82,7 @@ in wantedBy = [ "network-online.target" ]; serviceConfig = { User = "root"; - ExecStart = "${pkgs.python310Packages.huey}/bin/huey_consumer.py selfprivacy_api.task_registry.huey"; + ExecStart = "${pkgs.python312Packages.huey}/bin/huey_consumer.py selfprivacy_api.task_registry.huey"; Restart = "always"; RestartSec = "5"; }; diff --git a/selfprivacy_api/backup/tasks.py b/selfprivacy_api/backup/tasks.py index cd7c400..7478cc2 100644 --- a/selfprivacy_api/backup/tasks.py +++ b/selfprivacy_api/backup/tasks.py @@ -78,7 +78,7 @@ def do_autobackup() -> None: For some reason, we cannot launch periodic huey tasks inside tests """ - time = datetime.utcnow().replace(tzinfo=timezone.utc) + time = datetime.now(timezone.utc) services_to_back_up = Backups.services_to_back_up(time) if not services_to_back_up: return diff --git a/selfprivacy_api/graphql/queries/backup.py b/selfprivacy_api/graphql/queries/backup.py index 7695f0d..52d8680 100644 --- a/selfprivacy_api/graphql/queries/backup.py +++ b/selfprivacy_api/graphql/queries/backup.py @@ -50,6 +50,7 @@ def tombstone_service(service_id: str) -> Service: url=None, can_be_backed_up=False, backup_description="", + is_installed=False, ) diff --git a/selfprivacy_api/graphql/queries/system.py b/selfprivacy_api/graphql/queries/system.py index 55537d7..0275327 100644 --- a/selfprivacy_api/graphql/queries/system.py +++ b/selfprivacy_api/graphql/queries/system.py @@ -158,8 +158,8 @@ class System: ) ) domain_info: SystemDomainInfo = strawberry.field(resolver=get_system_domain_info) - settings: SystemSettings = SystemSettings() - info: SystemInfo = SystemInfo() + settings: SystemSettings = strawberry.field(default_factory=SystemSettings) + info: SystemInfo = strawberry.field(default_factory=SystemInfo) provider: SystemProviderInfo = strawberry.field(resolver=get_system_provider_info) @strawberry.field diff --git a/selfprivacy_api/utils/redis_pool.py b/selfprivacy_api/utils/redis_pool.py index ea827d1..64f5758 100644 --- a/selfprivacy_api/utils/redis_pool.py +++ b/selfprivacy_api/utils/redis_pool.py @@ -1,15 +1,15 @@ """ Redis pool module for selfprivacy_api """ + import redis import redis.asyncio as redis_async +from redis.asyncio.client import PubSub -from selfprivacy_api.utils.singleton_metaclass import SingletonMetaclass REDIS_SOCKET = "/run/redis-sp-api/redis.sock" -# class RedisPool(metaclass=SingletonMetaclass): class RedisPool: """ Redis connection pool singleton. @@ -51,7 +51,7 @@ class RedisPool: """ return redis_async.Redis(connection_pool=self._async_pool) - async def subscribe_to_keys(self, pattern: str) -> redis_async.client.PubSub: + async def subscribe_to_keys(self, pattern: str) -> PubSub: async_redis = self.get_connection_async() pubsub = async_redis.pubsub() await pubsub.psubscribe(f"__keyspace@{self._dbnumber}__:" + pattern) diff --git a/tests/test_config_item.py b/tests/test_config_item.py index 724c29e..355f1cc 100644 --- a/tests/test_config_item.py +++ b/tests/test_config_item.py @@ -12,7 +12,10 @@ def service_options(): return {} -def test_string_service_config_item(service_options): +DUMMY_SERVICE = "testservice" + + +def test_string_service_config_item(dummy_service): item = StringServiceConfigItem( id="test_string", default_value="1337", @@ -21,11 +24,11 @@ def test_string_service_config_item(service_options): widget="text", allow_empty=False, ) - assert item.get_value(service_options) == "1337" - item.set_value("123", service_options) - assert item.get_value(service_options) == "123" + assert item.get_value(DUMMY_SERVICE) == "1337" + item.set_value("123", DUMMY_SERVICE) + assert item.get_value(DUMMY_SERVICE) == "123" with pytest.raises(ValueError): - item.set_value("abc", service_options) + item.set_value("abc", DUMMY_SERVICE) assert item.validate_value("123") is True assert item.validate_value("abc") is False assert item.validate_value("123abc") is False @@ -36,7 +39,7 @@ def test_string_service_config_item(service_options): assert item.validate_value(True) is False -def test_string_service_config_item_allows_empty(service_options): +def test_string_service_config_item_allows_empty(dummy_service): item = StringServiceConfigItem( id="test_string", default_value="1337", @@ -44,9 +47,9 @@ def test_string_service_config_item_allows_empty(service_options): widget="text", allow_empty=True, ) - assert item.get_value(service_options) == "1337" - item.set_value("", service_options) - assert item.get_value(service_options) == "" + assert item.get_value(DUMMY_SERVICE) == "1337" + item.set_value("", DUMMY_SERVICE) + assert item.get_value(DUMMY_SERVICE) == "" assert item.validate_value("") is True assert item.validate_value(None) is False assert item.validate_value(123) is False @@ -57,17 +60,17 @@ def test_string_service_config_item_allows_empty(service_options): assert item.validate_value(True) is False -def test_string_service_config_item_not_allows_empty(service_options): +def test_string_service_config_item_not_allows_empty(dummy_service): item = StringServiceConfigItem( id="test_string", default_value="1337", description="Test digits string", widget="text", ) - assert item.get_value(service_options) == "1337" + assert item.get_value(DUMMY_SERVICE) == "1337" with pytest.raises(ValueError): - item.set_value("", service_options) - assert item.get_value(service_options) == "1337" + item.set_value("", DUMMY_SERVICE) + assert item.get_value(DUMMY_SERVICE) == "1337" assert item.validate_value("") is False assert item.validate_value(None) is False assert item.validate_value(123) is False @@ -78,16 +81,16 @@ def test_string_service_config_item_not_allows_empty(service_options): assert item.validate_value(True) is False -def test_bool_service_config_item(service_options): +def test_bool_service_config_item(dummy_service): item = BoolServiceConfigItem( id="test_bool", default_value=True, description="Test bool", widget="switch", ) - assert item.get_value(service_options) is True - item.set_value(False, service_options) - assert item.get_value(service_options) is False + assert item.get_value(DUMMY_SERVICE) is True + item.set_value(False, DUMMY_SERVICE) + assert item.get_value(DUMMY_SERVICE) is False assert item.validate_value(True) is True assert item.validate_value(False) is True assert item.validate_value("True") is False @@ -97,7 +100,7 @@ def test_bool_service_config_item(service_options): assert item.validate_value("1") is False -def test_enum_service_config_item(service_options): +def test_enum_service_config_item(dummy_service): item = EnumServiceConfigItem( id="test_enum", default_value="option1", @@ -105,11 +108,11 @@ def test_enum_service_config_item(service_options): options=["option1", "option2", "option3"], widget="select", ) - assert item.get_value(service_options) == "option1" - item.set_value("option2", service_options) - assert item.get_value(service_options) == "option2" + assert item.get_value(DUMMY_SERVICE) == "option1" + item.set_value("option2", DUMMY_SERVICE) + assert item.get_value(DUMMY_SERVICE) == "option2" with pytest.raises(ValueError): - item.set_value("option4", service_options) + item.set_value("option4", DUMMY_SERVICE) assert item.validate_value("option1") is True assert item.validate_value("option4") is False assert item.validate_value("option2") is True @@ -118,7 +121,7 @@ def test_enum_service_config_item(service_options): assert item.validate_value(True) is False -def test_string_service_config_item_subdomain(service_options, dummy_service): +def test_string_service_config_item_subdomain(dummy_service): item = StringServiceConfigItem( id="test_subdomain", default_value="example", @@ -127,13 +130,13 @@ def test_string_service_config_item_subdomain(service_options, dummy_service): allow_empty=False, regex=SUBDOMAIN_REGEX, ) - assert item.get_value(service_options) == "example" - item.set_value("subdomain", service_options) - assert item.get_value(service_options) == "subdomain" + assert item.get_value(DUMMY_SERVICE) == "example" + item.set_value("subdomain", DUMMY_SERVICE) + assert item.get_value(DUMMY_SERVICE) == "subdomain" with pytest.raises(ValueError): item.set_value( "invalid-subdomain-because-it-is-very-very-very-very-very-very-long", - service_options, + DUMMY_SERVICE, ) assert item.validate_value("subdomain") is True assert ( diff --git a/tests/test_graphql/test_api_backup.py b/tests/test_graphql/test_api_backup.py index 92f74bf..f3a68c1 100644 --- a/tests/test_graphql/test_api_backup.py +++ b/tests/test_graphql/test_api_backup.py @@ -218,7 +218,7 @@ def api_set_quotas(authorized_client, quotas: _AutobackupQuotas): "/graphql", json={ "query": API_SET_AUTOBACKUP_QUOTAS_MUTATION, - "variables": {"input": quotas.dict()}, + "variables": {"input": quotas.model_dump()}, }, ) return response @@ -401,7 +401,7 @@ def test_autobackup_quotas_nonzero(authorized_client, backups): assert_ok(data) configuration = data["configuration"] - assert configuration["autobackupQuotas"] == quotas + assert configuration["autobackupQuotas"] == quotas.model_dump() def test_autobackup_period_nonzero(authorized_client, backups): diff --git a/tests/test_redis.py b/tests/test_redis.py index 02dfb21..6db94fd 100644 --- a/tests/test_redis.py +++ b/tests/test_redis.py @@ -70,7 +70,6 @@ async def test_pubsub(empty_redis, event_loop): # Sanity checking because of previous event loop bugs assert event_loop == asyncio.get_event_loop() assert event_loop == asyncio.events.get_event_loop() - assert event_loop == asyncio.events._get_event_loop() assert event_loop == asyncio.events.get_running_loop() reader = streams.StreamReader(34)