~ubuntu-branches/debian/jessie/sqlalchemy/jessie

« back to all changes in this revision

Viewing changes to lib/sqlalchemy/ext/declarative/clsregistry.py

  • Committer: Package Import Robot
  • Author(s): Piotr Ożarowski, Jakub Wilk, Piotr Ożarowski
  • Date: 2013-07-06 20:53:52 UTC
  • mfrom: (1.4.23) (16.1.17 experimental)
  • Revision ID: package-import@ubuntu.com-20130706205352-ryppl1eto3illd79
Tags: 0.8.2-1
[ Jakub Wilk ]
* Use canonical URIs for Vcs-* fields.

[ Piotr Ożarowski ]
* New upstream release
* Upload to unstable
* Build depend on python3-all instead of -dev, extensions are not built for
  Python 3.X 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# ext/declarative/clsregistry.py
 
2
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
 
3
#
 
4
# This module is part of SQLAlchemy and is released under
 
5
# the MIT License: http://www.opensource.org/licenses/mit-license.php
 
6
"""Routines to handle the string class registry used by declarative.
 
7
 
 
8
This system allows specification of classes and expressions used in
 
9
:func:`.relationship` using strings.
 
10
 
 
11
"""
 
12
from ...orm.properties import ColumnProperty, RelationshipProperty, \
 
13
                            SynonymProperty
 
14
from ...schema import _get_table_key
 
15
from ...orm import class_mapper, interfaces
 
16
from ... import util
 
17
from ... import exc
 
18
import weakref
 
19
 
 
20
# strong references to registries which we place in
 
21
# the _decl_class_registry, which is usually weak referencing.
 
22
# the internal registries here link to classes with weakrefs and remove
 
23
# themselves when all references to contained classes are removed.
 
24
_registries = set()
 
25
 
 
26
 
 
27
def add_class(classname, cls):
 
28
    """Add a class to the _decl_class_registry associated with the
 
29
    given declarative class.
 
30
 
 
31
    """
 
32
    if classname in cls._decl_class_registry:
 
33
        # class already exists.
 
34
        existing = cls._decl_class_registry[classname]
 
35
        if not isinstance(existing, _MultipleClassMarker):
 
36
            existing = \
 
37
                cls._decl_class_registry[classname] = \
 
38
                _MultipleClassMarker([cls, existing])
 
39
    else:
 
40
        cls._decl_class_registry[classname] = cls
 
41
 
 
42
    try:
 
43
        root_module = cls._decl_class_registry['_sa_module_registry']
 
44
    except KeyError:
 
45
        cls._decl_class_registry['_sa_module_registry'] = \
 
46
            root_module = _ModuleMarker('_sa_module_registry', None)
 
47
 
 
48
    tokens = cls.__module__.split(".")
 
49
 
 
50
    # build up a tree like this:
 
51
    # modulename:  myapp.snacks.nuts
 
52
    #
 
53
    # myapp->snack->nuts->(classes)
 
54
    # snack->nuts->(classes)
 
55
    # nuts->(classes)
 
56
    #
 
57
    # this allows partial token paths to be used.
 
58
    while tokens:
 
59
        token = tokens.pop(0)
 
60
        module = root_module.get_module(token)
 
61
        for token in tokens:
 
62
            module = module.get_module(token)
 
63
        module.add_class(classname, cls)
 
64
 
 
65
 
 
66
class _MultipleClassMarker(object):
 
67
    """refers to multiple classes of the same name
 
68
    within _decl_class_registry.
 
69
 
 
70
    """
 
71
 
 
72
    def __init__(self, classes, on_remove=None):
 
73
        self.on_remove = on_remove
 
74
        self.contents = set([
 
75
                weakref.ref(item, self._remove_item) for item in classes])
 
76
        _registries.add(self)
 
77
 
 
78
    def __iter__(self):
 
79
        return (ref() for ref in self.contents)
 
80
 
 
81
    def attempt_get(self, path, key):
 
82
        if len(self.contents) > 1:
 
83
            raise exc.InvalidRequestError(
 
84
                "Multiple classes found for path \"%s\" "
 
85
                "in the registry of this declarative "
 
86
                "base. Please use a fully module-qualified path." %
 
87
                (".".join(path + [key]))
 
88
            )
 
89
        else:
 
90
            ref = list(self.contents)[0]
 
91
            cls = ref()
 
92
            if cls is None:
 
93
                raise NameError(key)
 
94
            return cls
 
95
 
 
96
    def _remove_item(self, ref):
 
97
        self.contents.remove(ref)
 
98
        if not self.contents:
 
99
            _registries.discard(self)
 
100
            if self.on_remove:
 
101
                self.on_remove()
 
102
 
 
103
    def add_item(self, item):
 
104
        modules = set([cls().__module__ for cls in self.contents])
 
105
        if item.__module__ in modules:
 
106
            util.warn(
 
107
                "This declarative base already contains a class with the "
 
108
                "same class name and module name as %s.%s, and will "
 
109
                "be replaced in the string-lookup table." % (
 
110
                    item.__module__,
 
111
                    item.__name__
 
112
                )
 
113
            )
 
114
        self.contents.add(weakref.ref(item, self._remove_item))
 
115
 
 
116
 
 
117
class _ModuleMarker(object):
 
118
    """"refers to a module name within
 
119
    _decl_class_registry.
 
120
 
 
121
    """
 
122
    def __init__(self, name, parent):
 
123
        self.parent = parent
 
124
        self.name = name
 
125
        self.contents = {}
 
126
        self.mod_ns = _ModNS(self)
 
127
        if self.parent:
 
128
            self.path = self.parent.path + [self.name]
 
129
        else:
 
130
            self.path = []
 
131
        _registries.add(self)
 
132
 
 
133
    def __contains__(self, name):
 
134
        return name in self.contents
 
135
 
 
136
    def __getitem__(self, name):
 
137
        return self.contents[name]
 
138
 
 
139
    def _remove_item(self, name):
 
140
        self.contents.pop(name, None)
 
141
        if not self.contents and self.parent is not None:
 
142
            self.parent._remove_item(self.name)
 
143
            _registries.discard(self)
 
144
 
 
145
    def resolve_attr(self, key):
 
146
        return getattr(self.mod_ns, key)
 
147
 
 
148
    def get_module(self, name):
 
149
        if name not in self.contents:
 
150
            marker = _ModuleMarker(name, self)
 
151
            self.contents[name] = marker
 
152
        else:
 
153
            marker = self.contents[name]
 
154
        return marker
 
155
 
 
156
    def add_class(self, name, cls):
 
157
        if name in self.contents:
 
158
            existing = self.contents[name]
 
159
            existing.add_item(cls)
 
160
        else:
 
161
            existing = self.contents[name] = \
 
162
                    _MultipleClassMarker([cls],
 
163
                        on_remove=lambda: self._remove_item(name))
 
164
 
 
165
 
 
166
class _ModNS(object):
 
167
    def __init__(self, parent):
 
168
        self.__parent = parent
 
169
 
 
170
    def __getattr__(self, key):
 
171
        try:
 
172
            value = self.__parent.contents[key]
 
173
        except KeyError:
 
174
            pass
 
175
        else:
 
176
            if value is not None:
 
177
                if isinstance(value, _ModuleMarker):
 
178
                    return value.mod_ns
 
179
                else:
 
180
                    assert isinstance(value, _MultipleClassMarker)
 
181
                    return value.attempt_get(self.__parent.path, key)
 
182
        raise AttributeError("Module %r has no mapped classes "
 
183
                    "registered under the name %r" % (self.__parent.name, key))
 
184
 
 
185
 
 
186
class _GetColumns(object):
 
187
    def __init__(self, cls):
 
188
        self.cls = cls
 
189
 
 
190
    def __getattr__(self, key):
 
191
        mp = class_mapper(self.cls, configure=False)
 
192
        if mp:
 
193
            if key not in mp.all_orm_descriptors:
 
194
                raise exc.InvalidRequestError(
 
195
                            "Class %r does not have a mapped column named %r"
 
196
                            % (self.cls, key))
 
197
 
 
198
            desc = mp.all_orm_descriptors[key]
 
199
            if desc.extension_type is interfaces.NOT_EXTENSION:
 
200
                prop = desc.property
 
201
                if isinstance(prop, SynonymProperty):
 
202
                    key = prop.name
 
203
                elif not isinstance(prop, ColumnProperty):
 
204
                    raise exc.InvalidRequestError(
 
205
                                "Property %r is not an instance of"
 
206
                                " ColumnProperty (i.e. does not correspond"
 
207
                                " directly to a Column)." % key)
 
208
        return getattr(self.cls, key)
 
209
 
 
210
 
 
211
class _GetTable(object):
 
212
    def __init__(self, key, metadata):
 
213
        self.key = key
 
214
        self.metadata = metadata
 
215
 
 
216
    def __getattr__(self, key):
 
217
        return self.metadata.tables[
 
218
                _get_table_key(key, self.key)
 
219
            ]
 
220
 
 
221
 
 
222
def _determine_container(key, value):
 
223
    if isinstance(value, _MultipleClassMarker):
 
224
        value = value.attempt_get([], key)
 
225
    return _GetColumns(value)
 
226
 
 
227
 
 
228
def _resolver(cls, prop):
 
229
    def resolve_arg(arg):
 
230
        import sqlalchemy
 
231
        from sqlalchemy.orm import foreign, remote
 
232
 
 
233
        fallback = sqlalchemy.__dict__.copy()
 
234
        fallback.update({'foreign': foreign, 'remote': remote})
 
235
 
 
236
        def access_cls(key):
 
237
            if key in cls._decl_class_registry:
 
238
                return _determine_container(key, cls._decl_class_registry[key])
 
239
            elif key in cls.metadata.tables:
 
240
                return cls.metadata.tables[key]
 
241
            elif key in cls.metadata._schemas:
 
242
                return _GetTable(key, cls.metadata)
 
243
            elif '_sa_module_registry' in cls._decl_class_registry and \
 
244
                key in cls._decl_class_registry['_sa_module_registry']:
 
245
                registry = cls._decl_class_registry['_sa_module_registry']
 
246
                return registry.resolve_attr(key)
 
247
            else:
 
248
                return fallback[key]
 
249
 
 
250
        d = util.PopulateDict(access_cls)
 
251
 
 
252
        def return_cls():
 
253
            try:
 
254
                x = eval(arg, globals(), d)
 
255
 
 
256
                if isinstance(x, _GetColumns):
 
257
                    return x.cls
 
258
                else:
 
259
                    return x
 
260
            except NameError, n:
 
261
                raise exc.InvalidRequestError(
 
262
                    "When initializing mapper %s, expression %r failed to "
 
263
                    "locate a name (%r). If this is a class name, consider "
 
264
                    "adding this relationship() to the %r class after "
 
265
                    "both dependent classes have been defined." %
 
266
                    (prop.parent, arg, n.args[0], cls)
 
267
                )
 
268
        return return_cls
 
269
    return resolve_arg
 
270
 
 
271
 
 
272
def _deferred_relationship(cls, prop):
 
273
 
 
274
    if isinstance(prop, RelationshipProperty):
 
275
        resolve_arg = _resolver(cls, prop)
 
276
 
 
277
        for attr in ('argument', 'order_by', 'primaryjoin', 'secondaryjoin',
 
278
                     'secondary', '_user_defined_foreign_keys', 'remote_side'):
 
279
            v = getattr(prop, attr)
 
280
            if isinstance(v, basestring):
 
281
                setattr(prop, attr, resolve_arg(v))
 
282
 
 
283
        if prop.backref and isinstance(prop.backref, tuple):
 
284
            key, kwargs = prop.backref
 
285
            for attr in ('primaryjoin', 'secondaryjoin', 'secondary',
 
286
                         'foreign_keys', 'remote_side', 'order_by'):
 
287
                if attr in kwargs and isinstance(kwargs[attr], basestring):
 
288
                    kwargs[attr] = resolve_arg(kwargs[attr])
 
289
 
 
290
    return prop