Source code for tdg.cli.log

#!/usr/bin/env python

import logging
logger = logging.getLogger(__name__)

import tdg.references.citation
import argparse

####
#### SETUP
####

# The aim is to allow the user to add different flags to an ArgumentParser relatively simply,
# with the syntactic suger
#
#   parser.add_argument('--option',  **TheOption())
#
# which proved to be a decent design choice for https://github.com/evanberkowitz/argparse-numpy-slices/
# This allows the user to pick different flag names than defaults() below but still leverage
# the actions provided here.
#
# An object which can be **unpacked into ArgumentParser.add_argument is of type StarStarSugar.
#
class StarStarSugar:
    # The idea is that TheOption should inherit from StarStarSugar
    def __init__(self, **kwargs):
        # and provide a default set of parameters, that can be overridden
        # when the object is created.
        self.parameters.update({**kwargs})
        if 'default' in self.parameters:
            self.parameters['help']+=f' Default is {str(self.parameters["default"]).replace("%","%%")}.'
        # All of the "meat" is that the action should be a custom argparse.Action
        #
        #   https://docs.python.org/3/library/argparse.html#argparse.Action
        #
        # which is accomplished by creating anonymous classes on the fly,
        # via the LogAction function, below.
        self.parameters['action'] = LogAction(self)

    # By Providing keys
    def keys(self):
        return self.parameters.keys()
    # and __getitem__
    def __getitem__(self, key):
        return self.parameters[key]
    # we make sure that we can **unpack StarStarSugar objects into parser.add_argument.
    def method(self, values):
        # Each child class needs to provide their own method.
        raise NotImplemented()

def LogAction(action):
    '''Construct an argparse.Action on the fly with init and call determined by the passed action.'''

    # Here is where we actually inherit from argparse.Action!
    class anonymous(argparse.Action):

        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)

            # On construction, set the default value.
            logging.basicConfig()
            action.method(action['default'])

        def __call__(self, parser, namespace, values, option_string, ):
            # On parsing, set the passed value.
            action.method(values)

    return anonymous

####
#### ACTUAL LOGGING OPTIONS
####

# Now we can begin implementing some options.
#
# First, something to parse logging levels
#
#   https://docs.python.org/3/library/logging.html#logging-levels
#
class LogLevel(StarStarSugar):

    parameters = {
            'default': 'WARNING',
            'help':    'Log level; one of DEBUG, INFO, WARNING, CITE, ERROR, CRITICAL.',
            'type':    str
            }

    levels = {
            'DEBUG':    logging.DEBUG,
            'INFO':     logging.INFO,
            'WARNING':  logging.WARNING,
            'CITE':     logging.CITE,
            'ERROR':    logging.ERROR,
            'CRITICAL': logging.CRITICAL,
            }

    def method(self, values):
        try:
            logging.getLogger().setLevel(self.levels[values])
        except:
            raise ValueError(f'Must be one of {self.levels.keys()}')

# Next, something to control the log format.
# The default is a custom format with a good balance of information.
class LogFormat(StarStarSugar):

    parameters = {
            'type': str,
            'default': '%(asctime)s %(name)-30s %(levelname)10s %(message)s',
            'help': 'Log format.  See https://docs.python.org/3/library/logging.html#logrecord-attributes for details.',
            }

    def method(self, fmt):
        formatter= logging.Formatter(fmt)
        for handler in logging.getLogger().handlers:
            handler.setFormatter(formatter)

[docs]def defaults(): r''' Returns an ``ArgumentParser`` which includes * ``--log-level`` * ``--log-format`` When parsed, even with the default arguments, these options trigger a call to `logging.basicConfig`_. If you want to manually control the logging setup you can override the created configuration by specifying ``force=True``. .. _logging.basicConfig: https://docs.python.org/3/library/logging.html#logging.basicConfig ''' log_arguments = argparse.ArgumentParser(add_help=False) log_arguments.add_argument('--log-level', **LogLevel()) log_arguments.add_argument('--log-format', **LogFormat()) return log_arguments
if __name__ == '__main__': parser = argparse.ArgumentParser(parents=[defaults(), ]) args = parser.parse_args() print(args) logger.debug ("This is DEBUG") logger.info ("This is INFO") logger.warning ("This is a WARNING") logger.cite ("This is a CITE") logger.error ("This is an ERROR") logger.critical ("This is CRITICAL")