mapper.py 7.52 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Copyright 2016 ZEROFAIL
#
# This file is part of Goblin.
#
# Goblin is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Goblin is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Goblin.  If not, see <http://www.gnu.org/licenses/>.

18
"""Helper functions and class to map between OGM Elements <-> DB Elements"""
19

20
import logging
davebshow's avatar
davebshow committed
21
22
import functools

davebshow's avatar
davebshow committed
23
from goblin import exception
24

25
26
27
logger = logging.getLogger(__name__)


davebshow's avatar
davebshow committed
28
def map_props_to_db(element, mapping):
29
    """Convert OGM property names/values to DB property names/values"""
davebshow's avatar
davebshow committed
30
    property_tuples = []
31
    props = mapping.ogm_properties
davebshow's avatar
davebshow committed
32
    for ogm_name, (db_name, data_type) in props.items():
davebshow's avatar
davebshow committed
33
        val = getattr(element, ogm_name, None)
34
35
36
        if val and isinstance(val, (list, set)):
            card = None
            for v in val:
davebshow's avatar
davebshow committed
37
                metaprops = get_metaprops(v, v.__mapping__)
38
39
40
41
42
                property_tuples.append(
                    (card, db_name, data_type.to_db(v.value), metaprops))
                card = v.cardinality
        else:
            if hasattr(val, '__mapping__'):
davebshow's avatar
davebshow committed
43
                metaprops = get_metaprops(val, val.__mapping__)
44
                val = val.value
davebshow's avatar
davebshow committed
45
46
47
48
            else:
                metaprops = None
            property_tuples.append(
                (None, db_name, data_type.to_db(val), metaprops))
davebshow's avatar
davebshow committed
49
50
51
    return property_tuples


davebshow's avatar
davebshow committed
52
53
54
55
56
57
58
59
60
def get_metaprops(vertex_property, mapping):
    props = mapping.ogm_properties
    metaprops = {}
    for ogm_name, (db_name, data_type) in props.items():
        val = getattr(vertex_property, ogm_name, None)
        metaprops[db_name] = data_type.to_db(val)
    return metaprops


davebshow's avatar
davebshow committed
61
def map_vertex_to_ogm(result, element, *, mapping=None):
62
    """Map a vertex returned by DB to OGM vertex"""
davebshow's avatar
davebshow committed
63
    for db_name, value in result['properties'].items():
davebshow's avatar
davebshow committed
64
        metaprop_dict = {}
65
        if len(value) > 1:
davebshow's avatar
davebshow committed
66
67
68
69
70
71
72
            values = []
            for v in value:
                values.append(v['value'])
                metaprops = v.get('properties', None)
                if metaprops:
                    metaprop_dict[v['value']] = metaprops
            value = values
73
        else:
davebshow's avatar
davebshow committed
74
            metaprops = value[0].get('properties', None)
75
            value = value[0]['value']
davebshow's avatar
davebshow committed
76
77
            if metaprops:
                metaprop_dict[value] = metaprops
78
        name, data_type = mapping.db_properties.get(db_name, (db_name, None))
davebshow's avatar
davebshow committed
79
80
81
        if data_type:
            value = data_type.to_ogm(value)
        setattr(element, name, value)
davebshow's avatar
davebshow committed
82
83
84
        if metaprop_dict:
            vert_prop = getattr(element, name)
            vert_prop.mapper_func(metaprop_dict, vert_prop)
davebshow's avatar
davebshow committed
85
    setattr(element, '__label__', result['label'])
86
    setattr(element, '_id', result['id'])
davebshow's avatar
davebshow committed
87
88
89
    return element


davebshow's avatar
davebshow committed
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
def map_vertex_property_to_ogm(result, element, *, mapping=None):
    """Map a vertex returned by DB to OGM vertex"""
    for val, metaprops in result.items():
        if isinstance(element, (list, set)):
            current = element(val)
        else:
            current = element
        for db_name, value in metaprops.items():
            name, data_type = mapping.db_properties.get(
                db_name, (db_name, None))
            if data_type:
                value = data_type.to_ogm(value)
            setattr(current, name, value)


davebshow's avatar
davebshow committed
105
def map_edge_to_ogm(result, element, *, mapping=None):
106
    """Map an edge returned by DB to OGM edge"""
davebshow's avatar
davebshow committed
107
    for db_name, value in result.get('properties', {}).items():
108
        name, data_type = mapping.db_properties.get(db_name, (db_name, None))
davebshow's avatar
davebshow committed
109
110
111
        if data_type:
            value = data_type.to_ogm(value)
        setattr(element, name, value)
davebshow's avatar
davebshow committed
112
    setattr(element, '__label__', result['label'])
113
    setattr(element, '_id', result['id'])
davebshow's avatar
davebshow committed
114
115
116
117
118
    setattr(element.source, '__label__', result['outVLabel'])
    setattr(element.target, '__label__', result['inVLabel'])
    sid = result['outV']
    esid = getattr(element.source, 'id', None)
    if _check_id(sid, esid):
davebshow's avatar
davebshow committed
119
120
        from goblin.element import GenericVertex
        element.source = GenericVertex()
davebshow's avatar
davebshow committed
121
122
123
    tid = result['inV']
    etid = getattr(element.target, 'id', None)
    if _check_id(tid, etid):
davebshow's avatar
davebshow committed
124
125
        from goblin.element import GenericVertex
        element.target = GenericVertex()
126
127
    setattr(element.source, '_id', sid)
    setattr(element.target, '_id', tid)
davebshow's avatar
davebshow committed
128
129
130
    return element


davebshow's avatar
davebshow committed
131
132
133
134
135
136
137
def _check_id(rid, eid):
    if eid and rid != eid:
        logger.warning('Edge vertex id has changed')
        return True
    return False


davebshow's avatar
davebshow committed
138
# DB <-> OGM Mapping
139
def create_mapping(namespace, properties):
140
    """Constructor for :py:class:`Mapping`"""
davebshow's avatar
davebshow committed
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
    element_type = namespace['__type__']
    if element_type == 'vertex':
        mapping_func = map_vertex_to_ogm
        mapping = Mapping(
            namespace, element_type, mapping_func, properties)
    elif element_type == 'edge':
        mapping_func = map_edge_to_ogm
        mapping = Mapping(
            namespace, element_type, mapping_func, properties)
    elif element_type == 'vertexproperty':
        mapping_func = map_vertex_property_to_ogm
        mapping = Mapping(
            namespace, element_type, mapping_func, properties)
    else:
        mapping = None
    return mapping
davebshow's avatar
davebshow committed
157
158


davebshow's avatar
davebshow committed
159
class Mapping:
davebshow's avatar
davebshow committed
160
161
162
163
    """
    This class stores the information necessary to map between an OGM element
    and a DB element.
    """
davebshow's avatar
davebshow committed
164
    def __init__(self, namespace, element_type, mapper_func, properties):
davebshow's avatar
davebshow committed
165
        self._label = namespace['__label__']
davebshow's avatar
davebshow committed
166
        self._element_type = element_type
davebshow's avatar
davebshow committed
167
        self._mapper_func = functools.partial(mapper_func, mapping=self)
168
169
        self._db_properties = {}
        self._ogm_properties = {}
170
        self._map_properties(properties)
davebshow's avatar
davebshow committed
171
172
173

    @property
    def label(self):
davebshow's avatar
davebshow committed
174
        """Element label"""
davebshow's avatar
davebshow committed
175
176
        return self._label

davebshow's avatar
davebshow committed
177
178
    @property
    def mapper_func(self):
davebshow's avatar
davebshow committed
179
        """Function responsible for mapping db results to ogm"""
davebshow's avatar
davebshow committed
180
181
        return self._mapper_func

davebshow's avatar
davebshow committed
182
    @property
183
184
185
186
187
188
    def db_properties(self):
        """A dictionary of property mappings"""
        return self._db_properties

    @property
    def ogm_properties(self):
davebshow's avatar
davebshow committed
189
        """A dictionary of property mappings"""
190
        return self._ogm_properties
davebshow's avatar
davebshow committed
191

davebshow's avatar
davebshow committed
192
193
    def __getattr__(self, value):
        try:
194
            mapping, _ = self._ogm_properties[value]
davebshow's avatar
davebshow committed
195
196
            return mapping
        except:
davebshow's avatar
davebshow committed
197
198
199
            raise exception.MappingError(
                "unrecognized property {} for class: {}".format(
                    value, self._element_type))
davebshow's avatar
davebshow committed
200

201
    def _map_properties(self, properties):
davebshow's avatar
davebshow committed
202
203
        for name, prop in properties.items():
            data_type = prop.data_type
204
205
206
207
            if prop.db_name:
                db_name = prop.db_name
            else:
                db_name = '{}__{}'.format(self._label, name)
208
209
210
211
212
213
            if hasattr(prop, '__mapping__'):
                if not self._element_type == 'vertex':
                    raise exception.MappingError(
                        'Only vertices can have vertex properties')
            self._db_properties[db_name] = (name, data_type)
            self._ogm_properties[name] = (db_name, data_type)
davebshow's avatar
davebshow committed
214

davebshow's avatar
davebshow committed
215
    def __repr__(self):
216
        return '<{}(type={}, label={}, properties={})>'.format(
davebshow's avatar
davebshow committed
217
            self.__class__.__name__, self._element_type, self._label,
218
            self._ogm_properties)