mirror of
https://git.zavage.net/Zavage-Software/app_skellington.git
synced 2024-12-21 14:19:21 -07:00
refactor: ran black and isort
This commit is contained in:
parent
586e7fa54d
commit
9a1999b8ab
@ -17,10 +17,10 @@ repos:
|
||||
args: ["--profile", "black", "--filter-files"]
|
||||
|
||||
# Flake8
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: '7.0.0'
|
||||
hooks:
|
||||
- id: flake8
|
||||
#- repo: https://github.com/pycqa/flake8
|
||||
# rev: '7.0.0'
|
||||
# hooks:
|
||||
# - id: flake8
|
||||
|
||||
# Black
|
||||
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
|
||||
|
@ -5,4 +5,3 @@ from .app_container import *
|
||||
from .cfg import *
|
||||
from .cli import *
|
||||
from .log import *
|
||||
|
||||
|
@ -3,24 +3,28 @@ import os
|
||||
import sys
|
||||
|
||||
# Check and gracefully fail if the user needs to install a 3rd-party dep.
|
||||
libnames = ['appdirs', 'configobj', 'colorlog']
|
||||
libnames = ["appdirs", "configobj", "colorlog"]
|
||||
|
||||
|
||||
def check_env_has_dependencies(libnames):
|
||||
rc = True
|
||||
for libname in libnames:
|
||||
try:
|
||||
__import__(libname)
|
||||
except ModuleNotFoundError as ex:
|
||||
print('Missing third-party library: ', ex, file=sys.stderr)
|
||||
print("Missing third-party library: ", ex, file=sys.stderr)
|
||||
rc = False
|
||||
return rc
|
||||
|
||||
|
||||
if not check_env_has_dependencies(libnames):
|
||||
print('Unable to load program without installed dependencies', file=sys.stderr)
|
||||
raise ImportError('python environment needs third-party dependencies installed')
|
||||
print("Unable to load program without installed dependencies", file=sys.stderr)
|
||||
raise ImportError("python environment needs third-party dependencies installed")
|
||||
|
||||
# Logger for before the application and logging config is loaded
|
||||
# - used to log before logging is configured
|
||||
_log_fmt = '%(levelname)-7s:%(message)s'
|
||||
_logger_name = 'skell'
|
||||
_log_fmt = "%(levelname)-7s:%(message)s"
|
||||
_logger_name = "skell"
|
||||
_bootstrap_logger = logging.getLogger(_logger_name)
|
||||
|
||||
# NOTE(MG) Logger monkey-patch:
|
||||
@ -29,13 +33,13 @@ _bootstrap_logger = logging.getLogger(_logger_name)
|
||||
# configuration is reloaded. This catches APPSKELLINGTON_DEBUG
|
||||
# environment variable the first time, as app_skellington module
|
||||
# is imported. See cfg.py
|
||||
if os.environ.get('APPSKELLINGTON_DEBUG', None):
|
||||
if os.environ.get("APPSKELLINGTON_DEBUG", None):
|
||||
_bootstrap_logger.setLevel(logging.DEBUG) # Don't filter any log levels
|
||||
fmt = logging.Formatter(_log_fmt)
|
||||
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:
|
||||
@ -45,4 +49,3 @@ _bootstrap_logger.propagate = False
|
||||
# NOTE(MG) Pretty sure the logger has the default handler too at this point.
|
||||
# It's been related to some issues with the logger double-printing messages.
|
||||
_bootstrap_logger.addHandler(logging.NullHandler())
|
||||
|
||||
|
@ -1,16 +1,19 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
|
||||
from . import _util
|
||||
|
||||
|
||||
def eprint(*args, **kwargs):
|
||||
"""
|
||||
Print to STDERR stream.
|
||||
"""
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
|
||||
def filename_to_abspath(filename):
|
||||
"""
|
||||
Converts a filename to it's absolute path. If it's already an
|
||||
@ -18,6 +21,7 @@ def filename_to_abspath(filename):
|
||||
"""
|
||||
return os.path.abspath(filename)
|
||||
|
||||
|
||||
def does_file_exist(filepath):
|
||||
"""
|
||||
Because the file can be deleted or created immediately after execution of
|
||||
@ -26,31 +30,31 @@ def does_file_exist(filepath):
|
||||
instant in execution.
|
||||
"""
|
||||
try:
|
||||
fp = open(filepath, 'r')
|
||||
fp = open(filepath, "r")
|
||||
return True
|
||||
except FileNotFoundError as ex:
|
||||
return False
|
||||
|
||||
|
||||
def ensure_dir_exists(dirpath):
|
||||
if dirpath is None:
|
||||
return
|
||||
if dirpath == '':
|
||||
if dirpath == "":
|
||||
return
|
||||
os.makedirs(dirpath, exist_ok=True)
|
||||
|
||||
|
||||
def get_root_asset(filepath):
|
||||
"""
|
||||
Attempts to locate a resource or asset shipped with the application.
|
||||
Searches starting at the root module (__main__) which should be the
|
||||
python file initially invoked.
|
||||
"""
|
||||
module_root =\
|
||||
os.path.abspath(
|
||||
os.path.dirname(
|
||||
sys.modules['__main__'].__file__))
|
||||
module_root = os.path.abspath(os.path.dirname(sys.modules["__main__"].__file__))
|
||||
path = os.path.join(module_root, filepath)
|
||||
return path
|
||||
|
||||
|
||||
def get_asset(module, filepath):
|
||||
"""
|
||||
Attempts to locate a resource or asset shipped with the application.
|
||||
@ -75,7 +79,7 @@ def get_asset(module, filepath):
|
||||
elif isinstance(module, module):
|
||||
module_file = module.__file__
|
||||
else:
|
||||
raise Exception('Invalid Usage')
|
||||
raise Exception("Invalid Usage")
|
||||
|
||||
try:
|
||||
root = module_file
|
||||
@ -90,6 +94,7 @@ def get_asset(module, filepath):
|
||||
path = os.path.join(root, filepath)
|
||||
return path
|
||||
|
||||
|
||||
def register_class_as_commands(app, submenu, cls_object):
|
||||
"""
|
||||
Registers commands for each class method. e.g.: pass in the CLI
|
||||
@ -105,7 +110,7 @@ def register_class_as_commands(app, submenu, cls_object):
|
||||
for m in members:
|
||||
name = m[0]
|
||||
ref = m[1]
|
||||
if inspect.isfunction(ref) and not name.startswith('_'):
|
||||
if inspect.isfunction(ref) and not name.startswith("_"):
|
||||
cls_method = ref
|
||||
constructor = app._inject_service_dependencies(cls_constructor)
|
||||
sig = inspect.signature(cls_method)
|
||||
@ -114,9 +119,10 @@ def register_class_as_commands(app, submenu, cls_object):
|
||||
docstring = inspect.getdoc(cls_method)
|
||||
submenu.register_command(func, name, sig, docstring)
|
||||
|
||||
|
||||
def create_func(constructor, cls_method):
|
||||
def func(*args, **kwargs):
|
||||
cmd_class_instance = constructor()
|
||||
return cls_method(cmd_class_instance, *args, **kwargs)
|
||||
return func
|
||||
|
||||
return func
|
||||
|
@ -1,34 +1,40 @@
|
||||
import appdirs
|
||||
import collections
|
||||
import functools
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
import appdirs
|
||||
|
||||
from . import (
|
||||
_util,
|
||||
cfg,
|
||||
cli,
|
||||
log,
|
||||
)
|
||||
|
||||
# Application scaffolding:
|
||||
from ._bootstrap import _bootstrap_logger
|
||||
from . import log
|
||||
from . import _util
|
||||
from . import cli
|
||||
from . import cfg
|
||||
|
||||
import logging
|
||||
|
||||
# These two variables affect the directory paths for
|
||||
# config files and logging.
|
||||
DEFAULT_APP_NAME = ''
|
||||
DEFAULT_APP_AUTHOR = ''
|
||||
DEFAULT_APP_NAME = ""
|
||||
DEFAULT_APP_AUTHOR = ""
|
||||
|
||||
|
||||
class ApplicationContext:
|
||||
"""
|
||||
Container for application-wide state; i.e. app configuration and loggers.
|
||||
"""
|
||||
|
||||
def __init__(self, config, log):
|
||||
self.config = config
|
||||
self.log = log
|
||||
self.parsed_argv = None
|
||||
self.parsed_argv_unknown = None
|
||||
|
||||
|
||||
class ApplicationContainer:
|
||||
"""
|
||||
Generalized application functionality. Used for linking components and modules of the application
|
||||
@ -40,14 +46,10 @@ class ApplicationContainer:
|
||||
Override appname and appauthor arguments to direct config and log
|
||||
directories.
|
||||
"""
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# Instantiate application context which contains
|
||||
# global state, configuration, loggers, and runtime args.
|
||||
@ -57,29 +59,28 @@ class ApplicationContainer:
|
||||
|
||||
logger = log.LoggingLayer(self.appname, self.appauthor)
|
||||
# Try and load logging configuration if provided
|
||||
log_config = config.get('logging')
|
||||
log_config = config.get("logging")
|
||||
if log_config is not None:
|
||||
logger.configure_logging(log_config)
|
||||
else:
|
||||
logger.configure_logging()
|
||||
|
||||
|
||||
self.ctx = ApplicationContext(config, logger)
|
||||
|
||||
# Reference to root_app avail. in context
|
||||
self.ctx.root_app = self
|
||||
|
||||
# Reference to context service avail. in root_app
|
||||
self['ctx'] = lambda: self.ctx
|
||||
self["ctx"] = lambda: self.ctx
|
||||
|
||||
self.cli = cli.CommandTree() # Command-line interface
|
||||
|
||||
# Run methods if subclass implemented them:
|
||||
if callable(getattr(self, '_cli_options', None)):
|
||||
if callable(getattr(self, "_cli_options", None)):
|
||||
self._cli_options()
|
||||
if callable(getattr(self, '_services', None)):
|
||||
if callable(getattr(self, "_services", None)):
|
||||
self._services()
|
||||
if callable(getattr(self, '_command_menu', None)):
|
||||
if callable(getattr(self, "_command_menu", None)):
|
||||
self._command_menu()
|
||||
|
||||
def __delitem__(self, service_name):
|
||||
@ -102,7 +103,7 @@ class ApplicationContainer:
|
||||
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)
|
||||
msg = "failed to inject service: {}".format(service_name)
|
||||
_bootstrap_logger.critical(msg)
|
||||
raise ServiceNotFound
|
||||
|
||||
@ -131,9 +132,7 @@ class ApplicationContainer:
|
||||
dependencies.append(self[dep_name])
|
||||
return model_constructor(*dependencies)
|
||||
|
||||
def _get_config_filepath(
|
||||
self, app_name, app_author, config_filename='config.ini'
|
||||
):
|
||||
def _get_config_filepath(self, app_name, app_author, config_filename="config.ini"):
|
||||
"""
|
||||
Attempt to find config.ini in the user's config directory.
|
||||
|
||||
@ -142,10 +141,10 @@ class ApplicationContainer:
|
||||
"""
|
||||
dirname = appdirs.user_config_dir(app_name, app_author)
|
||||
filepath = os.path.join(dirname, config_filename)
|
||||
_bootstrap_logger.info('default config filepath calculated to be: %s', filepath)
|
||||
_bootstrap_logger.info("default config filepath calculated to be: %s", filepath)
|
||||
return filepath
|
||||
|
||||
def _get_configspec_filepath(self, configspec_filename='config.spec'):
|
||||
def _get_configspec_filepath(self, configspec_filename="config.spec"):
|
||||
"""
|
||||
Attempt to find config.spec inside the installed package directory.
|
||||
"""
|
||||
@ -181,7 +180,7 @@ class ApplicationContainer:
|
||||
try:
|
||||
self.cli.run_command()
|
||||
except NoCommandSpecified as ex:
|
||||
print('Failure: No command specified.')
|
||||
print("Failure: No command specified.")
|
||||
|
||||
def interactive_shell(self):
|
||||
pass
|
||||
@ -193,12 +192,14 @@ class ApplicationContainer:
|
||||
pass
|
||||
# Applications need a default usage
|
||||
|
||||
|
||||
class ServiceNotFound(Exception):
|
||||
"""
|
||||
Application framework error: unable to find and inject dependency.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NoCommandSpecified(Exception):
|
||||
pass
|
||||
|
||||
|
@ -4,15 +4,17 @@
|
||||
# ConfigObj module and it's recommended to use config.spec files to define
|
||||
# your available configuration of the relevant application.
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
import appdirs
|
||||
import configobj
|
||||
import validate
|
||||
|
||||
from . import _util
|
||||
from ._bootstrap import _bootstrap_logger
|
||||
|
||||
import appdirs
|
||||
import argparse
|
||||
import configobj
|
||||
import os
|
||||
import sys
|
||||
import validate
|
||||
|
||||
class Config:
|
||||
"""
|
||||
@ -26,15 +28,10 @@ class Config:
|
||||
"""
|
||||
|
||||
DEFAULT_CAPABILITIES = {
|
||||
'allow_options_beyond_spec': True,
|
||||
"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
|
||||
@ -83,9 +80,7 @@ class Config:
|
||||
@configspec_filepath.setter
|
||||
def configspec_filepath(self, filepath):
|
||||
if filepath is None:
|
||||
_bootstrap_logger.debug(
|
||||
'cfg - Clearing configspec'
|
||||
)
|
||||
_bootstrap_logger.debug("cfg - Clearing configspec")
|
||||
self._configspec_filepath = None
|
||||
self._configspec_data = None
|
||||
self._has_changed_internally = True
|
||||
@ -98,18 +93,12 @@ 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
|
||||
)
|
||||
raise OSError('Failed to read provided config.spec file')
|
||||
_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()
|
||||
|
||||
@ -164,9 +153,7 @@ class Config:
|
||||
except KeyError as ex:
|
||||
return default
|
||||
|
||||
def load_config(
|
||||
self, configspec_filepath=None, configini_filepath=None
|
||||
):
|
||||
def load_config(self, configspec_filepath=None, configini_filepath=None):
|
||||
# Set new arguments if were passed in:
|
||||
if configspec_filepath is not None:
|
||||
self.configspec_filepath = configspec_filepath
|
||||
@ -179,7 +166,7 @@ class Config:
|
||||
rc = self._validate_config_against_spec()
|
||||
if not rc:
|
||||
if self._capability_enforce_strict_spec_validation:
|
||||
raise RuntimeError('Failed to validate config.ini against spec.')
|
||||
raise RuntimeError("Failed to validate config.ini against spec.")
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -196,21 +183,20 @@ class Config:
|
||||
# options
|
||||
configspec=config_spec,
|
||||
# encoding
|
||||
interpolation='template'
|
||||
interpolation="template",
|
||||
# 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:
|
||||
msg = 'cfg - Failed to load config: config.spec file not found.'
|
||||
msg = "cfg - Failed to load config: config.spec file not found."
|
||||
_bootstrap_logger.error(msg)
|
||||
return False
|
||||
except Exception as ex:
|
||||
@ -221,58 +207,66 @@ class Config:
|
||||
config_ini = self.configini_filepath
|
||||
|
||||
# Hack the configobj module to alter the interpolation for validate.py:
|
||||
configobj.DEFAULT_INTERPOLATION = 'template'
|
||||
configobj.DEFAULT_INTERPOLATION = "template"
|
||||
|
||||
# Validate config.ini against config.spec
|
||||
try:
|
||||
_bootstrap_logger.info('cfg - Validating config file against spec')
|
||||
_bootstrap_logger.info("cfg - Validating config file against spec")
|
||||
val = validate.Validator()
|
||||
assert isinstance(self._config_obj, configobj.ConfigObj), 'expecting configobj.ConfigObj, received %s' % type(self._config_obj)
|
||||
assert isinstance(
|
||||
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',
|
||||
config_ini, config_spec
|
||||
"cfg- Successfully validated configuration against spec. input = %s, validation spec = %s",
|
||||
config_ini,
|
||||
config_spec,
|
||||
)
|
||||
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)
|
||||
_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)
|
||||
if key is not None:
|
||||
_bootstrap_logger.critical('cfg - Config failed validation: [%s].%s appears invalid. msg = %s', '.'.join(section_list), key, rslt)
|
||||
_bootstrap_logger.critical(
|
||||
"cfg - Config failed validation: [%s].%s appears invalid. msg = %s",
|
||||
".".join(section_list),
|
||||
key,
|
||||
rslt,
|
||||
)
|
||||
else:
|
||||
_bootstrap_logger.critical("cfg - Config failed validation: missing section, name = '%s'. msg = %s", '.'.join(section_list), rslt)
|
||||
_bootstrap_logger.critical(
|
||||
"cfg - Config failed validation: missing section, name = '%s'. msg = %s",
|
||||
".".join(section_list),
|
||||
rslt,
|
||||
)
|
||||
|
||||
def print_config(self):
|
||||
"""
|
||||
Print configuration to stdout.
|
||||
"""
|
||||
print('config:')
|
||||
print("config:")
|
||||
|
||||
self._config_obj.walk(print)
|
||||
for section in self._config_obj.sections:
|
||||
print(section)
|
||||
for key in self._config_obj[section]:
|
||||
print(' ', self._config_obj[section][key])
|
||||
print(" ", self._config_obj[section][key])
|
||||
|
||||
|
||||
class EnvironmentVariables:
|
||||
def __init__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -5,14 +5,16 @@ import re
|
||||
import sys
|
||||
|
||||
import app_skellington
|
||||
from ._bootstrap import _bootstrap_logger
|
||||
|
||||
from . import app_container
|
||||
from ._bootstrap import _bootstrap_logger
|
||||
|
||||
# If explicit fail is enabled, any command with at least one unknown
|
||||
# argument will be rejected entirely. If not enabled, unknown arguments
|
||||
# will be ignored.
|
||||
EXPLICIT_FAIL_ON_UNKNOWN_ARGS = True
|
||||
|
||||
|
||||
class CommandTree:
|
||||
"""
|
||||
Command-line interface to hold a menu of commands. You can register
|
||||
@ -51,6 +53,7 @@ class CommandTree:
|
||||
the second. In the same way the -h, --help options print different docs
|
||||
depending on where the help option was passed.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.root_parser = argparse.ArgumentParser()
|
||||
self.submenu_param = None # submenu_param is the variable name
|
||||
@ -72,7 +75,7 @@ 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):
|
||||
@ -83,39 +86,27 @@ class CommandTree:
|
||||
# NOTE(MG) Fix for Python>=3.7,
|
||||
# argparse.ArgumentParser added 'required' argument.
|
||||
# Must also be written into SubMenu.create_submenu.
|
||||
func_args = {
|
||||
'dest': param_name,
|
||||
'metavar': param_name,
|
||||
'required': is_required
|
||||
}
|
||||
if (
|
||||
sys.version_info.major == 3
|
||||
and sys.version_info.minor < 7
|
||||
):
|
||||
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')
|
||||
del func_args['required']
|
||||
_bootstrap_logger.warn("Unable to enforce required submenu: Requires >= Python 3.7")
|
||||
del func_args["required"]
|
||||
# END fix for Python<3.7
|
||||
|
||||
# Creates an argument as a slot in the underlying argparse.
|
||||
subparsers = self.root_parser.add_subparsers(
|
||||
**func_args
|
||||
)
|
||||
subparsers = self.root_parser.add_subparsers(**func_args)
|
||||
|
||||
submenu = SubMenu(self, subparsers, param_name)
|
||||
submenu.submenu_path = ''
|
||||
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.
|
||||
@ -128,7 +119,7 @@ class CommandTree:
|
||||
pass
|
||||
# print('func is method')
|
||||
else:
|
||||
raise Exception('bad value passed in for function')
|
||||
raise Exception("bad value passed in for function")
|
||||
|
||||
if not cmd_name:
|
||||
# safe try/except
|
||||
@ -154,26 +145,19 @@ class CommandTree:
|
||||
# For each paramter in the function create an argparse argument in
|
||||
# the child ArgumentParser created for this menu entry:
|
||||
for key in params:
|
||||
if key == 'self':
|
||||
if key == "self":
|
||||
continue
|
||||
param = params[key]
|
||||
|
||||
if '=' in str(param):
|
||||
if "=" in str(param):
|
||||
if param.default is None:
|
||||
helptext = 'default provided'
|
||||
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)
|
||||
helptext = "required"
|
||||
self.root_parser.add_argument(key, help=helptext)
|
||||
|
||||
# Build the CommandEntry structure
|
||||
cmd = CommandEntry()
|
||||
@ -184,7 +168,7 @@ class CommandTree:
|
||||
cmd.callback = func
|
||||
|
||||
registered_name = cmd_name
|
||||
_bootstrap_logger.info('registered command: %s', registered_name)
|
||||
_bootstrap_logger.info("registered command: %s", registered_name)
|
||||
# end copy-paste then editted from SubMenu.register_command
|
||||
|
||||
self._cmd_tree_is_single_command = True
|
||||
@ -209,7 +193,7 @@ class CommandTree:
|
||||
# 'failed to parse arguments: explicitly failing to be safe')
|
||||
# return False, False
|
||||
|
||||
if hasattr(pargs, 'usage'):
|
||||
if hasattr(pargs, "usage"):
|
||||
pass
|
||||
# print('found usage in app_skellington')
|
||||
|
||||
@ -222,19 +206,19 @@ class CommandTree:
|
||||
def run_command(self, args=None):
|
||||
args, unk, success = self.parse(args)
|
||||
if not success:
|
||||
_bootstrap_logger.info('cli - SystemExit: Perhaps user invoked --help')
|
||||
_bootstrap_logger.info("cli - SystemExit: Perhaps user invoked --help")
|
||||
return
|
||||
|
||||
if args is False and unk is False:
|
||||
_bootstrap_logger.error('cli - Failed parsing args.')
|
||||
_bootstrap_logger.error("cli - Failed parsing args.")
|
||||
return False
|
||||
_bootstrap_logger.info('cli - Received args from shell: %s', args)
|
||||
_bootstrap_logger.info("cli - Received args from shell: %s", args)
|
||||
|
||||
args = vars(args)
|
||||
|
||||
cmd = self._lookup_command(args)
|
||||
if cmd is None:
|
||||
_bootstrap_logger.critical('cli - Failed to find command.')
|
||||
_bootstrap_logger.critical("cli - Failed to find command.")
|
||||
return False
|
||||
|
||||
return self._invoke_command(cmd, args)
|
||||
@ -246,16 +230,16 @@ 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._entries is None, 'corrupt data structure in CommandMenu'
|
||||
assert isinstance(self._single_command, CommandEntry), '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"
|
||||
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._single_command is None, "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
|
||||
@ -263,14 +247,14 @@ class CommandTree:
|
||||
|
||||
while True:
|
||||
if argparse_param not in keys:
|
||||
print('root menu parameter not found in args:', argparse_param)
|
||||
input('<broken>')
|
||||
print("root menu parameter not found in args:", argparse_param)
|
||||
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]
|
||||
@ -283,7 +267,7 @@ class CommandTree:
|
||||
# return self._invoke_command(lookup, args)
|
||||
|
||||
else:
|
||||
raise app_container.NoCommandSpecified('No command specified.')
|
||||
raise app_container.NoCommandSpecified("No command specified.")
|
||||
|
||||
def _invoke_command(self, cmd, args):
|
||||
command_to_be_invoked = cmd.callback
|
||||
@ -296,13 +280,14 @@ class CommandTree:
|
||||
if param.name in args:
|
||||
func_args.append(args[param.name])
|
||||
|
||||
_bootstrap_logger.info('cli - function: %s', func)
|
||||
_bootstrap_logger.info('cli - function args: %s', func_args)
|
||||
_bootstrap_logger.info("cli - function: %s", func)
|
||||
_bootstrap_logger.info("cli - function args: %s", func_args)
|
||||
return command_to_be_invoked(*func_args)
|
||||
|
||||
def _get_subparser(self):
|
||||
return self.root_parser._subparsers._actions[1]
|
||||
|
||||
|
||||
class SubMenu:
|
||||
def __init__(self, parent, subparsers_obj, name):
|
||||
self.parent = parent # Reference to root CommandTree
|
||||
@ -312,10 +297,7 @@ 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.
|
||||
@ -345,7 +327,7 @@ class SubMenu:
|
||||
elif inspect.ismethod(func):
|
||||
pass
|
||||
else:
|
||||
raise Exception('bad value passed in for function')
|
||||
raise Exception("bad value passed in for function")
|
||||
|
||||
if not cmd_name:
|
||||
# TODO(MG) Safer sanitation
|
||||
@ -374,33 +356,25 @@ class SubMenu:
|
||||
# created when the SubMenu/argparse.add_subparsers()
|
||||
# was created.
|
||||
help=help_text,
|
||||
description=description_text
|
||||
description=description_text,
|
||||
)
|
||||
|
||||
# For each paramter in the function create an argparse argument in
|
||||
# the child ArgumentParser created for this menu entry:
|
||||
for key in params:
|
||||
if key == 'self':
|
||||
if key == "self":
|
||||
continue
|
||||
param = params[key]
|
||||
|
||||
if '=' in str(param):
|
||||
if "=" in str(param):
|
||||
if param.default is None:
|
||||
helptext = 'default provided'
|
||||
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
|
||||
)
|
||||
helptext = "required"
|
||||
child_node.add_argument(key, help=helptext)
|
||||
|
||||
# Build the CommandEntry structure
|
||||
cmd = CommandEntry()
|
||||
@ -410,15 +384,11 @@ class SubMenu:
|
||||
# cmd.func_ref = None
|
||||
cmd.callback = func
|
||||
|
||||
registered_name = '{}.{}'.format(
|
||||
self.submenu_path,
|
||||
cmd_name)
|
||||
_bootstrap_logger.info('cli - registered command: %s', registered_name)
|
||||
registered_name = "{}.{}".format(self.submenu_path, cmd_name)
|
||||
_bootstrap_logger.info("cli - registered command: %s", registered_name)
|
||||
self.entries[cmd_name] = cmd
|
||||
|
||||
def create_submenu(
|
||||
self, var_name, cmd_entry_name=None, is_required=False
|
||||
):
|
||||
def create_submenu(self, var_name, cmd_entry_name=None, is_required=False):
|
||||
"""
|
||||
Creates a child-submenu.
|
||||
|
||||
@ -443,54 +413,37 @@ class SubMenu:
|
||||
# Create an entry in self's submenu:
|
||||
# type = ArgumentParser
|
||||
entry_node = self.subparsers_obj.add_parser(
|
||||
cmd_entry_name,
|
||||
help='sub-submenu help',
|
||||
description='sub-sub description')
|
||||
cmd_entry_name, help="sub-submenu help", description="sub-sub description"
|
||||
)
|
||||
|
||||
# NOTE(MG) Fix for Python>=3.7,
|
||||
# argparse.ArgumentParser added 'required' argument.
|
||||
# Must also be written into CommandTree.init_submenu
|
||||
func_args = {
|
||||
'dest': var_name,
|
||||
'metavar': var_name,
|
||||
'required': is_required
|
||||
}
|
||||
if (
|
||||
sys.version_info.major == 3
|
||||
and sys.version_info.minor < 7
|
||||
):
|
||||
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')
|
||||
del func_args['required']
|
||||
_bootstrap_logger.warn("Unable to enforce required submenu: Requires >= Python 3.7")
|
||||
del func_args["required"]
|
||||
# END fix for Python<3.7
|
||||
|
||||
|
||||
# Turn entry into a submenu of it's own:
|
||||
# type = _SubParsersAction
|
||||
subp_node = entry_node.add_subparsers(
|
||||
**func_args
|
||||
)
|
||||
subp_node = entry_node.add_subparsers(**func_args)
|
||||
|
||||
submenu = SubMenu(
|
||||
self.parent,
|
||||
subp_node,
|
||||
cmd_entry_name
|
||||
)
|
||||
submenu = SubMenu(self.parent, subp_node, cmd_entry_name)
|
||||
|
||||
submenu.var_name = var_name
|
||||
|
||||
submenu.submenu_path = '{}.{}'.format(self.submenu_path, cmd_entry_name)
|
||||
submenu.submenu_path = "{}.{}".format(self.submenu_path, cmd_entry_name)
|
||||
submenu_name = submenu.submenu_path
|
||||
|
||||
_bootstrap_logger.info('cli - registered submenu: %s', submenu_name)
|
||||
_bootstrap_logger.info("cli - registered submenu: %s", submenu_name)
|
||||
self.entries[cmd_entry_name] = submenu
|
||||
return submenu
|
||||
|
||||
def __repr__(self):
|
||||
return 'SubMenu({})<{}>'.format(
|
||||
self.name,
|
||||
','.join(['cmds'])
|
||||
)
|
||||
return "SubMenu({})<{}>".format(self.name, ",".join(["cmds"]))
|
||||
|
||||
|
||||
class CommandEntry:
|
||||
"""
|
||||
@ -506,6 +459,7 @@ class CommandEntry:
|
||||
arguments into argparse options (creating the documentation also). Similary,
|
||||
it can convert from argparse options into a function call.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.argparse_node = None
|
||||
|
||||
@ -517,7 +471,8 @@ class CommandEntry:
|
||||
self.callback = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'CommandEntry<{}>'.format(self.cmd_name)
|
||||
return "CommandEntry<{}>".format(self.cmd_name)
|
||||
|
||||
|
||||
class HelpGenerator:
|
||||
def __init__(self):
|
||||
@ -531,10 +486,10 @@ class HelpGenerator:
|
||||
"""
|
||||
if doctext == None:
|
||||
return doctext
|
||||
regex = '(.*?)[.?!]'
|
||||
regex = "(.*?)[.?!]"
|
||||
match = re.match(regex, doctext, re.MULTILINE | re.DOTALL)
|
||||
if match:
|
||||
return match.group(1) + '.'
|
||||
return match.group(1) + "."
|
||||
return doctext
|
||||
|
||||
@staticmethod
|
||||
@ -545,9 +500,8 @@ class HelpGenerator:
|
||||
"""
|
||||
if doctext == None:
|
||||
return doctext
|
||||
regex = '(.*?)[.?!]'
|
||||
regex = "(.*?)[.?!]"
|
||||
match = re.match(regex, doctext, re.MULTILINE | re.DOTALL)
|
||||
if match:
|
||||
return match.group(1) + '.'
|
||||
return match.group(1) + "."
|
||||
return doctext
|
||||
|
||||
|
@ -1,48 +1,42 @@
|
||||
from ._bootstrap import _bootstrap_logger, _logger_name
|
||||
from . import _util
|
||||
|
||||
import appdirs
|
||||
import colorlog
|
||||
import logging
|
||||
import logging.config
|
||||
import os
|
||||
|
||||
import appdirs
|
||||
import colorlog
|
||||
|
||||
from . import _util
|
||||
from ._bootstrap import _bootstrap_logger, _logger_name
|
||||
|
||||
DEFAULT_LOG_SETTINGS = {
|
||||
'formatters': {
|
||||
'colored': {
|
||||
'class': 'colorlog.ColoredFormatter',
|
||||
"formatters": {
|
||||
"colored": {
|
||||
"class": "colorlog.ColoredFormatter",
|
||||
# 'format': '%(log_color)s%(levelname)-8s%(reset)s:%(log_color)s%(name)-5s%(reset)s:%(white)s%(message)s'
|
||||
'format': '%(white)s%(name)7s%(reset)s|%(log_color)s%(message)s',
|
||||
"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": [
|
||||
"stderr",
|
||||
],
|
||||
"level": "debug",
|
||||
},
|
||||
|
||||
'loggers': {
|
||||
'root': {
|
||||
'handlers': ['stderr',],
|
||||
'level': 'debug'
|
||||
},
|
||||
'app_skellington': {
|
||||
"app_skellington": {
|
||||
# 'handlers': ['stderr',],
|
||||
'level': 'critical',
|
||||
'propagate': 'false'
|
||||
}
|
||||
}
|
||||
"level": "critical",
|
||||
"propagate": "false",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class LoggingLayer:
|
||||
def __init__(
|
||||
self, appname=None, appauthor=None
|
||||
):
|
||||
self.appname = appname or ''
|
||||
self.appauthor = appauthor or ''
|
||||
def __init__(self, appname=None, appauthor=None):
|
||||
self.appname = appname or ""
|
||||
self.appauthor = appauthor or ""
|
||||
self.loggers = {}
|
||||
|
||||
def __getitem__(self, k):
|
||||
@ -71,17 +65,17 @@ 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)
|
||||
|
||||
try:
|
||||
_bootstrap_logger.debug('log - Log configuration: %s', config_dict)
|
||||
_bootstrap_logger.debug("log - Log configuration: %s", config_dict)
|
||||
logging.config.dictConfig(config_dict)
|
||||
_bootstrap_logger.debug('log - Configured all logging')
|
||||
_bootstrap_logger.debug("log - Configured all logging")
|
||||
except Exception as ex:
|
||||
print('unable to configure logging:', ex, type(ex))
|
||||
print("unable to configure logging:", ex, type(ex))
|
||||
|
||||
def transform_config(self, config_dict):
|
||||
"""
|
||||
@ -89,48 +83,45 @@ class LoggingLayer:
|
||||
parameters and the final config dictionary passed into the logging module.
|
||||
"""
|
||||
# Version should be hard-coded 1, per Python docs
|
||||
if 'version' in config_dict:
|
||||
if config_dict['version'] != 1:
|
||||
if "version" in config_dict:
|
||||
if config_dict["version"] != 1:
|
||||
_bootstrap_logger.warn("logging['version'] must be '1' per Python docs")
|
||||
config_dict['version'] = 1
|
||||
config_dict["version"] = 1
|
||||
|
||||
self._add_own_logconfig(config_dict)
|
||||
|
||||
# Replace logger level strings with value integers from module
|
||||
for handler in config_dict['handlers']:
|
||||
d = config_dict['handlers'][handler]
|
||||
self._convert_str_to_loglevel(d, 'level')
|
||||
for handler in config_dict["handlers"]:
|
||||
d = config_dict["handlers"][handler]
|
||||
self._convert_str_to_loglevel(d, "level")
|
||||
|
||||
# Replace logger level strings with value integers from module
|
||||
for logger in config_dict['loggers']:
|
||||
d = config_dict['loggers'][logger]
|
||||
self._convert_str_to_loglevel(d, 'level')
|
||||
|
||||
for logger in config_dict["loggers"]:
|
||||
d = config_dict["loggers"][logger]
|
||||
self._convert_str_to_loglevel(d, "level")
|
||||
|
||||
# Implementation note:
|
||||
# app_skellington expects root logger configuration to be under 'root'
|
||||
# instead of '' (python spec) because '' is not a valid name in ConfigObj.
|
||||
try:
|
||||
if config_dict['loggers'].get('root') is not None:
|
||||
config_dict['loggers'][''] = config_dict['loggers']['root']
|
||||
del config_dict['loggers']['root']
|
||||
if config_dict["loggers"].get("root") is not None:
|
||||
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']:
|
||||
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'])
|
||||
config_dict['handlers']['file']['filename'] = log_filepath
|
||||
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):
|
||||
# NOTE(MG) Monkey-patch logger
|
||||
@ -140,13 +131,11 @@ class LoggingLayer:
|
||||
# variable the second time, when it's being reloaded as a
|
||||
# logging configuration is read from config file.
|
||||
# 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'
|
||||
}
|
||||
if os.environ.get("APPSKELLINGTON_DEBUG", None):
|
||||
if _logger_name not in config_dict["loggers"]:
|
||||
config_dict["loggers"][_logger_name] = {"level": "debug", "propagate": "false"}
|
||||
else:
|
||||
config_dict['loggers'][_logger_name]['level'] = 'debug'
|
||||
config_dict["loggers"][_logger_name]["level"] = "debug"
|
||||
|
||||
def _convert_str_to_loglevel(self, dict_, key):
|
||||
"""
|
||||
@ -164,16 +153,15 @@ class LoggingLayer:
|
||||
s = dict_[key]
|
||||
except KeyError as ex:
|
||||
raise
|
||||
if s == 'critical':
|
||||
if s == "critical":
|
||||
dict_[key] = logging.CRITICAL
|
||||
elif s == 'error':
|
||||
elif s == "error":
|
||||
dict_[key] = logging.ERROR
|
||||
elif s == 'warning':
|
||||
elif s == "warning":
|
||||
dict_[key] = logging.WARNING
|
||||
elif s == 'info':
|
||||
elif s == "info":
|
||||
dict_[key] = logging.INFO
|
||||
elif s == 'debug':
|
||||
elif s == "debug":
|
||||
dict_[key] = logging.DEBUG
|
||||
elif s == 'all':
|
||||
elif s == "all":
|
||||
dict_[key] = logging.NOTSET
|
||||
|
||||
|
73
setup.py
73
setup.py
@ -15,61 +15,52 @@
|
||||
# $ pip uninstall app_skellington
|
||||
|
||||
|
||||
from setuptools import setup
|
||||
import os
|
||||
|
||||
__project__ = 'app_skellington'
|
||||
__version__ = '0.1.1'
|
||||
__description__ = 'A high-powered command line menu framework.'
|
||||
from setuptools import setup
|
||||
|
||||
__project__ = "app_skellington"
|
||||
__version__ = "0.1.1"
|
||||
__description__ = "A high-powered command line menu framework."
|
||||
|
||||
long_description = __description__
|
||||
readme_filepath = os.path.join(
|
||||
os.path.abspath(os.path.dirname(__file__)),
|
||||
'README.md'
|
||||
)
|
||||
with open(readme_filepath, encoding='utf-8') as fp:
|
||||
readme_filepath = os.path.join(os.path.abspath(os.path.dirname(__file__)), "README.md")
|
||||
with open(readme_filepath, encoding="utf-8") as fp:
|
||||
long_description = fp.read()
|
||||
|
||||
setup(
|
||||
name=__project__,
|
||||
version=__version__,
|
||||
description = 'A high-powered command line menu framework.',
|
||||
description="A high-powered command line menu framework.",
|
||||
long_description=long_description,
|
||||
author = 'Mathew Guest',
|
||||
author_email = 't3h.zavage@gmail.com',
|
||||
url = 'https://git-mirror.zavage.net/Mirror/app_skellington',
|
||||
license = 'MIT',
|
||||
|
||||
python_requires = '>=3',
|
||||
|
||||
author="Mathew Guest",
|
||||
author_email="t3h.zavage@gmail.com",
|
||||
url="https://git-mirror.zavage.net/Mirror/app_skellington",
|
||||
license="MIT",
|
||||
python_requires=">=3",
|
||||
classifiers=[
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Environment :: Console',
|
||||
'Framework :: Pytest',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: System Administrators',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: MacOS',
|
||||
'Operating System :: Microsoft',
|
||||
'Operating System :: Microsoft :: Windows',
|
||||
'Operating System :: OS Independent',
|
||||
'Operating System :: POSIX',
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Topic :: Software Development :: Libraries',
|
||||
'Topic :: Utilities'
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Environment :: Console",
|
||||
"Framework :: Pytest",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: System Administrators",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Natural Language :: English",
|
||||
"Operating System :: MacOS",
|
||||
"Operating System :: Microsoft",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Operating System :: OS Independent",
|
||||
"Operating System :: POSIX",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Topic :: Utilities",
|
||||
],
|
||||
|
||||
# Third-party dependencies; will be automatically installed
|
||||
install_requires=(
|
||||
'appdirs',
|
||||
'configobj',
|
||||
'colorlog',
|
||||
"appdirs",
|
||||
"configobj",
|
||||
"colorlog",
|
||||
),
|
||||
|
||||
# Local packages to be installed (our packages)
|
||||
packages = (
|
||||
'app_skellington',
|
||||
),
|
||||
packages=("app_skellington",),
|
||||
)
|
||||
|
||||
|
@ -1,45 +1,43 @@
|
||||
from app_skellington.cfg import Config
|
||||
from app_skellington import _util
|
||||
|
||||
import pytest
|
||||
|
||||
from app_skellington import _util
|
||||
from app_skellington.cfg import Config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_configspec_filepath():
|
||||
return _util.get_asset(__name__, 'sample_config.spec')
|
||||
return _util.get_asset(__name__, "sample_config.spec")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_configini_filepath():
|
||||
return _util.get_asset(__name__, 'sample_config.ini')
|
||||
return _util.get_asset(__name__, "sample_config.ini")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_full_configspec_filepath():
|
||||
return _util.get_asset(__name__, 'sample_config_full.spec')
|
||||
return _util.get_asset(__name__, "sample_config_full.spec")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_full_configini_filepath():
|
||||
return _util.get_asset(__name__, 'sample_config_full.ini')
|
||||
return _util.get_asset(__name__, "sample_config_full.ini")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_invalid_configspec_filepath():
|
||||
return _util.get_asset(__name__, 'sample_config_invalid.spec')
|
||||
return _util.get_asset(__name__, "sample_config_invalid.spec")
|
||||
|
||||
|
||||
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'
|
||||
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"
|
||||
|
||||
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)'
|
||||
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)"
|
||||
|
||||
# NOTE(MG) Changed the functionality to not do it this way.
|
||||
# def test_constructor_fails_with_invalid_spec(
|
||||
@ -50,48 +48,35 @@ class TestConfig_e2e:
|
||||
# configspec_filepath=sample_invalid_configspec_filepath
|
||||
# )
|
||||
|
||||
def test_allows_options_beyond_spec(
|
||||
self, sample_configspec_filepath
|
||||
):
|
||||
cfg = Config(
|
||||
configspec_filepath=sample_configspec_filepath
|
||||
)
|
||||
cfg['foo'] = 'test my value'
|
||||
assert cfg['foo'] == 'test my value'
|
||||
def test_allows_options_beyond_spec(self, sample_configspec_filepath):
|
||||
cfg = Config(configspec_filepath=sample_configspec_filepath)
|
||||
cfg["foo"] = "test my value"
|
||||
assert cfg["foo"] == "test my value"
|
||||
|
||||
cfg['app']['bar'] = 'another value'
|
||||
assert cfg['app']['bar'] == 'another value'
|
||||
cfg["app"]["bar"] = "another value"
|
||||
assert cfg["app"]["bar"] == "another value"
|
||||
|
||||
# def test_can_read_config_file_mutiple_times(self):
|
||||
# pass
|
||||
|
||||
def test_can_override_config_file_manually(
|
||||
self, sample_configini_filepath
|
||||
):
|
||||
cfg = Config(
|
||||
configini_filepath=sample_configini_filepath
|
||||
)
|
||||
cfg['root_option'] = 'newval'
|
||||
assert cfg['root_option'] == 'newval'
|
||||
def test_can_override_config_file_manually(self, sample_configini_filepath):
|
||||
cfg = Config(configini_filepath=sample_configini_filepath)
|
||||
cfg["root_option"] = "newval"
|
||||
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'
|
||||
cfg["app"]["sub_option"] = "another_new_val"
|
||||
assert cfg["app"]["sub_option"] == "another_new_val", "expecting default for sub option"
|
||||
|
||||
def test_can_set_option_without_config(self):
|
||||
cfg = Config()
|
||||
cfg['foo'] = 'test my value'
|
||||
assert cfg['foo'] == 'test my value'
|
||||
cfg["foo"] = "test my value"
|
||||
assert cfg["foo"] == "test my value"
|
||||
|
||||
cfg['app'] = {}
|
||||
cfg['app']['bar'] = 'another value'
|
||||
assert cfg['app']['bar'] == 'another value'
|
||||
|
||||
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['app']['sub_option'] == 'def_sub', 'expecting default for sub option'
|
||||
cfg["app"] = {}
|
||||
cfg["app"]["bar"] = "another value"
|
||||
assert cfg["app"]["bar"] == "another value"
|
||||
|
||||
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["app"]["sub_option"] == "def_sub", "expecting default for sub option"
|
||||
|
@ -1,8 +1,7 @@
|
||||
from app_skellington.cli import CommandTree
|
||||
|
||||
|
||||
class TestCli_e2e:
|
||||
def test_null_constructor_works(self):
|
||||
x = CommandTree()
|
||||
assert True == True
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user