Convert main to new logging and model modules

This commit is contained in:
Tim Van Baak 2020-04-23 17:47:22 -07:00
parent bfe065583a
commit fdff9e5374
2 changed files with 71 additions and 98 deletions

View File

@ -1,63 +1,29 @@
# Standard library imports # Standard library imports
import argparse import argparse
import logging
import os import os
import traceback import sys
# Module imports # Module imports
import amanuensis.cli as cli from amanuensis.cli import describe_commands, get_commands
from amanuensis.cli.helpers import ( from amanuensis.config.context import RootConfigDirectoryContext
USER_ARGS, USER_KWARGS, LEXICON_ARGS, LEXICON_KWARGS)
import amanuensis.config as config import amanuensis.config as config
from amanuensis.errors import AmanuensisError from amanuensis.errors import AmanuensisError
from amanuensis.log import init_logging
from amanuensis.models import ModelFactory
def repl(args):
"""Runs a REPL with the given lexicon"""
# Get all the cli commands' descriptions and add help and exit.
commands = {
name[8:].replace("_", "-"): func.__doc__ for name, func in vars(cli).items()
if name.startswith("command_")}
commands['help'] = "Print this message"
commands['exit'] = "Exit"
print("Amanuensis running on Lexicon {}".format(args.tl_lexicon))
while True:
# Read input in a loop.
try:
data = input("{}> ".format(args.tl_lexicon))
except EOFError:
print()
break
tokens = data.strip().split()
if not data.strip():
pass
elif tokens[0] not in commands:
print("'{}' is not a valid command.".format(tokens[0]))
elif data.strip() == "help":
print("Available commands:")
for name, func in commands.items():
print(" {}: {}".format(name, func))
elif data.strip() == "exit":
print()
break
elif data.strip():
# Execute the command by appending it to the argv the
# REPL was invoked with.
try:
argv = sys.argv[1:] + data.split()
main(argv)
except Exception as e:
traceback.print_exc()
def process_doc(docstring): def process_doc(docstring):
return '\n'.join([ return '\n'.join([
line.strip() line.strip()
for line in (docstring or "").strip().splitlines() for line in (docstring or "").strip().splitlines()
]) ])
def get_parser(valid_commands): def get_parser(valid_commands):
# Set up the top-level parser. # Set up the top-level parser.
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description=cli.describe_commands(), description=describe_commands(),
formatter_class=argparse.RawDescriptionHelpFormatter) formatter_class=argparse.RawDescriptionHelpFormatter)
# The config directory. # The config directory.
parser.add_argument("--config-dir", parser.add_argument("--config-dir",
@ -73,21 +39,7 @@ def get_parser(valid_commands):
dest="log_file", dest="log_file",
default=os.environ.get(config.ENV_LOG_FILE), default=os.environ.get(config.ENV_LOG_FILE),
help="Enable verbose file logging") help="Enable verbose file logging")
parser.add_argument("--log-file-size", parser.set_defaults(func=lambda args: parser.print_help())
dest="log_file_size",
default=os.environ.get(config.ENV_LOG_FILE_SIZE),
help="Maximum rolling log file size")
parser.add_argument("--log-file-num",
dest="log_file_num",
default=os.environ.get(config.ENV_LOG_FILE_NUM),
help="Maximum rolling file count")
# Lexicon settings.
parser.add_argument(*LEXICON_ARGS, **LEXICON_KWARGS)
parser.add_argument(*USER_ARGS, **USER_KWARGS)
parser.set_defaults(
func=lambda args: repl(args)
if args.tl_lexicon
else parser.print_help())
subp = parser.add_subparsers( subp = parser.add_subparsers(
metavar="COMMAND", metavar="COMMAND",
dest="command", dest="command",
@ -109,23 +61,29 @@ def get_parser(valid_commands):
return parser return parser
def main(argv): def main(argv):
# Enumerate valid commands from the CLI module. # Enumerate valid commands from the CLI module.
commands = cli.get_commands() commands = get_commands()
# Parse args
args = get_parser(commands).parse_args(argv) args = get_parser(commands).parse_args(argv)
# If the command is the init command, a config directory will be # First things first, initialize logging
# initialized at args.config_dir. Otherwise, initialize configs using init_logging(args.verbose, args.log_file)
# that directory. logger = logging.getLogger('amanuensis')
# The init command initializes a config directory at --config-dir.
# All other commands assume that the config dir already exists.
if args.command and args.command != "init": if args.command and args.command != "init":
config.init_config(args) args.root = RootConfigDirectoryContext(args.config_dir)
args.model_factory = ModelFactory(args.root)
# If verbose logging, dump args namespace # If verbose logging, dump args namespace
if args.verbose: if args.verbose:
config.logger.debug("amanuensis") logger.debug('amanuensis')
for key, val in vars(args).items(): for key, val in vars(args).items():
config.logger.debug(" {}: {}".format(key, val)) logger.debug(f' {key}: {val}')
# Execute command. # Execute command.
try: try:
@ -134,6 +92,6 @@ def main(argv):
config.logger.error('Unexpected internal {}: {}'.format( config.logger.error('Unexpected internal {}: {}'.format(
type(e).__name__, str(e))) type(e).__name__, str(e)))
if __name__ == "__main__": if __name__ == "__main__":
import sys
sys.exit(main(sys.argv[1:])) sys.exit(main(sys.argv[1:]))

View File

@ -2,12 +2,19 @@
from argparse import ArgumentParser from argparse import ArgumentParser
from functools import wraps from functools import wraps
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError
from logging import getLogger
from sys import exc_info
logger = getLogger(__name__)
# These function wrappers allow us to use the same function for executing a #
# command and for configuring it. This keeps command arg configuration close to # The add_argument and no_argument function wrappers allow the same
# where the command is defined and allows the main parser to use the same # function to both configure a command and execute it. This keeps
# function to both set up and execute commands. # command argument configuration close to where the command is defined
# and reduces the number of things the main parser has to handle.
#
def add_argument(*args, **kwargs): def add_argument(*args, **kwargs):
"""Passes the given args and kwargs to subparser.add_argument""" """Passes the given args and kwargs to subparser.add_argument"""
@ -47,13 +54,21 @@ def no_argument(command):
return augmented_command return augmented_command
# Wrappers for commands requiring lexicon or username options #
# Many commands require specifying a lexicon or user to operate on, so
# the requires_lexicon and requires_user wrappers replace @add_argument
# as well as automatically create the model for the object from the
# provided identifier.
#
LEXICON_ARGS = ['--lexicon'] LEXICON_ARGS = ['--lexicon']
LEXICON_KWARGS = { LEXICON_KWARGS = {
'metavar': 'LEXICON', 'metavar': 'LEXICON',
'dest': 'lexicon', 'dest': 'lexicon',
'help': 'Specify a user to operate on'} 'help': 'Specify a user to operate on'}
def requires_lexicon(command): def requires_lexicon(command):
@wraps(command) @wraps(command)
def augmented_command(cmd_args): def augmented_command(cmd_args):
@ -67,34 +82,33 @@ def requires_lexicon(command):
return None return None
# Verify lexicon argument in execute pass # Verify lexicon argument in execute pass
val = (getattr(cmd_args, 'lexicon') val = getattr(cmd_args, 'lexicon', None)
if hasattr(cmd_args, 'lexicon')
else None)
if not val: if not val:
from amanuensis.config import logger logger.error("Missing --lexicon argument")
logger.error("This command requires specifying a lexicon")
return -1 return -1
from amanuensis.lexicon import LexiconModel try:
cmd_args.lexicon = LexiconModel.by(name=val) #TODO catch specific exceptions model_factory = cmd_args.model_factory
if cmd_args.lexicon is None: cmd_args.lexicon = model_factory.lexicon(val)
from amanuensis.config import logger except Exception:
logger.error('Could not find lexicon "{}"'.format(val)) ex_type, value, tb = exc_info()
logger.error(
f'Loading lexicon "{val}" failed with '
f'{ex_type.__name__}: {value}')
return -1 return -1
return command(cmd_args) return command(cmd_args)
augmented_command.__dict__['wrapper'] = True augmented_command.__dict__['wrapper'] = True
return augmented_command return augmented_command
USER_ARGS = ['--user'] USER_ARGS = ['--user']
USER_KWARGS = { USER_KWARGS = {
'metavar': 'USER', 'metavar': 'USER',
'dest': 'user', 'dest': 'user',
'help': 'Specify a user to operate on'} 'help': 'Specify a user to operate on'}
def requires_user(command): def requires_user(command):
"""
Performs all necessary setup and verification for passing a user to a CLI
command.
"""
@wraps(command) @wraps(command)
def augmented_command(cmd_args): def augmented_command(cmd_args):
# Add user argument in parser pass # Add user argument in parser pass
@ -107,18 +121,18 @@ def requires_user(command):
return None return None
# Verify user argument in execute pass # Verify user argument in execute pass
val = (getattr(cmd_args, "user") val = getattr(cmd_args, "user", None)
if hasattr(cmd_args, "user")
else None)
if not val: if not val:
from amanuensis.config import logger logger.error("Missing --user argument")
logger.error("This command requires specifying a user")
return -1 return -1
from amanuensis.user import UserModel try:
cmd_args.user = UserModel.by(name=val) #TODO catch specific exceptions model_factory = cmd_args.model_factory
if cmd_args.user is None: cmd_args.user = model_factory.user(val)
from amanuensis.config import logger except Exception:
logger.error('Could not find user "{}"'.format(val)) ex_type, value, tb = exc_info()
logger.error(
f'Loading user "{val}" failed with '
f'{ex_type.__name__}: {value}')
return -1 return -1
return command(cmd_args) return command(cmd_args)
@ -140,15 +154,16 @@ def alias(cmd_alias):
# Helpers for common command tasks # Helpers for common command tasks
CONFIG_GET_ROOT_VALUE = object() CONFIG_GET_ROOT_VALUE = object()
def config_get(cfg, pathspec): def config_get(cfg, pathspec):
""" """
Performs config --get for a given config Performs config --get for a given config
cfg is from a with json_ro context cfg is from a `with json_ro` context
path is the full pathspec, unsplit path is the full pathspec, unsplit
""" """
import json import json
from amanuensis.config import logger
if pathspec is CONFIG_GET_ROOT_VALUE: if pathspec is CONFIG_GET_ROOT_VALUE:
path = [] path = []
@ -162,6 +177,7 @@ def config_get(cfg, pathspec):
print(json.dumps(cfg, indent=2)) print(json.dumps(cfg, indent=2))
return 0 return 0
def config_set(obj_id, cfg, set_tuple): def config_set(obj_id, cfg, set_tuple):
""" """
Performs config --set for a given config Performs config --set for a given config
@ -170,7 +186,6 @@ def config_set(obj_id, cfg, set_tuple):
set_tuple is a tuple of the pathspec and the value set_tuple is a tuple of the pathspec and the value
""" """
import json import json
from amanuensis.config import logger
pathspec, value = set_tuple pathspec, value = set_tuple
if not pathspec: if not pathspec:
logger.error("Path must be non-empty") logger.error("Path must be non-empty")
@ -178,7 +193,7 @@ def config_set(obj_id, cfg, set_tuple):
try: try:
value = json.loads(value) value = json.loads(value)
except JSONDecodeError: except JSONDecodeError:
pass # Leave value as string pass # Leave value as string
for spec in path[:-1]: for spec in path[:-1]:
if spec not in cfg: if spec not in cfg:
logger.error("Path not found") logger.error("Path not found")