Completed
Push — master ( dfbec1...7e13b1 )
by Satoru
01:08
created

_gen_id()   A

Complexity

Conditions 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 1
c 2
b 0
f 1
dl 0
loc 5
rs 9.4285
1
#
2
# Copyright (C) 2017 Red Hat, Inc.
3
# License: MIT
4
#
5
"""Flatten nested dicts, etc.
6
"""
7
from __future__ import absolute_import
8
9
import itertools
10
import operator
11
12
import m9dicts.compat
13
import m9dicts.utils
14
15
16
def object_to_id(obj):
17
    """Object -> id.
18
19
    :param obj: Any object has __str__ method to get its ID value
20
21
    >>> object_to_id("test")
22
    '098f6bcd4621d373cade4e832627b4f6'
23
    >>> object_to_id({'a': "test"})
24
    'c5b846ec3b2f1a5b7c44c91678a61f47'
25
    >>> object_to_id(['a', 'b', 'c'])
26
    'eea457285a61f212e4bbaaf890263ab4'
27
    """
28
    return m9dicts.compat.md5(m9dicts.compat.to_str(obj)).hexdigest()
29
30
31
def _gen_id(*args):
32
    """
33
    :return: ID generated from `args`
34
    """
35
    return object_to_id(args)
36
37
38
def _rel_name(rel_name, key, level=0, names=None):
39
    """
40
    :return: Generated composite relation name from relations
41
    """
42
    if names is None:
43
        names = []
44
    name = "rel_%s_%s" % (rel_name, key)
45
    if name in names:
46
        name = "%s_%d" % (name, level)
47
    return name
48
49
50
def _sorted(items):
51
    """
52
    :param items: Any iterables
53
    """
54
    if items and m9dicts.utils.is_dict_like(items[0]):
55
        # return items  # Return as it is because dicts are not ordered.
56
        return sorted(items, key=lambda d: list(d.items()))
57
    else:
58
        return sorted(items)
59
60
61
def _dict_to_rels_itr_0(dic, key, rel_name, pid, **kwargs):
62
    """
63
    :param dic: A dict or dict-like object
64
    :param key: Key name
65
    :param rel_name: Name for parent relations
66
    :param pid: ID of parent object
67
    :param kwargs: Keyword arguments such as level, names
68
    """
69
    cid = dic.get("id", _gen_id(*sorted(dic.items())))
70
    name = _rel_name(rel_name, key, **kwargs)
71
    yield (name, ((key, cid), (rel_name, pid)))
72
73
    for tpl in _dict_to_rels_itr(dic, key, **kwargs):
74
        yield tpl
75
76
77
def _dict_to_rels_itr(dic, rel_name, level=0, names=None):
78
    """
79
    Convert nested dict[s] to tuples of relation name and relations of items in
80
    the dict, and yields each pairs.
81
82
    :param dic: A dict or dict-like object
83
    :param rel_name: Name for relations of items in `dic`
84
    :return: A list of (<relation_name>, [tuple of key and value])
85
86
    >>> list(_dict_to_rels_itr(dict(id=0, a=1, b="b"), "ab"))
87
    [('ab', (('id', 0), ('a', 1), ('b', 'b')))]
88
89
    >>> list(_dict_to_rels_itr(dict(id=0, a=dict(id=1, b=1), d="D"),
90
    ...                        "A"))  # doctest: +NORMALIZE_WHITESPACE
91
    [('A', (('id', 0), ('d', 'D'))),
92
     ('rel_A_a', (('a', 1), ('A', 0))),
93
     ('a', (('id', 1), ('b', 1)))]
94
    """
95
    if names is None:
96
        names = []
97
    else:
98
        names.append(rel_name)
99
100
    lkeys = [k for k, v in dic.items() if m9dicts.utils.is_list_like(v)]
101
    dkeys = [k for k, v in dic.items() if m9dicts.utils.is_dict_like(v)]
102
    items = sorted((k, v) for k, v in dic.items()
103
                   if k != "id" and k not in lkeys and k not in dkeys)
104
    pid = dic.get("id", _gen_id(*items))
105
    yield (rel_name, tuple([("id", pid)] + items))
106
107
    level += 1
108
    kwargs = dict(level=level, names=names)
109
    if lkeys:
110
        for key in sorted(lkeys):
111
            for val in _sorted(dic[key]):
112
                if m9dicts.utils.is_dict_like(val):
113
                    # :todo: Avoid name collision.
114
                    # if name in val:
115
                    #     ...
116
                    for tpl in _dict_to_rels_itr_0(val, key, rel_name, pid,
117
                                                   **kwargs):
118
                        yield tpl
119
                else:
120
                    cid = _gen_id(key, val)
121
                    name = _rel_name(rel_name, key, **kwargs)
122
                    yield (name, (("id", cid), (rel_name, pid), (key, val)))
123
124
    if dkeys:
125
        for key in sorted(dkeys):
126
            for tpl in _dict_to_rels_itr_0(dic[key], key, rel_name, pid,
127
                                           **kwargs):
128
                yield tpl
129
130
131
def dict_to_rels(dic, name=None):
132
    """
133
    Convert nested dict[s] to tuples of relation name and relations of items in
134
    the dict, and yields each pairs.
135
136
    :param dic: A dict or dict-like object
137
    :param name: Name for relations of items in `dic`
138
    :return: A list of (<relation_name>, [tuple of key and value])
139
    """
140
    assert m9dicts.utils.is_dict_like(dic)
141
142
    if name is None:
143
        name = "data"  # Default.
144
145
    fst = operator.itemgetter(0)
146
    rels = _dict_to_rels_itr(dic, name)
147
    return [(k, sorted(t[1] for t in g))
148
            for k, g in itertools.groupby(sorted(rels, key=fst), fst)]
149
150
# vim:sw=4:ts=4:et:
151