Module skm_pyutils.config

Config file and logging related utility functions.

Expand source code
"""Config file and logging related utility functions."""
import configparser
import json
import os
import sys
from pprint import pprint
import yaml


def read_cfg(location, verbose=True):
    """
    Read config file at location using ConfigParser.

    Parameters
    ----------
    location : str
        Where the config file is located
    verbose : bool, optional, defaults to True
        Should print the contents of the read config file.

    Returns
    -------
    ConfigParser
        The python ConfigParser object after reading the cfg.

    """
    if not os.path.exists(location):
        raise ValueError(f"Config file {location} does not exist")

    config = configparser.ConfigParser()
    config.read(location)

    if verbose:
        print_cfg(config, "Program started with configuration")
    return config


def print_cfg(config, msg=""):
    """
    Print the contents of a ConfigParser object.

    Parameters
    ----------
    config : ConfigParser
        The ConfigParser to print the contents of.
    msg: str, optional, defaults to ""
        Message to print before printing the config file.

    Returns
    -------
    None

    """
    if msg != "":
        print(msg)
    config_dict = [{x: tuple(config.items(x))} for x in config.sections()]
    pprint(config_dict, width=120)


def parse_args(parser, verbose=True):
    """
    Parse command line arguments into a Namespace.

    Parameters
    ----------
    verbose : bool, optional, defaults to True
        Should print the values of the command line args.

    Returns
    -------
    Namespace
        Parsed arguments.

    Raises
    ------
    ValueError
        If any arguments are passed which are not used in program.

    """
    args, unparsed = parser.parse_known_args()

    if len(unparsed) != 0:
        raise ValueError(
            "Unrecognised command line arguments passed {}".format(unparsed)
        )

    if verbose:
        if len(sys.argv) > 1:
            print("Command line arguments", args)
    return args


def read_python(path, dirname_replacement=""):
    """
    Execute a python script at path.

    The script is expected to have items visible at global scope,
    which are stored as metadata.

    Note
    ----
    The string "__thisdirname__" is magic and will be replaced by the
    absolute path to the directory containing the script.
    The string "__dirname__" is also magic and will be replaced by
    the value of dirname_replacement.

    Parameters
    ----------
    path : string
        The location of the python script.
    dirname_replacement : string, optional, optional, defaults to None
        What to replace __dirname__ with.
        By default, None will replace __dirname__ with dirname of path.

    Returns
    -------
    dict
        The scripts global scope variables stored in a dictionary.

    """

    def normalise_path(pth):
        s = os.path.abspath(pth)
        s = s.replace(os.sep, "/")
        return s

    path = os.path.realpath(os.path.expanduser(path))
    if not os.path.exists(path):
        raise ValueError("{} does not exist to read".format(path))
    with open(path, "r") as f:
        contents = f.read()
    if dirname_replacement != "":
        contents = contents.replace("__dirname__", normalise_path(dirname_replacement))
    else:
        contents = contents.replace(
            "__dirname__", normalise_path(os.path.dirname(path))
        )
    contents = contents.replace(
        "__thisdirname__", normalise_path(os.path.dirname(path))
    )
    metadata = {}
    try:
        exec(contents, {}, metadata)
    except Exception as e:
        import traceback

        print("QUITTING: An error occurred reading {}".format(path))
        traceback.print_exc()
        exit(-1)
    metadata = {k.lower(): v for (k, v) in metadata.items()}
    return metadata


def read_yaml(path):
    with open(path, "r") as stream:
        parsed_yaml = yaml.safe_load(stream)
    return parsed_yaml


def read_json(path):
    with open(path, "r") as stream:
        parsed_json = json.load(stream)
    return parsed_json


def split_dict(in_dict, index):
    """
    Grab the value at index from each list in the dictionary.

    Parameters
    ----------
    in_dict : dict
        The dictionary to grab from
    index : int
        The index in the lists to pull from

    Returns
    -------
    dict
        The original dictionary but with index values pulled out.

    """
    new_dict = {}
    for key, value in in_dict.items():
        if isinstance(value, list):
            new_dict[key] = value[index]
    return new_dict

def convert_dict_to_string(in_dict, name):
    """
    Convert the underlying parameters dictionary to string.

    Can be useful for printing or writing to a file.
    Does not overwrite default __str__ as the output is quite verbose.

    Parameters
    ----------
    in_dict : dict
        Input dictionary

    Returns
    -------
    str
        The string representation of the dict.

    """

    def _val_to_str(val):
        """
        Convert a value to a string.

        One caveat, if a string is passed, it returns
        the original string wrapped in quotes.

        Parameters
        ----------
        val : object
            The value to convert

        Returns
        -------
        str
            The value as a string.

        """
        return f"'{val}'" if isinstance(val, str) else val

    out_str = ""
    out_str += name + " = {\n"
    for k, v in in_dict.items():
        out_str += f"\t{_val_to_str(str(k))}:"
        if isinstance(v, dict):
            out_str += "\n\t\t{\n"
            for k2, v2 in v.items():
                out_str += "\t\t {}: {},\n".format(
                    _val_to_str(str(k2)), _val_to_str(v2)
                )
            out_str += "\t\t},\n"
        else:
            out_str += f" {_val_to_str(v)},\n"
    out_str += "\t}"
    return out_str

Functions

def convert_dict_to_string(in_dict, name)

Convert the underlying parameters dictionary to string.

Can be useful for printing or writing to a file. Does not overwrite default str as the output is quite verbose.

Parameters

in_dict : dict
Input dictionary

Returns

str
The string representation of the dict.
Expand source code
def convert_dict_to_string(in_dict, name):
    """
    Convert the underlying parameters dictionary to string.

    Can be useful for printing or writing to a file.
    Does not overwrite default __str__ as the output is quite verbose.

    Parameters
    ----------
    in_dict : dict
        Input dictionary

    Returns
    -------
    str
        The string representation of the dict.

    """

    def _val_to_str(val):
        """
        Convert a value to a string.

        One caveat, if a string is passed, it returns
        the original string wrapped in quotes.

        Parameters
        ----------
        val : object
            The value to convert

        Returns
        -------
        str
            The value as a string.

        """
        return f"'{val}'" if isinstance(val, str) else val

    out_str = ""
    out_str += name + " = {\n"
    for k, v in in_dict.items():
        out_str += f"\t{_val_to_str(str(k))}:"
        if isinstance(v, dict):
            out_str += "\n\t\t{\n"
            for k2, v2 in v.items():
                out_str += "\t\t {}: {},\n".format(
                    _val_to_str(str(k2)), _val_to_str(v2)
                )
            out_str += "\t\t},\n"
        else:
            out_str += f" {_val_to_str(v)},\n"
    out_str += "\t}"
    return out_str
def parse_args(parser, verbose=True)

Parse command line arguments into a Namespace.

Parameters

verbose : bool, optional, defaults to True
Should print the values of the command line args.

Returns

Namespace
Parsed arguments.

Raises

ValueError
If any arguments are passed which are not used in program.
Expand source code
def parse_args(parser, verbose=True):
    """
    Parse command line arguments into a Namespace.

    Parameters
    ----------
    verbose : bool, optional, defaults to True
        Should print the values of the command line args.

    Returns
    -------
    Namespace
        Parsed arguments.

    Raises
    ------
    ValueError
        If any arguments are passed which are not used in program.

    """
    args, unparsed = parser.parse_known_args()

    if len(unparsed) != 0:
        raise ValueError(
            "Unrecognised command line arguments passed {}".format(unparsed)
        )

    if verbose:
        if len(sys.argv) > 1:
            print("Command line arguments", args)
    return args
def print_cfg(config, msg='')

Print the contents of a ConfigParser object.

Parameters

config : ConfigParser
The ConfigParser to print the contents of.
msg : str, optional, defaults to ""
Message to print before printing the config file.

Returns

None
 
Expand source code
def print_cfg(config, msg=""):
    """
    Print the contents of a ConfigParser object.

    Parameters
    ----------
    config : ConfigParser
        The ConfigParser to print the contents of.
    msg: str, optional, defaults to ""
        Message to print before printing the config file.

    Returns
    -------
    None

    """
    if msg != "":
        print(msg)
    config_dict = [{x: tuple(config.items(x))} for x in config.sections()]
    pprint(config_dict, width=120)
def read_cfg(location, verbose=True)

Read config file at location using ConfigParser.

Parameters

location : str
Where the config file is located
verbose : bool, optional, defaults to True
Should print the contents of the read config file.

Returns

ConfigParser
The python ConfigParser object after reading the cfg.
Expand source code
def read_cfg(location, verbose=True):
    """
    Read config file at location using ConfigParser.

    Parameters
    ----------
    location : str
        Where the config file is located
    verbose : bool, optional, defaults to True
        Should print the contents of the read config file.

    Returns
    -------
    ConfigParser
        The python ConfigParser object after reading the cfg.

    """
    if not os.path.exists(location):
        raise ValueError(f"Config file {location} does not exist")

    config = configparser.ConfigParser()
    config.read(location)

    if verbose:
        print_cfg(config, "Program started with configuration")
    return config
def read_json(path)
Expand source code
def read_json(path):
    with open(path, "r") as stream:
        parsed_json = json.load(stream)
    return parsed_json
def read_python(path, dirname_replacement='')

Execute a python script at path.

The script is expected to have items visible at global scope, which are stored as metadata.

Note

The string "thisdirname" is magic and will be replaced by the absolute path to the directory containing the script. The string "dirname" is also magic and will be replaced by the value of dirname_replacement.

Parameters

path : string
The location of the python script.
dirname_replacement : string, optional, optional, defaults to None
What to replace dirname with. By default, None will replace dirname with dirname of path.

Returns

dict
The scripts global scope variables stored in a dictionary.
Expand source code
def read_python(path, dirname_replacement=""):
    """
    Execute a python script at path.

    The script is expected to have items visible at global scope,
    which are stored as metadata.

    Note
    ----
    The string "__thisdirname__" is magic and will be replaced by the
    absolute path to the directory containing the script.
    The string "__dirname__" is also magic and will be replaced by
    the value of dirname_replacement.

    Parameters
    ----------
    path : string
        The location of the python script.
    dirname_replacement : string, optional, optional, defaults to None
        What to replace __dirname__ with.
        By default, None will replace __dirname__ with dirname of path.

    Returns
    -------
    dict
        The scripts global scope variables stored in a dictionary.

    """

    def normalise_path(pth):
        s = os.path.abspath(pth)
        s = s.replace(os.sep, "/")
        return s

    path = os.path.realpath(os.path.expanduser(path))
    if not os.path.exists(path):
        raise ValueError("{} does not exist to read".format(path))
    with open(path, "r") as f:
        contents = f.read()
    if dirname_replacement != "":
        contents = contents.replace("__dirname__", normalise_path(dirname_replacement))
    else:
        contents = contents.replace(
            "__dirname__", normalise_path(os.path.dirname(path))
        )
    contents = contents.replace(
        "__thisdirname__", normalise_path(os.path.dirname(path))
    )
    metadata = {}
    try:
        exec(contents, {}, metadata)
    except Exception as e:
        import traceback

        print("QUITTING: An error occurred reading {}".format(path))
        traceback.print_exc()
        exit(-1)
    metadata = {k.lower(): v for (k, v) in metadata.items()}
    return metadata
def read_yaml(path)
Expand source code
def read_yaml(path):
    with open(path, "r") as stream:
        parsed_yaml = yaml.safe_load(stream)
    return parsed_yaml
def split_dict(in_dict, index)

Grab the value at index from each list in the dictionary.

Parameters

in_dict : dict
The dictionary to grab from
index : int
The index in the lists to pull from

Returns

dict
The original dictionary but with index values pulled out.
Expand source code
def split_dict(in_dict, index):
    """
    Grab the value at index from each list in the dictionary.

    Parameters
    ----------
    in_dict : dict
        The dictionary to grab from
    index : int
        The index in the lists to pull from

    Returns
    -------
    dict
        The original dictionary but with index values pulled out.

    """
    new_dict = {}
    for key, value in in_dict.items():
        if isinstance(value, list):
            new_dict[key] = value[index]
    return new_dict