a0a8fde6ef2fb2cb9b58731fee3247543eaaa900
[optf/osdf.git] / utils / programming_utils.py
1 # -------------------------------------------------------------------------
2 #   Copyright (c) 2015-2017 AT&T Intellectual Property
3 #
4 #   Licensed under the Apache License, Version 2.0 (the "License");
5 #   you may not use this file except in compliance with the License.
6 #   You may obtain a copy of the License at
7 #
8 #       http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #   Unless required by applicable law or agreed to in writing, software
11 #   distributed under the License is distributed on an "AS IS" BASIS,
12 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 #   See the License for the specific language governing permissions and
14 #   limitations under the License.
15 #
16 # -------------------------------------------------------------------------
17 #
18
19 import collections
20 import itertools
21
22
23 class DotDict(dict):
24     """A dot-dict mixin to be able to access a dictionary via dot notation
25     source: https://stackoverflow.com/questions/2352181/how-to-use-a-dot-to-access-members-of-dictionary
26     """
27     __getattr__ = dict.get
28     __setattr__ = dict.__setitem__
29     __delattr__ = dict.__delitem__
30
31
32 class MetaSingleton(type):
33     """Singleton class (2nd Chapter) from Learning Python Design Patterns - 2nd ed.
34     Chetan Giridhar, Packt Publ. 2016"""
35     _instances = {}
36
37     def __call__(cls, *args, **kwargs):
38         if cls not in cls._instances:
39             cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs)
40         return cls._instances[cls]
41
42
43 def namedtuple_with_defaults(typename, field_names, default_values=()):
44     """A namedtuple with default values -- Stack overflow recipe from Mark Lodato
45     http://stackoverflow.com/questions/11351032/named-tuple-and-optional-keyword-arguments
46     :param typename: Name for the class (same as for namedtuple)
47     :param field_names: Field names (same as for namedtuple)
48     :param default_values: Can be specified as a dictionary or as a list
49     :return: New namedtuple object
50     """
51     T = collections.namedtuple(typename, field_names)
52     T.__new__.__defaults__ = (None,) * len(T._fields)
53     if isinstance(default_values, collections.Mapping):
54         prototype = T(**default_values)
55     else:
56         prototype = T(*default_values)
57     T.__new__.__defaults__ = tuple(prototype)
58     return T
59
60
61 def dot_notation(dict_like, dot_spec):
62     """Return the value corresponding to the dot_spec from a dict_like object
63     :param dict_like: dictionary, JSON, etc.
64     :param dot_spec: a dot notation (e.g. a1.b1.c1.d1 => a1["b1"]["c1"]["d1"])
65     :return: the value referenced by the dot_spec
66     """
67     attrs = dot_spec.split(".")  # we split the path
68     parent = dict_like.get(attrs[0])
69     children = ".".join(attrs[1:])
70     if not (parent and children):  # if no children or no parent, bail out
71         return parent
72     if isinstance(parent, list):  # here, we apply remaining path spec to all children
73         return [dot_notation(j, children) for j in parent]
74     elif isinstance(parent, dict):
75         return dot_notation(parent, children)
76     else:
77         return None
78
79
80 def list_flatten(l):
81     """
82     Flatten a complex nested list of nested lists into a flat list (DFS).
83     For example, [ [1, 2], [[[2,3,4], [2,3,4]], [3,4,5, 'hello']]]
84     will produce [1, 2, 2, 3, 4, 2, 3, 4, 3, 4, 5, 'hello']
85     """
86     return list(itertools.chain(*[list_flatten(j) if isinstance(j, list) else [j] for j in l]))
87
88
89 def inverted_dict(keys: list, key_val_dict: dict) -> dict:
90     """
91     Get val -> [keys] mapping for the given keys using key_val_dict
92     :param keys: the keys we are interested in (a list)
93     :param key_val_dict: the key -> val mapping
94     :return: inverted dictionary of val -> [keys] (for the subset dict of given keys)
95     """
96     res = {}
97     all_tuples = ((k, key_val_dict[k] if k in key_val_dict else 'no-parent-' + k) for k in keys)
98     for k, v in all_tuples:
99         if v in res:
100             res[v].append(k)
101         else:
102             res[v] = [k]
103     # making sure to remove duplicate keys
104     res = dict((v, list(set(k_list))) for v, k_list in res.items())
105     return res