From 16f753fdf34e4ec41d85914d0d04a4ee743261fe Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Sat, 23 Oct 2021 21:05:36 +0100 Subject: [PATCH 1/5] updating todo --- rewrite.todo | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/rewrite.todo b/rewrite.todo index 19142e5..bb99cef 100644 --- a/rewrite.todo +++ b/rewrite.todo @@ -1,16 +1,26 @@ +Testing: + To Write: + ☐ Test the Config file skipping header with `skip_header_init` + ☐ Document coverage commands + `coverage run --source=./panaetius -m pytest` + `coverage report` & `coverage html` > gives ./htmlcov/index.html + ☐ Document for abstract methods should raise NotImplementedError + ☐ Document https://stackoverflow.com/a/9212387 Documentation: ☐ Rewrite documentation using `mkdocs` and using `.md`. ☐ Update the metadata in the `pyproject.toml`. ☐ Create a new `Readme.md` and remove the `.rst`. + ☐ Document the logging strategy + CLI tools should use `logger.critical` and raise SystemExit(1) + Libraries should raise custom errors and have a `logger.critical(exec_info=1)` Misc: ☐ Use the python runner to build the docs & run the tests (including coverage html) coverage run -m pytest && coverage report && coverage html ☐ document this in trilium - ☐ Bump the version to release 2.0 - Archive: + ✘ Bump the version to release 2.0 @cancelled(21-10-23 05:36) @project(Misc) ✔ Handle if a bool is passed in as a default @done(21-10-16 05:25) @project(Coding.No Config File) ✔ Handle if a bool is passed in as a default @done(21-10-16 05:25) @project(Coding.Config File) ✔ Create SimpleLogger, AdvancedLogger, CustomLogger classes @done(21-10-16 16:22) @project(Coding.Logging) From 2092245dadfeff8585a2b938b9bc407d4b5d0fca Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Sat, 23 Oct 2021 21:07:07 +0100 Subject: [PATCH 2/5] adding skip header directory option --- panaetius/config.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/panaetius/config.py b/panaetius/config.py index ab14ae2..0c64d92 100644 --- a/panaetius/config.py +++ b/panaetius/config.py @@ -21,14 +21,24 @@ from panaetius.exceptions import KeyErrorTooDeepException, InvalidPythonExceptio class Config: """The configuration class to access variables.""" - def __init__(self, header_variable: str, config_path: str = "") -> None: + def __init__( + self, + header_variable: str, + config_path: str | None = None, + skip_header_init: bool = False, + ) -> None: """ Create a Config object to set and access variables. Args: header_variable (str): Your header variable name. config_path (str, optional): The path where the header directory is stored. - Defaults to `~/.config`. + Defaults to None on initialisation. + skip_header_init (bool, optional): If True will not use a header + subdirectory in the `config_path`. Defaults to False. + + Examples: + `config_path` defaults to None on initialisation but will be set to `~/.config`. Example: A header of `data_analysis` with a config_path of `~/myapps` will define @@ -37,9 +47,10 @@ class Config: self.header_variable = header_variable self.config_path = ( pathlib.Path(config_path).expanduser() - if config_path + if config_path is not None else pathlib.Path.home() / ".config" ) + self.skip_header_init = skip_header_init self._missing_config = self._check_config_file_exists() # default logging options @@ -55,7 +66,12 @@ class Config: Returns: dict: The contents of the `.yml` loaded as a python dictionary. """ - config_file_location = self.config_path / self.header_variable / "config.yml" + if self.skip_header_init: + config_file_location = self.config_path / "config.yml" + else: + config_file_location = ( + self.config_path / self.header_variable / "config.yml" + ) try: with open(config_file_location, "r", encoding="utf-8") as config_file: # return dict(toml.load(config_file)) @@ -103,7 +119,10 @@ class Config: return self._get_env_value(env_key, default) def _check_config_file_exists(self) -> bool: - config_file_location = self.config_path / self.header_variable / "config.yml" + if self.skip_header_init is False: + config_file_location = self.config_path / self.header_variable / "config.yml" + else: + config_file_location = self.config_path / "config.yml" try: with open(config_file_location, "r", encoding="utf-8"): return False From 844a2f6f3fb004ddce39bb1fb080ab4996528ea4 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Sat, 23 Oct 2021 21:07:31 +0100 Subject: [PATCH 3/5] changing env var to use strings without extra quotes --- panaetius/config.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/panaetius/config.py b/panaetius/config.py index 0c64d92..34fa52a 100644 --- a/panaetius/config.py +++ b/panaetius/config.py @@ -15,7 +15,7 @@ from typing import Any # import toml import yaml -from panaetius.exceptions import KeyErrorTooDeepException, InvalidPythonException +from panaetius.exceptions import KeyErrorTooDeepException class Config: @@ -184,7 +184,8 @@ class Config: try: return ast.literal_eval(value) except (ValueError, SyntaxError): - raise InvalidPythonException(f"{value} is not valid Python.") # noqa + # string without spaces: ValueError, with spaces; SyntaxError + return value def __load_default_value(self, default: Any) -> Any: # noqa return default From 485ab9ef091e3982e4f177e36fa897a41c84bd4b Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Sat, 23 Oct 2021 21:08:07 +0100 Subject: [PATCH 4/5] change abc to raise NotImplementedError for tests --- .coveragerc | 7 +++++++ panaetius/logging.py | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..ff6415d --- /dev/null +++ b/.coveragerc @@ -0,0 +1,7 @@ +[report] +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain if tests don't hit defensive assertion code: + raise NotImplementedError diff --git a/panaetius/logging.py b/panaetius/logging.py index 08f5221..a307af3 100644 --- a/panaetius/logging.py +++ b/panaetius/logging.py @@ -98,11 +98,11 @@ class LoggingData(metaclass=ABCMeta): @property @abstractmethod def format(self) -> str: - pass + raise NotImplementedError @abstractmethod def __init__(self, logging_level: str): - self.logging_level = logging_level + raise NotImplementedError class SimpleLogger(LoggingData): From 1af790f01a0ca254277d99dd14917bd8bf284f2d Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Sat, 23 Oct 2021 21:08:16 +0100 Subject: [PATCH 5/5] updating tests --- panaetius/config.py | 1 - tests/data/without_header/config.yml | 9 +++++++++ tests/test_config.py | 30 +++++++++++++++++++++++++--- tests/test_logging.py | 3 +++ 4 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 tests/data/without_header/config.yml diff --git a/panaetius/config.py b/panaetius/config.py index 34fa52a..3677acf 100644 --- a/panaetius/config.py +++ b/panaetius/config.py @@ -40,7 +40,6 @@ class Config: Examples: `config_path` defaults to None on initialisation but will be set to `~/.config`. - Example: A header of `data_analysis` with a config_path of `~/myapps` will define a config file in `~/myapps/data_analysis/config.yml`. """ diff --git a/tests/data/without_header/config.yml b/tests/data/without_header/config.yml new file mode 100644 index 0000000..377f992 --- /dev/null +++ b/tests/data/without_header/config.yml @@ -0,0 +1,9 @@ +panaetius_testing: + some_top_string: some_top_value + second: + some_second_string: some_second_value + some_second_int: 1 + some_second_float: 1.0 + some_second_list: ["some", "second", "value"] + some_second_table: { "first": ["some", "first", "value"] } + some_second_table_bools: { "bool": [true, false] } diff --git a/tests/test_config.py b/tests/test_config.py index 3137e22..21b282c 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -29,6 +29,17 @@ def test_user_config_path_set(header, shared_datadir): assert str(config.config_path) == config_path +def test_user_config_path_without_header_dir_set(header, shared_datadir): + # arrange + config_path = str(shared_datadir / "without_header") + + # act + config = panaetius.Config(header, config_path, skip_header_init=True) + + # assert + assert str(config.config_path) == config_path + + # test config files @@ -44,6 +55,18 @@ def test_config_file_exists(header, shared_datadir): assert config._missing_config is False +def test_config_file_without_header_dir_exists(header, shared_datadir): + # arrange + config_path = str(shared_datadir / "without_header") + + # act + config = panaetius.Config(header, config_path, skip_header_init=True) + _ = config.config + + # assert + assert config._missing_config is False + + def test_config_file_contents_read_success( header, shared_datadir, testing_config_contents ): @@ -106,7 +129,7 @@ def test_get_value_from_key( def test_get_value_environment_var_override(header, shared_datadir): # arrange - os.environ[f"{header.upper()}_SOME_TOP_STRING"] = '"some_overridden_value"' + os.environ[f"{header.upper()}_SOME_TOP_STRING"] = "some_overridden_value" config_path = str(shared_datadir / "without_logging") config = panaetius.Config(header, config_path) panaetius.set_config(config, "some_top_string") @@ -158,7 +181,7 @@ def test_get_value_missing_key_from_default(header, shared_datadir): def test_get_value_missing_key_from_env(header, shared_datadir): # arrange - os.environ[f"{header.upper()}_MISSING_KEY"] = '"some missing key"' + os.environ[f"{header.upper()}_MISSING_KEY"] = "some missing key" config_path = str(shared_datadir / "without_logging") config = panaetius.Config(header, config_path) @@ -205,7 +228,7 @@ def test_missing_config_read_from_default(header, shared_datadir): @pytest.mark.parametrize( "env_value,expected_value", [ - ('"a missing string"', "a missing string"), + ("a missing string", "a missing string"), ("1", 1), ("1.0", 1.0), ("True", True), @@ -237,6 +260,7 @@ def test_missing_config_read_from_env_var( del os.environ[f"{header.upper()}_MISSING_KEY_READ_FROM_ENV_VAR"] +@pytest.mark.skip(reason="No longer needed as strings are loaded without quotes") def test_missing_config_read_from_env_var_invalid_python(header): # arrange os.environ[f"{header.upper()}_INVALID_PYTHON"] = "a string without quotes" diff --git a/tests/test_logging.py b/tests/test_logging.py index b1344ff..46ccc78 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -21,6 +21,7 @@ def test_logging_directory_does_not_exist(header, shared_datadir): assert str(logging_exception.value) == "" +# TODO: change this test so it asserts the dir exists def test_logging_directory_does_exist(header, shared_datadir): # arrange config = Config(header) @@ -32,3 +33,5 @@ def test_logging_directory_does_exist(header, shared_datadir): # assert assert isinstance(logger, logging.Logger) + +# TODO: add tests to check that SimpleLogger, AdvancedLogger, CustomLogger work as intended