utils.py 3.15 KB
Newer Older
BorjaEst's avatar
BorjaEst committed
1
2
3
4
5
6
7
8
"""This module offers some utils for code management. For example
utils to change easily from directory or create netCDF files.

Although this module is not expected to be used out of the o3skim
package, some functions might be convenient when using the python 
shell.
"""

9
10
from contextlib import contextmanager
import os
11
12
13
import yaml
import netCDF4
import xarray as xr
BorjaEst's avatar
BorjaEst committed
14
15
16
import logging

logger = logging.getLogger('o3skim.utils')
17
18
19
20


@contextmanager
def cd(newdir):
BorjaEst's avatar
BorjaEst committed
21
22
23
24
25
26
27
    """Changes the directory inside a 'with' context. When the code
    reaches the end of the 'with' block or the code fails, the 
    previous folder is restored.

    :param newdir: Path folder where to change the working directory.
    :type newdir: str
    """
28
29
30
    prevdir = os.getcwd()
    os.chdir(os.path.expanduser(newdir))
    try:
BorjaEst's avatar
BorjaEst committed
31
        logger.debug("Changing directory: '%s'", newdir)
32
33
34
        yield
    finally:
        os.chdir(prevdir)
BorjaEst's avatar
BorjaEst committed
35
        logger.debug("Restoring directory: '%s'", prevdir)
36

37

38
def return_on_failure(message, default=None):
BorjaEst's avatar
BorjaEst committed
39
40
41
42
43
    """Decorator to do not break but log. Note that the error stack
    is printed as well to do not lose relevant information.

    :param message: Additional message to log when the function fails.
    :type message: str
44
45
46

    :param default: Value to return if fails.
    :type default: any or None, optional
BorjaEst's avatar
BorjaEst committed
47
    """
48
49
50
    def decorate(function):
        def applicator(*args, **kwargs):
            try:
51
                return function(*args, **kwargs)
52
            except:
53
                # Log error with stack using root (not utils)
54
                logging.error(message, exc_info=True)
55
                return default
56
        applicator.__doc__ = function.__doc__
57
58
59
60
        return applicator
    return decorate


61
def load(yaml_file):
BorjaEst's avatar
BorjaEst committed
62
63
64
65
66
67
68
69
70
71
72
    """Loads the .yaml file and returns a python dictionary with all
    the yaml keys and values. Note a yaml file can have key:values 
    sotored inside values, therefore the returned dictionary might 
    have dictionaries stored inside values. 

    :param yaml_file: Path pointing to the yaml configuration file.
    :type yaml_file: str

    :return: Dictionary with the yaml structure key:value.
    :rtype: dict
    """
73
    with open(yaml_file, "r") as ymlfile:
74
        config = yaml.safe_load(ymlfile)
BorjaEst's avatar
BorjaEst committed
75
76
        logging.debug("Configuration data: %s", config)
        return config
77
78
79
80
81
82
83
84
85
86
87
88
89
90


def save(file_name, metadata):
    """Saves the metadata dict on the current folder with yaml 
    format. 

    :param file_name: Name for the output yaml file.
    :type file_name: str

    :param metadata: Dict with the data to save into.
    :type metadata: dict
    """
    with open(file_name, 'w+') as ymlfile:
        yaml.dump(metadata, ymlfile, allow_unicode=True)
BorjaEst's avatar
BorjaEst committed
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107


def mergedicts(d1, d2):
    """Merges dict d2 in dict d2 recursively. If two keys exist in 
    both dicts, the value in d1 is superseded by the value in d2.

    :param d1: Dict to be recursively completed by d2.
    :type d1: dict

    :param d2: Dict to be recursively merged in d1.
    :type d2: dict
    """
    for key in d2:
        if key in d1 and isinstance(d1[key], dict) and isinstance(d2[key], dict):
            mergedicts(d1[key], d2[key])
        else:
            d1[key] = d2[key]