1
# ext/declarative/clsregistry.py
2
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
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.
8
This system allows specification of classes and expressions used in
9
:func:`.relationship` using strings.
12
from ...orm.properties import ColumnProperty, RelationshipProperty, \
14
from ...schema import _get_table_key
15
from ...orm import class_mapper, interfaces
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.
27
def add_class(classname, cls):
28
"""Add a class to the _decl_class_registry associated with the
29
given declarative class.
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):
37
cls._decl_class_registry[classname] = \
38
_MultipleClassMarker([cls, existing])
40
cls._decl_class_registry[classname] = cls
43
root_module = cls._decl_class_registry['_sa_module_registry']
45
cls._decl_class_registry['_sa_module_registry'] = \
46
root_module = _ModuleMarker('_sa_module_registry', None)
48
tokens = cls.__module__.split(".")
50
# build up a tree like this:
51
# modulename: myapp.snacks.nuts
53
# myapp->snack->nuts->(classes)
54
# snack->nuts->(classes)
57
# this allows partial token paths to be used.
60
module = root_module.get_module(token)
62
module = module.get_module(token)
63
module.add_class(classname, cls)
66
class _MultipleClassMarker(object):
67
"""refers to multiple classes of the same name
68
within _decl_class_registry.
72
def __init__(self, classes, on_remove=None):
73
self.on_remove = on_remove
75
weakref.ref(item, self._remove_item) for item in classes])
79
return (ref() for ref in self.contents)
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]))
90
ref = list(self.contents)[0]
96
def _remove_item(self, ref):
97
self.contents.remove(ref)
99
_registries.discard(self)
103
def add_item(self, item):
104
modules = set([cls().__module__ for cls in self.contents])
105
if item.__module__ in modules:
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." % (
114
self.contents.add(weakref.ref(item, self._remove_item))
117
class _ModuleMarker(object):
118
""""refers to a module name within
119
_decl_class_registry.
122
def __init__(self, name, parent):
126
self.mod_ns = _ModNS(self)
128
self.path = self.parent.path + [self.name]
131
_registries.add(self)
133
def __contains__(self, name):
134
return name in self.contents
136
def __getitem__(self, name):
137
return self.contents[name]
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)
145
def resolve_attr(self, key):
146
return getattr(self.mod_ns, key)
148
def get_module(self, name):
149
if name not in self.contents:
150
marker = _ModuleMarker(name, self)
151
self.contents[name] = marker
153
marker = self.contents[name]
156
def add_class(self, name, cls):
157
if name in self.contents:
158
existing = self.contents[name]
159
existing.add_item(cls)
161
existing = self.contents[name] = \
162
_MultipleClassMarker([cls],
163
on_remove=lambda: self._remove_item(name))
166
class _ModNS(object):
167
def __init__(self, parent):
168
self.__parent = parent
170
def __getattr__(self, key):
172
value = self.__parent.contents[key]
176
if value is not None:
177
if isinstance(value, _ModuleMarker):
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))
186
class _GetColumns(object):
187
def __init__(self, cls):
190
def __getattr__(self, key):
191
mp = class_mapper(self.cls, configure=False)
193
if key not in mp.all_orm_descriptors:
194
raise exc.InvalidRequestError(
195
"Class %r does not have a mapped column named %r"
198
desc = mp.all_orm_descriptors[key]
199
if desc.extension_type is interfaces.NOT_EXTENSION:
201
if isinstance(prop, SynonymProperty):
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)
211
class _GetTable(object):
212
def __init__(self, key, metadata):
214
self.metadata = metadata
216
def __getattr__(self, key):
217
return self.metadata.tables[
218
_get_table_key(key, self.key)
222
def _determine_container(key, value):
223
if isinstance(value, _MultipleClassMarker):
224
value = value.attempt_get([], key)
225
return _GetColumns(value)
228
def _resolver(cls, prop):
229
def resolve_arg(arg):
231
from sqlalchemy.orm import foreign, remote
233
fallback = sqlalchemy.__dict__.copy()
234
fallback.update({'foreign': foreign, 'remote': remote})
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)
250
d = util.PopulateDict(access_cls)
254
x = eval(arg, globals(), d)
256
if isinstance(x, _GetColumns):
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)
272
def _deferred_relationship(cls, prop):
274
if isinstance(prop, RelationshipProperty):
275
resolve_arg = _resolver(cls, prop)
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))
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])