From ad840e6b27d392ba8364dd426ce2db4e75f2eaee Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Mon, 18 Oct 2021 02:31:17 +0100 Subject: [PATCH] adding latest testing + docstrings --- panaetius/__init__.py | 6 +++++ panaetius/config.py | 57 ++++++++++++++++++++++++++++++++++++++++- panaetius/exceptions.py | 4 +++ panaetius/library.py | 31 +++++++++++++++++++++- panaetius/logging.py | 46 +++++++++++++++++++++++++++++++++ prospector.yaml | 25 +++++++++--------- rewrite.todo | 4 +-- tests/scratchpad.py | 8 +++--- 8 files changed, 161 insertions(+), 20 deletions(-) diff --git a/panaetius/__init__.py b/panaetius/__init__.py index 500f8d6..6a0c396 100644 --- a/panaetius/__init__.py +++ b/panaetius/__init__.py @@ -1,3 +1,9 @@ +""" +panaetius - a utility library to read variables and provide convenient logging. + +Author: Daniel Tomlinson (dtomlinson@panaetius.co.uk) +""" + from panaetius.config import Config from panaetius.library import set_config from panaetius.logging import set_logger, SimpleLogger, AdvancedLogger, CustomLogger diff --git a/panaetius/config.py b/panaetius/config.py index 193f287..d91aa4e 100644 --- a/panaetius/config.py +++ b/panaetius/config.py @@ -1,3 +1,10 @@ +""" +Access variables from a config file or an environment variable. + +This module defines the `Config` class to interact and read variables from either a +`config.toml` or an environment variable. +""" + from __future__ import annotations import ast @@ -11,9 +18,21 @@ from panaetius.exceptions import KeyErrorTooDeepException, InvalidPythonExceptio class Config: - """docstring for Config().""" + """The configuration class to access variables.""" def __init__(self, header_variable: str, config_path: str = "") -> 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`. + + Example: + A header of `data_analysis` with a config_path of `~/myapps` will define + a config file in `~/myapps/data_analysis/config.toml`. + """ self.header_variable = header_variable self.config_path = ( pathlib.Path(config_path) @@ -29,6 +48,12 @@ class Config: @property def config(self) -> dict: + """ + Return the contents of the config file. If missing returns an empty dictionary. + + Returns: + dict: The contents of the `.toml` loaded as a python dictionary. + """ config_file_location = self.config_path / self.header_variable / "config.toml" try: with open(config_file_location, "r", encoding="utf-8") as config_file: @@ -37,6 +62,36 @@ class Config: return {} def get_value(self, key: str, default: Any) -> Any: + """ + Get the value of a variable from the key name. + + The key can either be one (`value`) or two (`data.value`) levels deep. + + A key of (`value`) (with a header of `data_analysis`) would refer to a + `config.toml` of: + + ``` + [data_analysis] + value = "some value" + ``` + + or an environment variable of `DATA_ANALYSIS_VALUE="'some value'"`. + + A key of (`data.value`) would refer to a `config.toml` of: + ``` + [data_analysis.data] + value = "some value" + ``` + or an environment variable of `DATA_ANALYSIS_DATA_VALUE="'some value'"`. + + Args: + key (str): The key of the variable. + default (Any): The default value if the key cannot be found in the config + file, or an environment variable. + + Returns: + Any: The value of the variable. + """ env_key = f"{self.header_variable.upper()}_{key.upper().replace('.', '_')}" if not self._missing_config: diff --git a/panaetius/exceptions.py b/panaetius/exceptions.py index fbaea3a..0e03564 100644 --- a/panaetius/exceptions.py +++ b/panaetius/exceptions.py @@ -1,3 +1,6 @@ +"""Exceptions for the module.""" + + class KeyErrorTooDeepException(Exception): pass @@ -5,5 +8,6 @@ class KeyErrorTooDeepException(Exception): class LoggingDirectoryDoesNotExistException(Exception): pass + class InvalidPythonException(Exception): pass diff --git a/panaetius/library.py b/panaetius/library.py index f624159..ffea4c5 100644 --- a/panaetius/library.py +++ b/panaetius/library.py @@ -1,3 +1,5 @@ +"""Module to provide functionality when interacting with variables.""" + from __future__ import annotations from typing import Any @@ -9,6 +11,33 @@ def set_config( config_inst: Config, key: str, default: Any = None, -): +) -> None: + """ + Define a variable to be read from a `config.toml` or an environment variable. + + Args: + config_inst (Config): The instance of the `Config` class. + key (str): The key of the variable. + default (Any, optional): The default value if the key cannot be found in the config + file, or an environment variable. Defaults to None. + + Example: + `set_config(CONFIG, "value", default=[1, 2])` would look for a + `config.toml` with the following structure (with `CONFIG` having a header of + `data_analysis`): + + ``` + [data_analysis] + value = "some value" + ``` + + Or an environment variable of `DATA_ANALYSIS_VALUE="'some value'"`. + + If found, this value can be access with `CONFIG.value` which would return + `some_value`. + + If neither the environment variable nor the `config.toml` are present, the + default of `[1, 2]` would be returned instead. + """ config_var = key.lower().replace(".", "_") setattr(config_inst, config_var, config_inst.get_value(key, default)) diff --git a/panaetius/logging.py b/panaetius/logging.py index 4b86b3a..08f5221 100644 --- a/panaetius/logging.py +++ b/panaetius/logging.py @@ -1,3 +1,5 @@ +"""Module to define a convenient logger instance with json formatted output.""" + from __future__ import annotations from abc import ABCMeta, abstractmethod @@ -12,6 +14,50 @@ from panaetius.exceptions import LoggingDirectoryDoesNotExistException def set_logger(config_inst: Config, logging_format_inst: LoggingData) -> logging.Logger: + """ + Set and return a `logging.Logger` instance for quick logging. + + `logging_format_inst` should be an instance of either SimpleLogger, AdvancedLogger, + or CustomLogger. + + SimpleLogger and AdvancedLogger define a logging format and a logging level info. + + CustomLogger defines a logging level info and should have a logging format passed + in. + + Logging to a file is defined by a `logging.path` key set on `Config`. This path + should exist as it will not be created. + + Args: + config_inst (Config): The instance of the `Config` class. + logging_format_inst (LoggingData): The instance of the `LoggingData` class. + + Raises: + LoggingDirectoryDoesNotExistException: If the logging directory specified does + not exist. + + Returns: + logging.Logger: An configured instance of `logging.Logger` ready to be used. + + Example: + + ``` + logger = set_logger(CONFIG, SimpleLogger()) + + logger.info("some logging message") + ``` + + Would create a logging output of: + + ``` + { + "time": "2021-10-18 02:26:24,037", + "logging_level":"INFO", + "message": "some logging message" + } + ``` + + """ logger = logging.getLogger(config_inst.header_variable) log_handler_sys = logging.StreamHandler(sys.stdout) diff --git a/prospector.yaml b/prospector.yaml index f7479e1..03a75e7 100644 --- a/prospector.yaml +++ b/prospector.yaml @@ -12,9 +12,9 @@ pylint: # disables TODO warnings - fixme # !doc docstrings - - missing-module-docstring - - missing-class-docstring - - missing-function-docstring + # - missing-module-docstring + # - missing-class-docstring + # - missing-function-docstring # ! doc end of docstrings # disables warnings about abstract methods not overridden - abstract-method @@ -67,23 +67,24 @@ pep257: disable: # !doc docstrings # Missing docstring in __init__ - - D107 + # - D107 # Missing docstring in public module - - D100 + # - D100 # Missing docstring in public class - - D101 + # - D101 # Missing docstring in public method - - D102 + # - D102 # Missing docstring in public function - - D103 + # - D103 + # Multi-line docstring summary should start at the second line + # - D213 + # First word of the docstring should not be This + # - D404 + # DEFAULT IGNORES # 1 blank line required before class docstring - D203 # Multi-line docstring summary should start at the first line - D212 - # Multi-line docstring summary should start at the second line - - D213 - # First word of the docstring should not be This - - D404 # !doc end of docstrings # Section name should end with a newline - D406 diff --git a/rewrite.todo b/rewrite.todo index f3f3a44..c432a16 100644 --- a/rewrite.todo +++ b/rewrite.todo @@ -19,10 +19,10 @@ Coding: ✔ Check for a key > 2 levels, raise custom error, write test @done(21-10-17 23:30) Linting: - ☐ Check all functions and annotations. + ✔ Check all functions and annotations. @done(21-10-18 01:07) Docstrings: - ☐ Write the docstrings for public functions/methods. + ✔ Write the docstrings for public functions/methods. @done(21-10-18 02:29) Functionality: ✔ When both a config file and a env var is found, use the env var. @done(21-10-18 00:38) diff --git a/tests/scratchpad.py b/tests/scratchpad.py index 8153517..6141857 100644 --- a/tests/scratchpad.py +++ b/tests/scratchpad.py @@ -50,8 +50,8 @@ if __name__ == "__main__": set_config(c, key="embedded.noexistbool", default=False) # logger = set_logger(c, SimpleLogger()) - logger = set_logger(c, AdvancedLogger(logging_level="DEBUG")) - logger.info("test logging message") + logger = set_logger(c, SimpleLogger(logging_level="DEBUG")) + logger.info("some logging message") logger.debug("debugging message") - for i in dir(c): - logger.debug(i + ": " + str(getattr(c, i)) + " - " + str(type(getattr(c, i)))) + # for i in dir(c): + # logger.debug(i + ": " + str(getattr(c, i)) + " - " + str(type(getattr(c, i))))