~free.ekanayaka/storm/infoheritance

« back to all changes in this revision

Viewing changes to storm/references.py

  • Committer: Free Ekanayaka
  • Date: 2011-10-25 10:17:32 UTC
  • Revision ID: free.ekanayaka@canonical.com-20111025101732-chgjhm27mg3rw8o4
Add polymorphic support to Reference

Show diffs side-by-side

added added

removed removed

Lines of Context:
111
111
    # warnings!).
112
112
    _relation = LazyAttribute("_relation", "_build_relation")
113
113
 
114
 
    def __init__(self, local_key, remote_key, on_remote=False):
 
114
    def __init__(self, local_key, remote_key, on_remote=False, poly_type=None):
115
115
        """
116
116
        Create a Reference property.
117
117
 
124
124
        @param on_remote: If specified, then the reference is
125
125
            backwards: It is the C{remote_key} which is a foreign key
126
126
            onto C{local_key}.
 
127
        @param poly_type: If specified, the sibling column that is used
 
128
            to determine the type of the referred-to object. In this case,
 
129
            the C{remote_key} parameter must be a C{str} which will resolve
 
130
            to the matching column of the referred-to type.
127
131
        """
128
132
        self._local_key = local_key
129
133
        self._remote_key = remote_key
130
134
        self._on_remote = on_remote
 
135
        self._poly_type = poly_type
131
136
        self._cls = None
132
137
 
133
138
    def __get__(self, local, cls=None):
141
146
        if local is None:
142
147
            return self
143
148
 
144
 
        remote = self._relation.get_remote(local)
 
149
        if isinstance(self._relation, Relation):
 
150
            relation = self._relation
 
151
        else:
 
152
            # This means the reference is polymorfic and we have to select
 
153
            # the relation according to the poly type
 
154
            relation = self._select_relation(local)
 
155
 
 
156
        remote = relation.get_remote(local)
145
157
        if remote is not None:
146
158
            return remote
147
159
 
148
 
        if self._relation.local_variables_are_none(local):
 
160
        if relation.local_variables_are_none(local):
149
161
            return None
150
162
 
151
163
        store = Store.of(local)
152
164
        if store is None:
153
165
            return None
154
166
 
155
 
        if self._relation.remote_key_is_primary:
156
 
            remote = store.get(self._relation.remote_cls,
157
 
                               self._relation.get_local_variables(local))
 
167
        if relation.remote_key_is_primary:
 
168
            remote = store.get(relation.remote_cls,
 
169
                               relation.get_local_variables(local))
158
170
        else:
159
 
            where = self._relation.get_where_for_remote(local)
160
 
            result = store.find(self._relation.remote_cls, where)
 
171
            where = relation.get_where_for_remote(local)
 
172
            result = store.find(relation.remote_cls, where)
161
173
            remote = result.one()
162
174
 
163
175
        if remote is not None:
164
 
            self._relation.link(local, remote)
 
176
            relation.link(local, remote)
165
177
 
166
178
        return remote
167
179
 
172
184
        if self._cls is None:
173
185
            self._cls = _find_descriptor_class(local.__class__, self)
174
186
 
 
187
        if isinstance(self._relation, Relation):
 
188
            relation = self._relation
 
189
        else:
 
190
            # This means the reference is polymorfic and we have to select
 
191
            # the relation according to the poly type
 
192
            relation = self._select_relation(local, remote)
 
193
 
175
194
        if remote is None:
176
195
            if self._on_remote:
177
196
                remote = self.__get__(local)
178
197
                if remote is None:
179
198
                    return
180
199
            else:
181
 
                remote = self._relation.get_remote(local)
 
200
                remote = relation.get_remote(local)
182
201
            if remote is None:
183
202
                remote_info = None
184
203
            else:
185
204
                remote_info = get_obj_info(remote)
186
 
            self._relation.unlink(get_obj_info(local), remote_info, True)
 
205
                if self._poly_type:
 
206
                    # We also need to reset the poly type
 
207
                    local_info = get_obj_info(local)
 
208
                    local_info.variables[self._poly_column].set(None)
 
209
            relation.unlink(get_obj_info(local), remote_info, True)
187
210
        else:
188
211
            # Don't use remote here, as it might be
189
212
            # security proxied or something.
190
213
            try:
191
214
                remote = get_obj_info(remote).get_obj()
192
215
            except ClassInfoError:
193
 
                pass # It might fail when remote is a tuple or a raw value.
194
 
            self._relation.link(local, remote, True)
 
216
                pass  # It might fail when remote is a tuple or a raw value.
 
217
            relation.link(local, remote, True)
195
218
 
196
219
    def _build_relation(self):
197
220
        resolver = PropertyResolver(self, self._cls)
198
221
        self._local_key = resolver.resolve(self._local_key)
199
 
        self._remote_key = resolver.resolve(self._remote_key)
200
 
        self._relation = Relation(self._local_key, self._remote_key,
201
 
                                  False, self._on_remote)
 
222
        if self._poly_type is None:
 
223
            self._remote_key = resolver.resolve(self._remote_key)
 
224
            self._relation = Relation(self._local_key, self._remote_key, False,
 
225
                                      self._on_remote)
 
226
        else:
 
227
            # The reference is polymorfic, we will build relations lazily,
 
228
            # saving them in a poly_type -> poly_class mapping
 
229
            self._relation = {}
 
230
            if type(self._remote_key) is not tuple:
 
231
                self._remote_key = tuple((self._remote_key,))
 
232
            self._poly_column = resolver.resolve_one(self._poly_type)
 
233
 
 
234
    def _select_relation(self, local, remote=None):
 
235
        """
 
236
        Select the correct L{Relation} object for the polymorphic type at hand,
 
237
        building if it's the first time we hit this type.
 
238
        """
 
239
        local_info = get_obj_info(local)
 
240
        local_poly_type = local_info.variables[self._poly_column].get()
 
241
        if remote is None:
 
242
            # We're being called from __get__, as poly type we use the value of
 
243
            # the given poly_type column on the local object
 
244
            poly_type = local_poly_type
 
245
        else:
 
246
            # We're being called from __set__, as the poly type we use the
 
247
            # value stored in the remote obect's class by register_poly_type
 
248
            remote_poly_type = getattr(remote.__class__,
 
249
                                       self._poly_column.name)
 
250
            # Set the poly_type column of the local object to the poly
 
251
            # type value found in the remote object's class
 
252
            local_info.variables[self._poly_column].set(remote_poly_type)
 
253
            poly_type = remote_poly_type
 
254
 
 
255
        relation = self._relation.get(poly_type)
 
256
 
 
257
        if relation is None:
 
258
            # The relation for the given poly type is not in the map, build
 
259
            # it now
 
260
            if remote is None:
 
261
                remote_class = self._poly_lookup[poly_type]
 
262
            else:
 
263
                remote_class = remote.__class__
 
264
 
 
265
            if hasattr(remote_class, "__storm_table__"):
 
266
                # The remote object is a Storm object, so build a regular
 
267
                # relation
 
268
                resolver = PropertyResolver(self, self._cls)
 
269
                remote_key = resolver.resolve(
 
270
                    tuple(getattr(remote_class, k) for k in self._remote_key))
 
271
                relation = Relation(self._local_key, remote_key, False,
 
272
                                    self._on_remote)
 
273
            else:
 
274
                relation = RemoteFactory(remote_class, self._poly_column)
 
275
 
 
276
            self._relation[poly_type] = relation
 
277
        return relation
202
278
 
203
279
    def __eq__(self, other):
204
280
        return self._relation.get_where_for_local(other)
892
968
            return self._r_to_l.setdefault(local_cls, map).get(remote_column)
893
969
 
894
970
 
 
971
class RemoteFactory(object):
 
972
    """Factory for in-memory remote object references."""
 
973
 
 
974
    def __init__(self, remote_class, poly_column):
 
975
        self._remote_class = remote_class
 
976
        self._local_attr = "__storm_poly_remote_%s_" % poly_column.name
 
977
 
 
978
    def get_remote(self, local):
 
979
        remote = getattr(local, self._local_attr, None)
 
980
        if remote is None:
 
981
            remote = self._remote_class.__new__(self._remote_class)
 
982
            self.link(local, remote, False)
 
983
        return remote
 
984
 
 
985
    def link(self, local, remote, setting):
 
986
        setattr(local, self._local_attr, remote)
 
987
        if not setting and getattr(remote, "link", None) is not None:
 
988
            remote.link(local)
 
989
 
 
990
 
895
991
class PropertyResolver(object):
896
992
    """Transform strings and pure properties (non-columns) into columns."""
897
993
 
943
1039
            if _descr is descr:
944
1040
                return getattr(cls, attr)
945
1041
    raise RuntimeError("Reference used in an unknown class")
 
1042
 
 
1043
 
 
1044
def register_poly_type(cls, type, on):
 
1045
    """Register a new type for a polymorfic reference.
 
1046
 
 
1047
    @param cls: The class of the new type to register.
 
1048
    @param type: A unique identifier for the class, usually an integer value.
 
1049
    @param on: The polymorfic L{Reference} to associated the type with.
 
1050
    """
 
1051
    if getattr(on, "_poly_lookup", None) is None:
 
1052
        on._poly_lookup = {}
 
1053
    if type in on._poly_lookup:
 
1054
        raise RuntimeError("poly type already registered")
 
1055
    on._poly_lookup[type] = cls
 
1056
    for attr, value in on._cls.__dict__.iteritems():
 
1057
        if value is on._poly_type:
 
1058
            setattr(cls, attr, type)
 
1059
            break
 
1060
    else:
 
1061
        raise RuntimeError("parent attr not found")
 
1062
 
 
1063
 
 
1064
def unregister_poly_type(cls, on):
 
1065
    """Unregister a type of a polymorphic reference."""
 
1066
    for attr, value in on._cls.__dict__.iteritems():
 
1067
        if value is on._poly_type:
 
1068
            del on._poly_lookup[getattr(cls, attr)]
 
1069
            break
 
1070
    else:
 
1071
        raise RuntimeError("parent attr not found")
 
1072
 
 
1073
 
 
1074
def unregister_poly_types(on):
 
1075
    """Unregister all types on the given polymorfic reference."""
 
1076
    on._poly_lookup.clear()
 
1077
 
 
1078
 
 
1079
def poly_type(type, on):
 
1080
    """
 
1081
    Decorator used to register a class as a type for a polymorfic reference.
 
1082
    """
 
1083
 
 
1084
    def register(cls):
 
1085
        register_poly_type(cls, type, on)
 
1086
        return cls
 
1087
 
 
1088
    return register