mirror of
https://github.com/dtomlinson91/panaetius.git
synced 2025-12-21 20:55:43 +00:00
Merge branch 'feature/skip_header_in_config_init' into develop
This commit is contained in:
7
.coveragerc
Normal file
7
.coveragerc
Normal file
@@ -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
|
||||
@@ -15,31 +15,41 @@ from typing import Any
|
||||
# import toml
|
||||
import yaml
|
||||
|
||||
from panaetius.exceptions import KeyErrorTooDeepException, InvalidPythonException
|
||||
from panaetius.exceptions import KeyErrorTooDeepException
|
||||
|
||||
|
||||
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
|
||||
a config file in `~/myapps/data_analysis/config.yml`.
|
||||
"""
|
||||
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 +65,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 +118,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
|
||||
@@ -165,7 +183,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
|
||||
|
||||
@@ -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):
|
||||
|
||||
14
rewrite.todo
14
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)
|
||||
|
||||
9
tests/data/without_header/config.yml
Normal file
9
tests/data/without_header/config.yml
Normal file
@@ -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] }
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user