From f73a6d24415d0d6d261943cddcca5ff763609f4e Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Mon, 18 Oct 2021 00:12:20 +0100 Subject: [PATCH] adding latest tests --- .vscode/settings.json | 15 -- TODO | 3 + panaetius/config.py | 65 +++--- panaetius/exceptions.py | 6 + panaetius/library.py | 3 +- panaetius/logging.py | 4 + poetry.lock | 48 ++++- prospector.yaml | 4 +- pyproject.toml | 1 + pytest.ini | 6 +- rewrite.todo | 47 +++-- tests/conftest.py | 17 ++ .../panaetius_testing/config.toml | 4 +- tests/scratchpad.py | 41 ++-- tests/test_config.py | 186 +++++++++++++++++- tests/test_library.py | 13 ++ tests/test_logging.py | 34 ++++ 17 files changed, 402 insertions(+), 95 deletions(-) create mode 100644 TODO create mode 100644 panaetius/exceptions.py rename tests/{test_config => data}/without_logging/panaetius_testing/config.toml (83%) create mode 100644 tests/test_library.py create mode 100644 tests/test_logging.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 3525082..9f07c66 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,20 +4,5 @@ "python.linting.enabled": true, "python.pythonPath": ".venv/bin/python", "restructuredtext.confPath": "${workspaceFolder}/docs/source", - "workbench.colorCustomizations": { - "editorGroup.border": "#3ea389", - "panel.border": "#3ea389", - "sash.hoverBorder": "#3ea389", - "sideBar.border": "#3ea389", - "statusBar.background": "#307e6a", - "statusBar.foreground": "#e7e7e7", - "statusBarItem.hoverBackground": "#3ea389", - "statusBarItem.remoteBackground": "#307e6a", - "statusBarItem.remoteForeground": "#e7e7e7", - "titleBar.activeBackground": "#307e6a", - "titleBar.activeForeground": "#e7e7e7", - "titleBar.inactiveBackground": "#307e6a99", - "titleBar.inactiveForeground": "#e7e7e799" - }, "peacock.color": "#307E6A" } diff --git a/TODO b/TODO new file mode 100644 index 0000000..a0814df --- /dev/null +++ b/TODO @@ -0,0 +1,3 @@ + +Todo: + ☐ Item diff --git a/panaetius/config.py b/panaetius/config.py index 14fd6a6..0f9f44a 100644 --- a/panaetius/config.py +++ b/panaetius/config.py @@ -1,10 +1,14 @@ from __future__ import annotations + +import ast import os import pathlib from typing import Any import toml +from panaetius.exceptions import KeyErrorTooDeepException + class Config: """docstring for Config().""" @@ -16,7 +20,7 @@ class Config: if config_path else pathlib.Path.home() / ".config" ) - self._missing_config = False + self._missing_config = self._check_config_file_exists() # default logging options self.logging_path: str | None = None @@ -24,42 +28,52 @@ class Config: self.logging_backup_count: int = 0 @property - def config(self) -> dict[str, Any]: + def config(self) -> dict: config_file_location = self.config_path / self.header_variable / "config.toml" try: with open(config_file_location, "r", encoding="utf-8") as config_file: return dict(toml.load(config_file)) except FileNotFoundError: - self._missing_config = True return {} - def get_value(self, key: str, default: Any, coerce: bool = False) -> Any: + def get_value(self, key: str, default: Any) -> Any: env_key = f"{self.header_variable.upper()}_{key.upper().replace('.', '_')}" if not self._missing_config: # look in the config file - return self._get_config_value(env_key, key, default, coerce) + return self._get_config_value(env_key, key, default) # no config file, look for env vars - return self._get_env_value(env_key, default, coerce) + return self._get_env_value(env_key, default) - def _get_config_value( - self, env_key: str, key: str, default: Any, coerce: bool = False - ) -> Any: + def _check_config_file_exists(self) -> bool: + config_file_location = self.config_path / self.header_variable / "config.toml" + try: + with open(config_file_location, "r", encoding="utf-8"): + return False + except FileNotFoundError: + return True + + def _get_config_value(self, env_key: str, key: str, default: Any) -> Any: try: # look under top header # REVIEW: could this be auto handled for a key of arbitrary length? + if len(key.split(".")) > 2: + raise KeyErrorTooDeepException( + f"Your key of {key} can only be 2 levels deep maximum. " + f"You have {len(key.split('.'))}" + ) if len(key.split(".")) == 1: return self.__get_config_value_key_split_once(key) if len(key.split(".")) == 2: return self.__get_config_value_key_split_twice(key) - raise KeyError + raise KeyError() except (KeyError, TypeError): value = os.environ.get(env_key.replace("-", "_")) if value is None: return self.__get_config_value_missing_key_value_is_none(default) # if env var, coerce value if flag is set, else return a TOML string - return self.__get_config_value_missing_key_value_is_not_none(value, coerce) + return self.__get_config_value_missing_key_value_is_not_none(value) def __get_config_value_key_split_once(self, key: str) -> Any: name = key.lower() @@ -72,33 +86,18 @@ class Config: def __get_config_value_missing_key_value_is_none(self, default: Any) -> Any: return self.__load_default_value(default) - def __get_config_value_missing_key_value_is_not_none( - self, value: str, coerce: bool - ) -> Any: - return self.__load_value(value, coerce) + def __get_config_value_missing_key_value_is_not_none(self, value: str) -> Any: + return self.__load_value(value) - def _get_env_value( # noqa - self, env_key: str, default: Any, coerce: bool = False - ) -> Any: + def _get_env_value(self, env_key: str, default: Any) -> Any: # noqa # look for an environment variable, fallback to default value = os.environ.get(env_key.replace("-", "_")) if value is None: return self.__load_default_value(default) - return self.__load_value(value, coerce) + return self.__load_value(value) - def __load_value(self, value: str, coerce: bool) -> Any: # noqa - value = str(value).lower() if isinstance(value, bool) else value - return ( - toml.loads(f"value = {value}")["value"] - if coerce - else toml.loads(f'value = "{value}"')["value"] - ) + def __load_value(self, value: str) -> Any: # noqa + return ast.literal_eval(value) def __load_default_value(self, default: Any) -> Any: # noqa - if isinstance(default, str): - return toml.loads(f'value = "{default}"')["value"] - # if default is bool convert to lower case toml syntax - default = str(default).lower() if isinstance(default, bool) else default - return ( - toml.loads(f"value = {default}")["value"] if default is not None else None - ) + return default diff --git a/panaetius/exceptions.py b/panaetius/exceptions.py new file mode 100644 index 0000000..a7a6333 --- /dev/null +++ b/panaetius/exceptions.py @@ -0,0 +1,6 @@ +class KeyErrorTooDeepException(Exception): + pass + + +class LoggingDirectoryDoesNotExistException(Exception): + pass diff --git a/panaetius/library.py b/panaetius/library.py index 2f76417..f624159 100644 --- a/panaetius/library.py +++ b/panaetius/library.py @@ -9,7 +9,6 @@ def set_config( config_inst: Config, key: str, default: Any = None, - coerce: bool = False, ): config_var = key.lower().replace(".", "_") - setattr(config_inst, config_var, config_inst.get_value(key, default, coerce)) + setattr(config_inst, config_var, config_inst.get_value(key, default)) diff --git a/panaetius/logging.py b/panaetius/logging.py index 574db26..4b86b3a 100644 --- a/panaetius/logging.py +++ b/panaetius/logging.py @@ -8,6 +8,7 @@ import sys from panaetius import Config from panaetius.library import set_config +from panaetius.exceptions import LoggingDirectoryDoesNotExistException def set_logger(config_inst: Config, logging_format_inst: LoggingData) -> logging.Logger: @@ -22,6 +23,9 @@ def set_logger(config_inst: Config, logging_format_inst: LoggingData) -> logging / f"{config_inst.header_variable}.log" ).expanduser() + if not logging_file.parents[0].exists(): + raise LoggingDirectoryDoesNotExistException() + if config_inst.logging_rotate_bytes == 0: set_config(config_inst, "logging.rotate_bytes", 512000) if config_inst.logging_backup_count == 0: diff --git a/poetry.lock b/poetry.lock index e936ba2..7580b27 100644 --- a/poetry.lock +++ b/poetry.lock @@ -57,6 +57,17 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "coverage" +version = "6.0.2" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +toml = ["tomli"] + [[package]] name = "dodgy" version = "0.2.1" @@ -610,7 +621,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "bc75d0878aaf4033c2d9520333a559d29e81962a8a0138ed8861014d2fc77eac" +content-hash = "468d1aa5e0c440262f6041ad859358a84ef32462941aa6f3ba71838a52cc1ced" [metadata.files] astroid = [ @@ -633,6 +644,41 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] +coverage = [ + {file = "coverage-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1549e1d08ce38259de2bc3e9a0d5f3642ff4a8f500ffc1b2df73fd621a6cdfc0"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcae10fccb27ca2a5f456bf64d84110a5a74144be3136a5e598f9d9fb48c0caa"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:53a294dc53cfb39c74758edaa6305193fb4258a30b1f6af24b360a6c8bd0ffa7"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8251b37be1f2cd9c0e5ccd9ae0380909c24d2a5ed2162a41fcdbafaf59a85ebd"}, + {file = "coverage-6.0.2-cp310-cp310-win32.whl", hash = "sha256:db42baa892cba723326284490283a68d4de516bfb5aaba369b4e3b2787a778b7"}, + {file = "coverage-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:bbffde2a68398682623d9dd8c0ca3f46fda074709b26fcf08ae7a4c431a6ab2d"}, + {file = "coverage-6.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:60e51a3dd55540bec686d7fff61b05048ca31e804c1f32cbb44533e6372d9cc3"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a6a9409223a27d5ef3cca57dd7cd4dfcb64aadf2fad5c3b787830ac9223e01a"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4b34ae4f51bbfa5f96b758b55a163d502be3dcb24f505d0227858c2b3f94f5b9"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3bbda1b550e70fa6ac40533d3f23acd4f4e9cb4e6e77251ce77fdf41b3309fb2"}, + {file = "coverage-6.0.2-cp36-cp36m-win32.whl", hash = "sha256:4e28d2a195c533b58fc94a12826f4431726d8eb029ac21d874345f943530c122"}, + {file = "coverage-6.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a82d79586a0a4f5fd1cf153e647464ced402938fbccb3ffc358c7babd4da1dd9"}, + {file = "coverage-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3be1206dc09fb6298de3fce70593e27436862331a85daee36270b6d0e1c251c4"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cd3828bbe1a40070c11fe16a51df733fd2f0cb0d745fb83b7b5c1f05967df7"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d036dc1ed8e1388e995833c62325df3f996675779541f682677efc6af71e96cc"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04560539c19ec26995ecfb3d9307ff154fbb9a172cb57e3b3cfc4ced673103d1"}, + {file = "coverage-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:e4fb7ced4d9dec77d6cf533acfbf8e1415fe799430366affb18d69ee8a3c6330"}, + {file = "coverage-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:77b1da5767ed2f44611bc9bc019bc93c03fa495728ec389759b6e9e5039ac6b1"}, + {file = "coverage-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61b598cbdbaae22d9e34e3f675997194342f866bb1d781da5d0be54783dce1ff"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36e9040a43d2017f2787b28d365a4bb33fcd792c7ff46a047a04094dc0e2a30d"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9f1627e162e3864a596486774876415a7410021f4b67fd2d9efdf93ade681afc"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7a0b42db2a47ecb488cde14e0f6c7679a2c5a9f44814393b162ff6397fcdfbb"}, + {file = "coverage-6.0.2-cp38-cp38-win32.whl", hash = "sha256:a1b73c7c4d2a42b9d37dd43199c5711d91424ff3c6c22681bc132db4a4afec6f"}, + {file = "coverage-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:1db67c497688fd4ba85b373b37cc52c50d437fd7267520ecd77bddbd89ea22c9"}, + {file = "coverage-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f2f184bf38e74f152eed7f87e345b51f3ab0b703842f447c22efe35e59942c24"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1cf1deb3d5544bd942356364a2fdc8959bad2b6cf6eb17f47d301ea34ae822"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad9b8c1206ae41d46ec7380b78ba735ebb77758a650643e841dd3894966c31d0"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:381d773d896cc7f8ba4ff3b92dee4ed740fb88dfe33b6e42efc5e8ab6dfa1cfe"}, + {file = "coverage-6.0.2-cp39-cp39-win32.whl", hash = "sha256:424c44f65e8be58b54e2b0bd1515e434b940679624b1b72726147cfc6a9fc7ce"}, + {file = "coverage-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:abbff240f77347d17306d3201e14431519bf64495648ca5a49571f988f88dee9"}, + {file = "coverage-6.0.2-pp36-none-any.whl", hash = "sha256:7092eab374346121805fb637572483270324407bf150c30a3b161fc0c4ca5164"}, + {file = "coverage-6.0.2-pp37-none-any.whl", hash = "sha256:30922626ce6f7a5a30bdba984ad21021529d3d05a68b4f71ea3b16bda35b8895"}, + {file = "coverage-6.0.2.tar.gz", hash = "sha256:6807947a09510dc31fa86f43595bf3a14017cd60bf633cc746d52141bfa6b149"}, +] dodgy = [ {file = "dodgy-0.2.1-py3-none-any.whl", hash = "sha256:51f54c0fd886fa3854387f354b19f429d38c04f984f38bc572558b703c0542a6"}, {file = "dodgy-0.2.1.tar.gz", hash = "sha256:28323cbfc9352139fdd3d316fa17f325cc0e9ac74438cbba51d70f9b48f86c3a"}, diff --git a/prospector.yaml b/prospector.yaml index 0e99e41..f7479e1 100644 --- a/prospector.yaml +++ b/prospector.yaml @@ -103,9 +103,9 @@ dodgy: bandit: run: true - options: + # options: # ignore assert warning - B101 + # - B101 mypy: run: true diff --git a/pyproject.toml b/pyproject.toml index 8bf09bb..3be3c93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ types-toml = "^0.10.1" pytest = "^6.2.5" pytest-datadir = "^1.3.1" pytest-xdist = "^2.4.0" +coverage = "^6.0.2" [build-system] requires = ["poetry>=0.12"] diff --git a/pytest.ini b/pytest.ini index bd07e3a..8661e8e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,3 @@ -; parallel tests with pytest-xdist -[pytest] -addopts=-n4 +; ; parallel tests with pytest-xdist +; [pytest] +; addopts=-n4 diff --git a/rewrite.todo b/rewrite.todo index b8e5604..b28f883 100644 --- a/rewrite.todo +++ b/rewrite.todo @@ -14,8 +14,9 @@ Coding: ✔ Logging path should take by default the config path unless overwritten? @done(21-10-16 23:49) Errors: - ☐ Check logging path + config path are valid, if not raise error. - ☐ Add tests for these. + ✔ Check logging path + config path are valid, if not raise error. @done(21-10-18 00:04) + ✔ Add tests for these. @done(21-10-18 00:04) + ✔ Check for a key > 2 levels, raise custom error, write test @done(21-10-17 23:30) Linting: ☐ Check all functions and annotations. @@ -23,21 +24,45 @@ Coding: Docstrings: ☐ Write the docstrings for public functions/methods. + Functionality: + ☐ When both a config file and a env var is found, use the env var. + Documentation: ☐ Rewrite documentation using `mkdocs` and using `.md`. @2h +Misc: + ☐ Use the python runner to build the docs & run the tests + ☐ document this in trilium Tests: - Config File: - ☐ - - Environment Variable: - ☐ + Bugfixes: + ✔ If loading from a default, don't covert to TOML @done(21-10-17 20:33) + ✔ Env Vars should be given as python objects @done(21-10-17 20:33) + The string or node provided may only consist of the following Python literal structures: strings, bytes, numbers, tuples, lists, dicts, sets, booleans, and None. + use ast.literal_eval() + https://docs.python.org/3/library/ast.html#ast.literal_eval __init__: - ☐ Test default config path set to "~/.config" - ☐ Test config path is set when passed in + ✔ Test default config path set to "~/.config" @done(21-10-17 17:25) + ✔ Test config path is set when passed in @done(21-10-17 17:25) config property: - ☐ Check testing config file is returned as dict - ☐ Check _self.missing_config and empty dict is returned + ✔ Check testing config file is returned as dict @done(21-10-17 17:25) + ✔ Check _self.missing_config and empty dict is returned @done(21-10-17 17:25) + + get_value: + config_file: + ✔ Arrays & tables loaded correctly from config file @done(21-10-17 20:34) + ✔ test when key length is 1 the value is returned @done(21-10-17 18:55) + ✔ test when key length is 2 the value is returned @done(21-10-17 18:55) + ✔ test when key not found and no env var default is loaded @done(21-10-17 19:01) + ✔ test bool's are properly converted @done(21-10-17 19:01) + ✔ test when key not found and env var is set value is loaded @done(21-10-17 20:43) + + env_var: + ✔ check if env key is missing the default is read in @done(21-10-17 20:55) + ✔ check if env key is present the values are read in @done(21-10-17 22:24) + ✔ parametrise a test to read in values form env vars and they're set correctly @done(21-10-17 22:24) + + library: + ✔ test set_config works @done(21-10-17 23:29) diff --git a/tests/conftest.py b/tests/conftest.py index 4a173d5..a2b59da 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,3 +4,20 @@ import pytest @pytest.fixture() def header(): return "panaetius_testing" + + +@pytest.fixture() +def testing_config_contents(): + return { + "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/without_logging/panaetius_testing/config.toml b/tests/data/without_logging/panaetius_testing/config.toml similarity index 83% rename from tests/test_config/without_logging/panaetius_testing/config.toml rename to tests/data/without_logging/panaetius_testing/config.toml index 02a7118..b4aa99e 100644 --- a/tests/test_config/without_logging/panaetius_testing/config.toml +++ b/tests/data/without_logging/panaetius_testing/config.toml @@ -7,6 +7,4 @@ some_second_int = 1 some_second_float = 1.0 some_second_list = ["some", "second", "value"] some_second_table = { "first" = ["some", "first", "value"] } - -# [panaetius_testing.logging] -# path = "" +some_second_table_bools = { "bool" = [true, false] } diff --git a/tests/scratchpad.py b/tests/scratchpad.py index 28949dc..8153517 100644 --- a/tests/scratchpad.py +++ b/tests/scratchpad.py @@ -1,54 +1,57 @@ import os from panaetius import Config, set_config, set_logger, SimpleLogger -# from panaetius.logging import AdvancedLogger + +from panaetius.logging import AdvancedLogger if __name__ == "__main__": - os.environ["PANAETIUS_TEST_PATH"] = "/usr/local" - os.environ["PANAETIUS_TEST_BOOL"] = "true" - print(os.environ.get("PANAETIUS_TEST_PATH")) + os.environ["PANAETIUS_TEST_PATH"] = '"/usr/local"' + os.environ["PANAETIUS_TEST_BOOL"] = "True" + # print(os.environ.get("PANAETIUS_TEST_PATH")) # os.environ[ # "PANAETIUS_TEST_TOML_POINTS" # ] = "[ { x = 1, y = 2, z = 3 }, { x = 7, y = 8, z = 9 }, { x = 2, y = 4, z = 8 }]" - os.environ["PANAETIUS_TEST_NOC_PATH"] = "/usr/locals" + os.environ["PANAETIUS_TEST_NOC_PATH"] = '"/usr/locals"' os.environ["PANAETIUS_TEST_NOC_FLOAT"] = "2.0" - os.environ["PANAETIUS_TEST_NOC_BOOL"] = "true" - os.environ["PANAETIUS_TEST_NOC_EMBEDDED_PATH"] = "/usr/local" + os.environ["PANAETIUS_TEST_NOC_BOOL"] = "True" + os.environ["PANAETIUS_TEST_NOC_EMBEDDED_PATH"] = '"/usr/local"' os.environ["PANAETIUS_TEST_NOC_EMBEDDED_FLOAT"] = "2.0" - os.environ["PANAETIUS_TEST_NOC_EMBEDDED_BOOL"] = "true" + os.environ["PANAETIUS_TEST_NOC_EMBEDDED_BOOL"] = "True" - # c = Config("panaetius_test") - c = Config("panaetius_test_noc") + c = Config("panaetius_test") + # c = Config("panaetius_test_noc") - set_config(c, key="toml.points", coerce=True) + set_config(c, key="toml.points") set_config(c, key="path", default="some path") set_config(c, key="top", default="some top") set_config(c, key="logging.path") set_config(c, key="nonexistent.item", default="some nonexistent item") set_config(c, key="nonexistent.item") set_config(c, key="toml.points_config") - set_config(c, key="float", coerce=True) + set_config(c, key="float") set_config(c, key="float_str", default="2.0") - set_config(c, key="bool", coerce=True) + set_config(c, key="bool") set_config(c, key="noexistbool", default=False) set_config(c, key="middle.middle") # set_config(c, key="path") - # set_config(c, key="float", coerce=True) - # set_config(c, key="bool", coerce=True) + # set_config(c, key="float") + # set_config(c, key="bool") # set_config(c, key="noexiststr", default="2.0") # set_config(c, key="noexistfloat", default=2.0) # set_config(c, key="noexistbool", default=False) set_config(c, key="embedded.path") - set_config(c, key="embedded.float", coerce=True) - set_config(c, key="embedded.bool", coerce=True) + set_config(c, key="embedded.float") + set_config(c, key="embedded.bool") set_config(c, key="embedded.noexiststr", default="2.0") set_config(c, key="embedded.noexistfloat", default=2.0) set_config(c, key="embedded.noexistbool", default=False) - logger = set_logger(c, SimpleLogger()) - # logger = set_logger(c, AdvancedLogger(logging_level="INFO")) + # logger = set_logger(c, SimpleLogger()) + logger = set_logger(c, AdvancedLogger(logging_level="DEBUG")) logger.info("test logging message") logger.debug("debugging message") + for i in dir(c): + logger.debug(i + ": " + str(getattr(c, i)) + " - " + str(type(getattr(c, i)))) diff --git a/tests/test_config.py b/tests/test_config.py index caa2cde..d9bb8ec 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,8 +1,13 @@ +import os import pathlib +from uuid import uuid4 -import toml +import pytest import panaetius +from panaetius.exceptions import KeyErrorTooDeepException + +# test config paths def test_default_config_path_set(header): @@ -13,9 +18,9 @@ def test_default_config_path_set(header): assert str(config.config_path) == str(pathlib.Path.home() / ".config") -def test_user_config_path_set(header, datadir): +def test_user_config_path_set(header, shared_datadir): # arrange - config_path = str(datadir / "without_logging") + config_path = str(shared_datadir / "without_logging") # act config = panaetius.Config(header, config_path) @@ -24,13 +29,182 @@ def test_user_config_path_set(header, datadir): assert str(config.config_path) == config_path -def test_config_file_exists(header, datadir): +# test config files + + +def test_config_file_exists(header, shared_datadir): # arrange - config_path = str(datadir / "without_logging") + config_path = str(shared_datadir / "without_logging") + + # act + config = panaetius.Config(header, config_path) + _ = config.config + + # assert + assert config._missing_config is False + + +def test_config_file_contents_read_success(header, shared_datadir, testing_config_contents): + # arrange + config_path = str(shared_datadir / "without_logging") # act config = panaetius.Config(header, config_path) config_contents = config.config # assert - assert config._missing_config == False + assert config_contents == testing_config_contents + + +@pytest.mark.parametrize( + "set_config_key,get_config_key,expected_value", + [ + ("some_top_string", "some_top_string", "some_top_value"), + ("second.some_second_string", "second_some_second_string", "some_second_value"), + ( + "second.some_second_list", + "second_some_second_list", + ["some", "second", "value"], + ), + ( + "second.some_second_table", + "second_some_second_table", + {"first": ["some", "first", "value"]}, + ), + ( + "second.some_second_table_bools", + "second_some_second_table_bools", + {"bool": [True, False]}, + ), + ], +) +def test_get_value_from_key( + set_config_key, get_config_key, expected_value, header, shared_datadir +): + """ + Test the following: + + - keys are read from top level key + - keys are read from two level key + - inline arrays are read correctly + - inline tables are read correctly + - inline tables & arrays read bools correctly + """ + # arrange + config_path = str(shared_datadir / "without_logging") + config = panaetius.Config(header, config_path) + panaetius.set_config(config, set_config_key) + + # act + config_value = getattr(config, get_config_key) + + # assert + assert config_value == expected_value + + +def test_key_level_too_deep(header, shared_datadir): + # arrange + config_path = str(shared_datadir / "without_logging") + config = panaetius.Config(header, config_path) + key = "a.key.too.deep" + + # act + with pytest.raises(KeyErrorTooDeepException) as key_error_too_deep: + panaetius.set_config(config, key) + + # assert + assert ( + str(key_error_too_deep.value) + == f"Your key of {key} can only be 2 levels deep maximum. " + f"You have 4" + ) + + +def test_get_value_missing_key_from_default(header, shared_datadir): + # arrange + config_path = str(shared_datadir / "without_logging") + config = panaetius.Config(header, config_path) + panaetius.set_config( + config, + "missing.key_from_default", + default=["some", "default", "value", 1.0, True], + ) + + # act + default_value = getattr(config, "missing_key_from_default") + + # assert + assert default_value == ["some", "default", "value", 1.0, True] + + +def test_get_value_missing_key_from_env(header, shared_datadir): + # arrange + os.environ[f"{header.upper()}_MISSING_KEY"] = '"some missing key"' + + config_path = str(shared_datadir / "without_logging") + config = panaetius.Config(header, config_path) + panaetius.set_config(config, "missing_key") + + # act + value_from_key = getattr(config, "missing_key") + + # assert + assert value_from_key == "some missing key" + + +# test env vars + + +def test_config_file_does_not_exist(header, shared_datadir): + # arrange + config_path = str(shared_datadir / "nonexistent_folder") + + # act + config = panaetius.Config(header, config_path) + config_contents = config.config + + # assert + assert config._missing_config is True + assert config_contents == {} + + +def test_missing_config_read_from_default(header, shared_datadir): + # arrange + config_path = str(shared_datadir / "nonexistent_folder") + + # act + config = panaetius.Config(header, config_path) + panaetius.set_config(config, "missing.key_read_from_default", default=True) + + # assert + assert getattr(config, "missing_key_read_from_default") is True + + +@pytest.mark.parametrize( + "env_value,expected_value", + [ + ('"a missing string"', "a missing string"), + ("1", 1), + ("1.0", 1.0), + ("True", True), + ( + '["an", "array", "of", "items", 1, True]', + ["an", "array", "of", "items", 1, True], + ), + ( + '{"an": "array", "of": "items", "1": True}', + {"an": "array", "of": "items", "1": True}, + ), + ], +) +def test_missing_config_read_from_env_var(env_value, expected_value, header, shared_datadir): + # arrange + config_path = str(shared_datadir / str(uuid4())) + os.environ[f"{header.upper()}_MISSING_KEY_READ_FROM_ENV_VAR"] = env_value + + # act + config = panaetius.Config(header, config_path) + panaetius.set_config(config, "missing.key_read_from_env_var") + + # assert + assert getattr(config, "missing_key_read_from_env_var") == expected_value diff --git a/tests/test_library.py b/tests/test_library.py new file mode 100644 index 0000000..2152e8f --- /dev/null +++ b/tests/test_library.py @@ -0,0 +1,13 @@ +import panaetius + + +def test_set_config(header, shared_datadir): + # arrange + config_path = str(shared_datadir / "without_logging") + + # act + config = panaetius.Config(header, config_path) + panaetius.set_config(config, "some_top_string") + + # assert + assert getattr(config, "some_top_string") == "some_top_value" diff --git a/tests/test_logging.py b/tests/test_logging.py new file mode 100644 index 0000000..b1344ff --- /dev/null +++ b/tests/test_logging.py @@ -0,0 +1,34 @@ +import logging +from uuid import uuid4 + +import pytest + +from panaetius import set_logger, SimpleLogger, Config, set_config +from panaetius.exceptions import LoggingDirectoryDoesNotExistException + + +def test_logging_directory_does_not_exist(header, shared_datadir): + # arrange + config = Config(header) + logging_path = str(shared_datadir / str(uuid4())) + set_config(config, "logging.path", default=str(logging_path)) + + # act + with pytest.raises(LoggingDirectoryDoesNotExistException) as logging_exception: + _ = set_logger(config, SimpleLogger()) + + # assert + assert str(logging_exception.value) == "" + + +def test_logging_directory_does_exist(header, shared_datadir): + # arrange + config = Config(header) + logging_path = str(shared_datadir / "without_logging") + set_config(config, "logging.path", default=str(logging_path)) + + # act + logger = set_logger(config, SimpleLogger()) + + # assert + assert isinstance(logger, logging.Logger)