tparker-usgs/tomputils

View on GitHub
tomputils/util.py

Summary

Maintainability
A
1 hr
Test Coverage
# -*- coding: utf-8 -*-
"""
utility functions.

"""
from __future__ import absolute_import, division, print_function, unicode_literals

import logging
import os
from buffering_smtp_handler import BufferingSMTPHandler
import sys
import pathlib
import ruamel.yaml
import errno


def exit_with_error(error, exit_status=1):
    """
    Log an error and exit after clening up logging.

    I assume that setup_logging() has been called or a global logger variables
    has been set some other way.

    Parameters
    ----------
    error : string
        error message
    exit_status : int
        exit status return to shell

    Examples
    --------
    >>> from tomputils.util import *
    >>> setup_logging()
    2018-08-04 01:13:36,086;INFO;SMTP logging not configured. [util.py:70]
    >>> exit_with_error("Just testing")
    2018-08-04 01:13:47,271;ERROR;Just testing [util.py:32]
    >>>

    """

    try:
        logger.error(error)
        logging.shutdown()
    finally:
        print(error, file=sys.stderr)

    sys.exit(exit_status)


def enforce_version(req_version, excuse):
    """
    Go no further if the python interpreter is too old.

    Parameters
    ----------
    req_version : tuple
        minimum acceptable version as returned by sys.version_info
    excuse : string
        Explanation for interpreter requirement

    Examples
    --------
    >>> import sys
    >>> import tomputils.util as tutil
    >>> sys.version_info
    sys.version_info(major=3, minor=6, micro=5, releaselevel='final', serial=0)
    >>> msg = "Sorry, I need at least python 3.6 for a really good reason."
    >>> tutil.enforce_version((3,6), msg)
    >>> msg = "Sorry, I need at least python 3.7 for a really good reason."
    >>> tutil.enforce_version((3,7), msg)
    Sorry, I need at least python 3.7 for a really good reason.
    """
    if sys.version_info < req_version:
        exit_with_error(excuse)


def get_env_var(var, default=None, secret=False):
    """
    Retrieve an environment variable. A defult may be supplied and will be
    returned if the requested variable is not set. If no default is provided,
    an unset environment variable will considered a fatal error.

    I assume that setup_logging() has been called or a global logger variables
    has been set some other way.

    Parameters
    ----------
    var : string
        variable to find
    default : string, optional
        default returned if variable is unset
    secret : boolean, optional
        if true, do not log value.

    Returns
    -------
    str
        environment variable

    Examples
    --------
    >>> from tomputils.util import *
    >>> setup_logging()
    2018-08-04 01:18:37,519;INFO;SMTP logging not configured. [util.py:99]
    >>> get_env_var("HOME")
    2018-08-04 01:18:41,039;DEBUG;HOME: /Users/tomp [util.py:68]
    '/Users/tomp'
    >>> get_env_var("NOTSET", default="close enough")
    2018-08-04 01:18:44,863;DEBUG;NOTSET: close enough (default) [util.py:76]
    'close enough'
    >>> get_env_var("NOTSET")
    2018-08-04 01:18:51,248;ERROR;Envionment variable NOTSET not set, exiting.
    [util.py:35]
    >>>

    """
    if var in os.environ:
        if not secret:
            logger.debug("%s: %s", var, os.environ[var])
        return os.environ[var]

    else:
        if default is None:
            msg = "Envionment variable {} not set, exiting.".format(var)
            exit_with_error(EnvironmentError(msg))
        else:
            if not secret:
                logger.debug("%s: %s (default)", var, default)
            return default


def setup_logging(subject="Error logs"):
    """
    Setup logging the way I like it. If the following environment variables are
    provided, an email with error level logs will be sent.

    * MAILHOST : where to email logs
    * LOG_SENDER: From: address
    * LOG_RECIPIENT: To: address


    Parameters
    ----------
    subject : string, optional
        subject used if email is generated


    Examples
    --------
    >>> from tomputils.util import *
    >>> logger = setup_logging("Tomp test")
    >>> logger.debug("Test message")
    2018-08-05 12:01:28,577 DEBUG - Test message (<stdin>:1)
    >>> logging.shutdown()
    >>>

    """

    global logger
    logger = logging.getLogger("")
    logger.setLevel(logging.DEBUG)

    ch = logging.StreamHandler()
    fmt = (
        "%(asctime)s %(levelname)s - %(message)s "
        + "(%(filename)s:%(lineno)s PID %(process)d)"
    )
    formatter = logging.Formatter(fmt)
    ch.setFormatter(formatter)
    logger.addHandler(ch)

    try:
        handler = BufferingSMTPHandler(
            os.environ["MAILHOST"],
            os.environ["LOG_SENDER"],
            os.environ["LOG_RECIPIENT"].split(","),
            subject,
            1000,
            fmt,
        )
        handler.setLevel(logging.ERROR)
        logger.addHandler(handler)
        logger.info("SMTP configured, will send email.")
    except KeyError:
        logger.info("SMTP logging not configured.")

    return logger


def parse_config(config_path):
    """
    Parse a local config file

    Parameters
    ----------
    config_path : string
        config file path


    Examples
    --------
    >>> from tomputils.util import *
    >>> config = tutils.parse_config("/tmp/test.yaml")
    >>>

    """
    logger.debug("Parsing config %s", config_path)
    config_file = pathlib.Path(config_path)
    yaml = ruamel.yaml.YAML()
    config = None
    try:
        config = yaml.load(config_file)
    except ruamel.yaml.parser.ParserError as e1:
        logger.error("Cannot parse config file")
        exit_with_error(e1)
    except OSError as e:
        if e.errno == errno.EEXIST:
            logger.error("Cannot read config file %s", config_file)
            exit_with_error(e)
        else:
            raise
    return config