mirror of
https://git.zavage.net/Zavage-Software/app_skellington.git
synced 2024-12-21 22:29:20 -07:00
style: black and isort
This commit is contained in:
parent
82543ce157
commit
25ded9e2b3
12
README.md
12
README.md
@ -11,7 +11,7 @@ Application framework for Python, features include:
|
||||
Principles:
|
||||
- Lend to creating beautiful, easy to read and understand code in the application.
|
||||
- Minimize coupling of applications to this framework.
|
||||
- Compatable with Linux, Windows, and Mac. Try to be compatible as possible otherwise.
|
||||
- Compatible with Linux, Windows, and Mac. Try to be compatible as possible otherwise.
|
||||
- Try to be compatible with alternate Python runtimes such as PyPy and older python environments. \*WIP
|
||||
|
||||
# PyPi Hosted Link
|
||||
@ -103,6 +103,13 @@ Install:
|
||||
pip install .
|
||||
```
|
||||
|
||||
Formatting and Linters:
|
||||
```
|
||||
black app_skellington
|
||||
isort app_skellington
|
||||
flake8 app_skellington
|
||||
```
|
||||
|
||||
|
||||
|
||||
# Version
|
||||
@ -123,7 +130,6 @@ MIT no attribution required - https://opensource.org/license/mit-0
|
||||
* Project page: https://zavage-software.com/portfolio/app_skellington
|
||||
* Please report bugs, improvements, or feedback!
|
||||
* Contact: mathew@zavage.net
|
||||
|
||||
|
||||
* Packing and distribution conforms to PEP 621 https://peps.python.org/pep-0621/
|
||||
* Reference https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/
|
||||
|
||||
|
@ -39,7 +39,9 @@ if os.environ.get("APPSKELLINGTON_DEBUG", None):
|
||||
handler = logging.StreamHandler()
|
||||
handler.setFormatter(fmt)
|
||||
_bootstrap_logger.addHandler(handler)
|
||||
_bootstrap_logger.debug("log - APPSKELLINGTON_DEBUG set in environment: Enabling verbose logging.")
|
||||
_bootstrap_logger.debug(
|
||||
"log - APPSKELLINGTON_DEBUG set in environment: Enabling verbose logging."
|
||||
)
|
||||
|
||||
# Logging is by default off, excepting CRITICAL
|
||||
else:
|
||||
|
@ -7,12 +7,7 @@ import sys
|
||||
|
||||
import appdirs
|
||||
|
||||
from . import (
|
||||
_util,
|
||||
cfg,
|
||||
cli,
|
||||
log,
|
||||
)
|
||||
from . import _util, cfg, cli, log
|
||||
|
||||
# Application scaffolding:
|
||||
from ._bootstrap import _bootstrap_logger
|
||||
@ -47,7 +42,9 @@ class ApplicationContainer:
|
||||
directories.
|
||||
"""
|
||||
|
||||
def __init__(self, configspec_filepath=None, configini_filepath=None, *args, **kwargs):
|
||||
def __init__(
|
||||
self, configspec_filepath=None, configini_filepath=None, *args, **kwargs
|
||||
):
|
||||
self.appname = kwargs.get("appname") or DEFAULT_APP_NAME
|
||||
self.appauthor = kwargs.get("appauthor") or DEFAULT_APP_AUTHOR
|
||||
|
||||
@ -100,7 +97,9 @@ class ApplicationContainer:
|
||||
app['datalayer'] => returns the made-up "datalayer" service.
|
||||
"""
|
||||
try:
|
||||
service_factory = self._dependencies[service_name] # Retrieve factory function
|
||||
service_factory = self._dependencies[
|
||||
service_name
|
||||
] # Retrieve factory function
|
||||
return service_factory() # Call factory() to return instance of service
|
||||
except KeyError as ex:
|
||||
msg = "failed to inject service: {}".format(service_name)
|
||||
@ -160,7 +159,9 @@ class ApplicationContainer:
|
||||
"""
|
||||
sig = inspect.signature(constructor.__init__)
|
||||
params = sig.parameters
|
||||
params = [params[paramname].name for paramname in params] # Convert Param() type => str
|
||||
params = [
|
||||
params[paramname].name for paramname in params
|
||||
] # Convert Param() type => str
|
||||
cls_dependencies = params[1:] # Skip 'self' parameter on class methods.
|
||||
|
||||
return functools.partial(self._construct_model, constructor, *cls_dependencies)
|
||||
|
@ -31,7 +31,9 @@ class Config:
|
||||
"allow_options_beyond_spec": True,
|
||||
}
|
||||
|
||||
def __init__(self, configspec_filepath=None, configini_filepath=None, capabilities=None):
|
||||
def __init__(
|
||||
self, configspec_filepath=None, configini_filepath=None, capabilities=None
|
||||
):
|
||||
self._config_obj = None # must be type configobj.ConfigObj()
|
||||
self._configini_data = None
|
||||
self._configini_filepath = None
|
||||
@ -93,11 +95,15 @@ class Config:
|
||||
self._configspec_filepath = filepath
|
||||
self._configspec_data = data
|
||||
self._has_changed_internally = True
|
||||
_bootstrap_logger.debug("cfg - Set configspec and read contents: %s", filepath)
|
||||
_bootstrap_logger.debug(
|
||||
"cfg - Set configspec and read contents: %s", filepath
|
||||
)
|
||||
self.load_config()
|
||||
return
|
||||
except OSError as ex:
|
||||
_bootstrap_logger.critical("cfg - Failed to find config.spec: file not found (%s)", filepath)
|
||||
_bootstrap_logger.critical(
|
||||
"cfg - Failed to find config.spec: file not found (%s)", filepath
|
||||
)
|
||||
raise OSError("Failed to read provided config.spec file")
|
||||
|
||||
self.load_config()
|
||||
@ -187,12 +193,16 @@ class Config:
|
||||
# raise_errors
|
||||
)
|
||||
_bootstrap_logger.debug(
|
||||
"cfg - Parsed configuration. config.spec = %s, config.ini = %s", config_spec, config_ini
|
||||
"cfg - Parsed configuration. config.spec = %s, config.ini = %s",
|
||||
config_spec,
|
||||
config_ini,
|
||||
)
|
||||
return True
|
||||
|
||||
except configobj.ParseError as ex:
|
||||
msg = "cfg - Failed to load config: error in config.spec configuration: {}".format(config_filepath)
|
||||
msg = "cfg - Failed to load config: error in config.spec configuration: {}".format(
|
||||
config_filepath
|
||||
)
|
||||
_bootstrap_logger.error(msg)
|
||||
return False
|
||||
except OSError as ex:
|
||||
@ -217,7 +227,9 @@ class Config:
|
||||
self._config_obj, configobj.ConfigObj
|
||||
), "expecting configobj.ConfigObj, received %s" % type(self._config_obj)
|
||||
# NOTE(MG) copy arg below instructs configobj to use defaults from spec file
|
||||
test_results = self._config_obj.validate(val, copy=True, preserve_errors=True)
|
||||
test_results = self._config_obj.validate(
|
||||
val, copy=True, preserve_errors=True
|
||||
)
|
||||
if test_results is True:
|
||||
_bootstrap_logger.info(
|
||||
"cfg- Successfully validated configuration against spec. input = %s, validation spec = %s",
|
||||
@ -227,19 +239,27 @@ class Config:
|
||||
return True
|
||||
|
||||
elif test_results is False:
|
||||
_bootstrap_logger.debug("cfg - Potentially discovered invalid config.spec")
|
||||
_bootstrap_logger.debug(
|
||||
"cfg - Potentially discovered invalid config.spec"
|
||||
)
|
||||
|
||||
else:
|
||||
self._validate_parse_errors(test_results)
|
||||
return False
|
||||
except ValueError as ex:
|
||||
_bootstrap_logger.error("cfg - Failed while validating config against spec. ")
|
||||
_bootstrap_logger.error(
|
||||
"cfg - Failed while validating config against spec. "
|
||||
)
|
||||
return False
|
||||
|
||||
def _validate_parse_errors(self, test_results):
|
||||
_bootstrap_logger.critical("cfg - Config file failed validation.")
|
||||
for section_list, key, rslt in configobj.flatten_errors(self._config_obj, test_results):
|
||||
_bootstrap_logger.critical("cfg - Config error info: %s %s %s", section_list, key, rslt)
|
||||
for section_list, key, rslt in configobj.flatten_errors(
|
||||
self._config_obj, test_results
|
||||
):
|
||||
_bootstrap_logger.critical(
|
||||
"cfg - Config error info: %s %s %s", section_list, key, rslt
|
||||
)
|
||||
if key is not None:
|
||||
_bootstrap_logger.critical(
|
||||
"cfg - Config failed validation: [%s].%s appears invalid. msg = %s",
|
||||
|
@ -75,7 +75,9 @@ class CommandTree:
|
||||
"""
|
||||
Adds an argument to the root parser.
|
||||
"""
|
||||
_bootstrap_logger.info("adding argument to root parser: %s and %s", args, kwargs)
|
||||
_bootstrap_logger.info(
|
||||
"adding argument to root parser: %s and %s", args, kwargs
|
||||
)
|
||||
self.root_parser.add_argument(*args, **kwargs)
|
||||
|
||||
def init_submenu(self, param_name, is_required=False):
|
||||
@ -89,7 +91,9 @@ class CommandTree:
|
||||
func_args = {"dest": param_name, "metavar": param_name, "required": is_required}
|
||||
if sys.version_info.major == 3 and sys.version_info.minor < 7:
|
||||
if is_required:
|
||||
_bootstrap_logger.warn("Unable to enforce required submenu: Requires >= Python 3.7")
|
||||
_bootstrap_logger.warn(
|
||||
"Unable to enforce required submenu: Requires >= Python 3.7"
|
||||
)
|
||||
del func_args["required"]
|
||||
# END fix for Python<3.7
|
||||
|
||||
@ -100,13 +104,17 @@ class CommandTree:
|
||||
submenu.submenu_path = ""
|
||||
submenu.var_name = param_name
|
||||
|
||||
_bootstrap_logger.info("Initialized root-level submenu: Parameter = '%s'", param_name)
|
||||
_bootstrap_logger.info(
|
||||
"Initialized root-level submenu: Parameter = '%s'", param_name
|
||||
)
|
||||
self.entries[param_name] = submenu
|
||||
self.submenu_param = param_name
|
||||
|
||||
return submenu
|
||||
|
||||
def register_command(self, func, cmd_name=None, func_signature=None, docstring=None):
|
||||
def register_command(
|
||||
self, func, cmd_name=None, func_signature=None, docstring=None
|
||||
):
|
||||
"""
|
||||
When no submenu functionality is desired, this links a single
|
||||
command into underlying argparse options.
|
||||
@ -154,7 +162,9 @@ class CommandTree:
|
||||
helptext = "default provided"
|
||||
else:
|
||||
helptext = "default = '{}'".format(param.default)
|
||||
self.root_parser.add_argument(key, help=helptext, nargs="?", default=param.default)
|
||||
self.root_parser.add_argument(
|
||||
key, help=helptext, nargs="?", default=param.default
|
||||
)
|
||||
else:
|
||||
helptext = "required"
|
||||
self.root_parser.add_argument(key, help=helptext)
|
||||
@ -230,16 +240,22 @@ class CommandTree:
|
||||
# the CommandTree with no SubMenu (submenu will be disabled
|
||||
# in this case):
|
||||
if self._cmd_tree_is_single_command:
|
||||
assert self._cmd_tree_is_single_command is True, "corrupt data structure in CommandMenu"
|
||||
assert (
|
||||
self._cmd_tree_is_single_command is True
|
||||
), "corrupt data structure in CommandMenu"
|
||||
assert self._entries is None, "corrupt data structure in CommandMenu"
|
||||
assert isinstance(self._single_command, CommandEntry), "corrupt data structure in CommandMenu"
|
||||
assert isinstance(
|
||||
self._single_command, CommandEntry
|
||||
), "corrupt data structure in CommandMenu"
|
||||
return self._single_command
|
||||
|
||||
# There is at least one submenu we need to go down:
|
||||
else:
|
||||
|
||||
assert self._single_command is None, "corrupt data structure in CommandMenu"
|
||||
assert self._cmd_tree_is_single_command == False, "corrupt data structure in CommandMenu"
|
||||
assert (
|
||||
self._cmd_tree_is_single_command == False
|
||||
), "corrupt data structure in CommandMenu"
|
||||
|
||||
# Key or variable name used by argparse to store the submenu options
|
||||
argparse_param = self.submenu_param # e.g.: submenu_root
|
||||
@ -251,10 +267,14 @@ class CommandTree:
|
||||
input("<broken>")
|
||||
|
||||
val = args.get(argparse_param)
|
||||
_bootstrap_logger.debug("cli - argparse command is '{}' = {}".format(argparse_param, val))
|
||||
_bootstrap_logger.debug(
|
||||
"cli - argparse command is '{}' = {}".format(argparse_param, val)
|
||||
)
|
||||
|
||||
lookup = submenu.entries.get(val)
|
||||
_bootstrap_logger.debug("cli - lookup, entries[{}] = {}".format(val, lookup))
|
||||
_bootstrap_logger.debug(
|
||||
"cli - lookup, entries[{}] = {}".format(val, lookup)
|
||||
)
|
||||
|
||||
# pop value
|
||||
del args[argparse_param]
|
||||
@ -297,7 +317,9 @@ class SubMenu:
|
||||
|
||||
self.entries = {}
|
||||
|
||||
def register_command(self, func, cmd_name=None, func_signature=None, docstring=None):
|
||||
def register_command(
|
||||
self, func, cmd_name=None, func_signature=None, docstring=None
|
||||
):
|
||||
"""
|
||||
Registers a command as an entry in this submenu. Provided function is
|
||||
converted into argparse arguments and made available to the user.
|
||||
@ -371,7 +393,9 @@ class SubMenu:
|
||||
helptext = "default provided"
|
||||
else:
|
||||
helptext = "default = '{}'".format(param.default)
|
||||
child_node.add_argument(key, help=helptext, nargs="?", default=param.default)
|
||||
child_node.add_argument(
|
||||
key, help=helptext, nargs="?", default=param.default
|
||||
)
|
||||
else:
|
||||
helptext = "required"
|
||||
child_node.add_argument(key, help=helptext)
|
||||
@ -422,7 +446,9 @@ class SubMenu:
|
||||
func_args = {"dest": var_name, "metavar": var_name, "required": is_required}
|
||||
if sys.version_info.major == 3 and sys.version_info.minor < 7:
|
||||
if is_required:
|
||||
_bootstrap_logger.warn("Unable to enforce required submenu: Requires >= Python 3.7")
|
||||
_bootstrap_logger.warn(
|
||||
"Unable to enforce required submenu: Requires >= Python 3.7"
|
||||
)
|
||||
del func_args["required"]
|
||||
# END fix for Python<3.7
|
||||
|
||||
|
@ -16,7 +16,13 @@ DEFAULT_LOG_SETTINGS = {
|
||||
"format": "%(white)s%(name)7s%(reset)s|%(log_color)s%(message)s",
|
||||
}
|
||||
},
|
||||
"handlers": {"stderr": {"class": "logging.StreamHandler", "level": "debug", "formatter": "colored"}},
|
||||
"handlers": {
|
||||
"stderr": {
|
||||
"class": "logging.StreamHandler",
|
||||
"level": "debug",
|
||||
"formatter": "colored",
|
||||
}
|
||||
},
|
||||
"loggers": {
|
||||
"root": {
|
||||
"handlers": [
|
||||
@ -65,7 +71,9 @@ class LoggingLayer:
|
||||
noise for typical operation.
|
||||
"""
|
||||
if config_dict is None:
|
||||
_bootstrap_logger.debug("log - No application logging configuration provided. Using default")
|
||||
_bootstrap_logger.debug(
|
||||
"log - No application logging configuration provided. Using default"
|
||||
)
|
||||
config_dict = DEFAULT_LOG_SETTINGS
|
||||
|
||||
self.transform_config(config_dict)
|
||||
@ -108,19 +116,26 @@ class LoggingLayer:
|
||||
config_dict["loggers"][""] = config_dict["loggers"]["root"]
|
||||
del config_dict["loggers"]["root"]
|
||||
except Exception as ex:
|
||||
_bootstrap_logger.warn("was not able to find and patch root logger configuration from arguments")
|
||||
_bootstrap_logger.warn(
|
||||
"was not able to find and patch root logger configuration from arguments"
|
||||
)
|
||||
|
||||
# Evaluate the full filepath of the file handler
|
||||
if "file" not in config_dict["handlers"]:
|
||||
return
|
||||
|
||||
if os.path.abspath(config_dict["handlers"]["file"]["filename"]) == config_dict["handlers"]["file"]["filename"]:
|
||||
if (
|
||||
os.path.abspath(config_dict["handlers"]["file"]["filename"])
|
||||
== config_dict["handlers"]["file"]["filename"]
|
||||
):
|
||||
# Path is already absolute
|
||||
pass
|
||||
else:
|
||||
dirname = appdirs.user_log_dir(self.appname, self.appauthor)
|
||||
_util.ensure_dir_exists(dirname)
|
||||
log_filepath = os.path.join(dirname, config_dict["handlers"]["file"]["filename"])
|
||||
log_filepath = os.path.join(
|
||||
dirname, config_dict["handlers"]["file"]["filename"]
|
||||
)
|
||||
config_dict["handlers"]["file"]["filename"] = log_filepath
|
||||
|
||||
def _add_own_logconfig(self, config_dict):
|
||||
@ -133,7 +148,10 @@ class LoggingLayer:
|
||||
# See _bootstrap.py
|
||||
if os.environ.get("APPSKELLINGTON_DEBUG", None):
|
||||
if _logger_name not in config_dict["loggers"]:
|
||||
config_dict["loggers"][_logger_name] = {"level": "debug", "propagate": "false"}
|
||||
config_dict["loggers"][_logger_name] = {
|
||||
"level": "debug",
|
||||
"propagate": "false",
|
||||
}
|
||||
else:
|
||||
config_dict["loggers"][_logger_name]["level"] = "debug"
|
||||
|
||||
|
@ -32,12 +32,18 @@ def sample_invalid_configspec_filepath():
|
||||
class TestConfig_e2e:
|
||||
def test_allows_reading_ini_and_no_spec(self, sample_configini_filepath):
|
||||
cfg = Config(configini_filepath=sample_configini_filepath)
|
||||
assert cfg["root_option"] == "root_option_val", "expecting default from config.spec (didnt get)"
|
||||
assert cfg["app"]["sub_option"] == "sub_option_val", "expecting default for sub option"
|
||||
assert (
|
||||
cfg["root_option"] == "root_option_val"
|
||||
), "expecting default from config.spec (didnt get)"
|
||||
assert (
|
||||
cfg["app"]["sub_option"] == "sub_option_val"
|
||||
), "expecting default for sub option"
|
||||
|
||||
def test_allows_reading_spec_and_no_ini(self, sample_configspec_filepath):
|
||||
cfg = Config(configspec_filepath=sample_configspec_filepath)
|
||||
assert cfg["root_option"] == "def_string", "expecting default from config.spec (didnt get)"
|
||||
assert (
|
||||
cfg["root_option"] == "def_string"
|
||||
), "expecting default from config.spec (didnt get)"
|
||||
|
||||
# NOTE(MG) Changed the functionality to not do it this way.
|
||||
# def test_constructor_fails_with_invalid_spec(
|
||||
@ -65,7 +71,9 @@ class TestConfig_e2e:
|
||||
assert cfg["root_option"] == "newval"
|
||||
|
||||
cfg["app"]["sub_option"] = "another_new_val"
|
||||
assert cfg["app"]["sub_option"] == "another_new_val", "expecting default for sub option"
|
||||
assert (
|
||||
cfg["app"]["sub_option"] == "another_new_val"
|
||||
), "expecting default for sub option"
|
||||
|
||||
def test_can_set_option_without_config(self):
|
||||
cfg = Config()
|
||||
@ -78,5 +86,7 @@ class TestConfig_e2e:
|
||||
|
||||
def test_uses_spec_as_defaults(self, sample_configspec_filepath):
|
||||
cfg = Config(configspec_filepath=sample_configspec_filepath)
|
||||
assert cfg["root_option"] == "def_string", "expecting default from config.spec (didnt get)"
|
||||
assert (
|
||||
cfg["root_option"] == "def_string"
|
||||
), "expecting default from config.spec (didnt get)"
|
||||
assert cfg["app"]["sub_option"] == "def_sub", "expecting default for sub option"
|
||||
|
Loading…
Reference in New Issue
Block a user