From b42d3d2643e208ac3aeac5ae4c234f0a8c4c5073 Mon Sep 17 00:00:00 2001 From: Ross Patterson Date: Mon, 30 Oct 2023 11:39:36 -0700 Subject: [PATCH] fix(daemon): Broken handling of empty/None keys --- newsfragments/+config-empty-keys.bugfix.rst | 18 ++++++++++++++++++ src/prunerr/runner.py | 18 ++++++++++++++---- .../home/daemon/.config/prunerr-example.yml | 11 +++++++++++ .../tests/home/daemon/.config/prunerr.yml | 2 -- src/prunerr/tests/test_daemon.py | 6 +++++- 5 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 newsfragments/+config-empty-keys.bugfix.rst create mode 100644 src/prunerr/tests/home/daemon/.config/prunerr-example.yml diff --git a/newsfragments/+config-empty-keys.bugfix.rst b/newsfragments/+config-empty-keys.bugfix.rst new file mode 100644 index 00000000..2f3f48c6 --- /dev/null +++ b/newsfragments/+config-empty-keys.bugfix.rst @@ -0,0 +1,18 @@ +Fix handling of empty top-level keys in the configuration file:: + + DEBUG:prunerr.runner:Sub-command `exec` completed in 89.50181317329407s + Traceback (most recent call last): + File "/usr/local/bin/prunerr", line 8, in + sys.exit(main()) + ^^^^^^ + File "/usr/local/lib/python3.11/site-packages/prunerr/__init__.py", line 241, in main + _main(args=args) + File "/usr/local/lib/python3.11/site-packages/prunerr/__init__.py", line 288, in _main + if (result := parsed_args.command(runner, **command_kwargs)) is not None: + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/prunerr/__init__.py", line 181, in daemon + runner.daemon(*args, **kwargs) + File "/usr/local/lib/python3.11/site-packages/prunerr/runner.py", line 337, in daemon + poll = self.config.get("daemon", {}).get("poll", 60) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + AttributeError: 'NoneType' object has no attribute 'get' diff --git a/src/prunerr/runner.py b/src/prunerr/runner.py index 606a0508..8499b563 100644 --- a/src/prunerr/runner.py +++ b/src/prunerr/runner.py @@ -65,6 +65,15 @@ def validate(self) -> dict: with self.config_file.open(encoding="utf-8") as config_opened: self.config = yaml.safe_load(config_opened) + # Avoid issues with empty keys having a `None` value in YAML: + for top_key in self.example_confg.keys(): + if top_key in self.config and self.config[top_key] is None: + logger.debug( + "Top-level configuration key is empty: %s", + top_key, + ) + self.config[top_key] = {} + # Raise helpful errors for required values: if not self.config.get("download-clients", {}).get("urls"): raise PrunerrValidationError( @@ -81,6 +90,10 @@ def validate(self) -> dict: "min-download-time-margin", self.example_confg["download-clients"]["min-download-time-margin"], ) + self.config.setdefault("daemon", {}).setdefault( + "poll", + self.example_confg["daemon"]["poll"], + ) return self.config @@ -356,10 +369,7 @@ def daemon(self): logger.debug("Sub-command `exec` completed in %ss", time.time() - start) # Determine the poll interval before clearing the config - poll = self.config.get("daemon", {}).get( - "poll", - self.example_confg["daemon"]["poll"], - ) + poll = self.config["daemon"]["poll"] # Free any memory possible between daemon loops self.clear() diff --git a/src/prunerr/tests/home/daemon/.config/prunerr-example.yml b/src/prunerr/tests/home/daemon/.config/prunerr-example.yml new file mode 100644 index 00000000..b114d68f --- /dev/null +++ b/src/prunerr/tests/home/daemon/.config/prunerr-example.yml @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2023 Ross Patterson +# +# SPDX-License-Identifier: MIT + +daemon: + poll: 1 +servarrs: +download-clients: + max-download-bandwidth: 100 + min-download-time-margin: 3600 +indexers: diff --git a/src/prunerr/tests/home/daemon/.config/prunerr.yml b/src/prunerr/tests/home/daemon/.config/prunerr.yml index 6ea546cc..8cced605 100644 --- a/src/prunerr/tests/home/daemon/.config/prunerr.yml +++ b/src/prunerr/tests/home/daemon/.config/prunerr.yml @@ -3,8 +3,6 @@ # SPDX-License-Identifier: MIT daemon: - # Cover the `time.sleep(1)` part of the `daemon` loop. - poll: 1 servarrs: Sonarr: url: "http://localhost:8989" diff --git a/src/prunerr/tests/test_daemon.py b/src/prunerr/tests/test_daemon.py index 022b4fad..3fdf1ba6 100644 --- a/src/prunerr/tests/test_daemon.py +++ b/src/prunerr/tests/test_daemon.py @@ -15,7 +15,7 @@ from unittest import mock -import prunerr.downloadclient +import prunerr from .. import tests @@ -64,6 +64,10 @@ def mock_exit_daemon_response( @mock.patch.dict(os.environ, ENV) +@mock.patch( + "prunerr.runner.PrunerrRunner.EXAMPLE_CONFIG", + HOME / ".config" / "prunerr-example.yml", +) class PrunerrDaemonTests(tests.PrunerrTestCase): """ Tests covering the Prunerr `daemon` sub-command.