From 2c2563a26d374b5e9955485e2af8f93a6f150a1c Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Sat, 8 May 2021 00:22:15 -0400 Subject: [PATCH] refactor: configuration I/O to the outside --- setup.cfg | 1 - src/plotman/_tests/configuration_test.py | 45 +++++++++--------------- src/plotman/configuration.py | 30 ++++++++++------ src/plotman/interactive.py | 4 ++- src/plotman/plotman.py | 4 ++- 5 files changed, 43 insertions(+), 41 deletions(-) diff --git a/setup.cfg b/setup.cfg index 87f539b9..98e6f84f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -65,7 +65,6 @@ test = check-manifest pytest pytest-cov - pytest-mock pyfakefs [options.data_files] diff --git a/src/plotman/_tests/configuration_test.py b/src/plotman/_tests/configuration_test.py index 6ad2abb9..f0a548b6 100644 --- a/src/plotman/_tests/configuration_test.py +++ b/src/plotman/_tests/configuration_test.py @@ -8,60 +8,49 @@ from plotman import resources as plotman_resources -@pytest.fixture(name='config_path') -def config_fixture(tmp_path): - """Return direct path to plotman.yaml""" - with importlib.resources.path(plotman_resources, "plotman.yaml") as path: - yield path +@pytest.fixture(name='config_text') +def config_text_fixture(): + return importlib.resources.read_text(plotman_resources, "plotman.yaml") -def test_get_validated_configs__default(mocker, config_path): +def test_get_validated_configs__default(config_text): """Check that get_validated_configs() works with default/example plotman.yaml file.""" - mocker.patch("plotman.configuration.get_path", return_value=config_path) - res = configuration.get_validated_configs() + res = configuration.get_validated_configs(config_text, '') assert isinstance(res, configuration.PlotmanConfig) -def test_get_validated_configs__malformed(mocker, config_path): +def test_get_validated_configs__malformed(config_text): """Check that get_validated_configs() raises exception with invalid plotman.yaml contents.""" - mocker.patch("plotman.configuration.get_path", return_value=config_path) - with open(configuration.get_path(), "r") as file: - loaded_yaml = yaml.load(file, Loader=yaml.SafeLoader) + loaded_yaml = yaml.load(config_text, Loader=yaml.SafeLoader) # Purposefully malform the contents of loaded_yaml by changing tmp from List[str] --> str loaded_yaml["directories"]["tmp"] = "/mnt/tmp/00" - mocker.patch("yaml.load", return_value=loaded_yaml) + malformed_config_text = yaml.dump(loaded_yaml, Dumper=yaml.SafeDumper) with pytest.raises(configuration.ConfigurationException) as exc_info: - configuration.get_validated_configs() + configuration.get_validated_configs(malformed_config_text, '/the_path') - assert exc_info.value.args[0] == f"Config file at: '{configuration.get_path()}' is malformed" + assert exc_info.value.args[0] == f"Config file at: '/the_path' is malformed" -def test_get_validated_configs__missing(mocker, config_path): +def test_get_validated_configs__missing(): """Check that get_validated_configs() raises exception when plotman.yaml does not exist.""" - nonexistent_config = config_path.with_name("plotman2.yaml") - mocker.patch("plotman.configuration.get_path", return_value=nonexistent_config) - with pytest.raises(configuration.ConfigurationException) as exc_info: - configuration.get_validated_configs() + configuration.read_configuration_text('/invalid_path') assert exc_info.value.args[0] == ( - f"No 'plotman.yaml' file exists at expected location: '{nonexistent_config}'. To generate " + f"No 'plotman.yaml' file exists at expected location: '/invalid_path'. To generate " f"default config file, run: 'plotman config generate'" ) -def test_loads_without_user_interface(mocker, config_path, tmp_path): - with open(config_path, "r") as file: - loaded_yaml = yaml.load(file, Loader=yaml.SafeLoader) +def test_loads_without_user_interface(config_text): + loaded_yaml = yaml.load(config_text, Loader=yaml.SafeLoader) del loaded_yaml["user_interface"] - temporary_configuration_path = tmp_path.joinpath("config.yaml") - temporary_configuration_path.write_text(yaml.safe_dump(loaded_yaml)) + stripped_config_text = yaml.dump(loaded_yaml, Dumper=yaml.SafeDumper) - mocker.patch("plotman.configuration.get_path", return_value=temporary_configuration_path) - reloaded_yaml = configuration.get_validated_configs() + reloaded_yaml = configuration.get_validated_configs(stripped_config_text, '') assert reloaded_yaml.user_interface == configuration.UserInterface() diff --git a/src/plotman/configuration.py b/src/plotman/configuration.py index df15b8ea..2434456e 100644 --- a/src/plotman/configuration.py +++ b/src/plotman/configuration.py @@ -1,3 +1,4 @@ +import contextlib from typing import Dict, List, Optional import appdirs @@ -16,24 +17,33 @@ def get_path(): return appdirs.user_config_dir("plotman") + "/plotman.yaml" -def get_validated_configs(): +def read_configuration_text(config_path): + try: + with open(config_path, "r") as file: + return file.read() + except FileNotFoundError as e: + raise ConfigurationException( + f"No 'plotman.yaml' file exists at expected location: '{config_path}'. To generate " + f"default config file, run: 'plotman config generate'" + ) from e + + +def get_validated_configs(config_text, config_path): """Return a validated instance of PlotmanConfig with data from plotman.yaml :raises ConfigurationException: Raised when plotman.yaml is either missing or malformed """ schema = desert.schema(PlotmanConfig) - config_file_path = get_path() + config_objects = yaml.load(config_text, Loader=yaml.SafeLoader) + try: - with open(config_file_path, "r") as file: - config_file = yaml.load(file, Loader=yaml.SafeLoader) - return schema.load(config_file) - except FileNotFoundError as e: + loaded = schema.load(config_objects) + except marshmallow.exceptions.ValidationError as e: raise ConfigurationException( - f"No 'plotman.yaml' file exists at expected location: '{config_file_path}'. To generate " - f"default config file, run: 'plotman config generate'" + f"Config file at: '{config_path}' is malformed" ) from e - except marshmallow.exceptions.ValidationError as e: - raise ConfigurationException(f"Config file at: '{config_file_path}' is malformed") from e + + return loaded # Data models used to deserializing/formatting plotman.yaml files. diff --git a/src/plotman/interactive.py b/src/plotman/interactive.py index d5f32319..5835b18d 100644 --- a/src/plotman/interactive.py +++ b/src/plotman/interactive.py @@ -65,7 +65,9 @@ def archiving_status_msg(configured, active, status): def curses_main(stdscr): log = Log() - cfg = configuration.get_validated_configs() + config_path = configuration.get_path() + config_text = configuration.read_configuration_text(config_path) + cfg = configuration.get_validated_configs(config_text, config_path) plotting_active = True archiving_configured = cfg.directories.archive is not None diff --git a/src/plotman/plotman.py b/src/plotman/plotman.py index 3319cae8..d3468735 100755 --- a/src/plotman/plotman.py +++ b/src/plotman/plotman.py @@ -132,7 +132,9 @@ def main(): print("No action requested, add 'generate' or 'path'.") return - cfg = configuration.get_validated_configs() + config_path = configuration.get_path() + config_text = configuration.read_configuration_text(config_path) + cfg = configuration.get_validated_configs(config_text, config_path) # # Stay alive, spawning plot jobs