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

« back to all changes in this revision

Viewing changes to lib/sqlalchemy/ext/declarative/base.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/base.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
"""Internal implementation for declarative."""
 
7
 
 
8
from ...schema import Table, Column
 
9
from ...orm import mapper, class_mapper
 
10
from ...orm.interfaces import MapperProperty
 
11
from ...orm.properties import ColumnProperty, CompositeProperty
 
12
from ...orm.util import _is_mapped_class
 
13
from ... import util, exc
 
14
from ...sql import expression
 
15
from ... import event
 
16
from . import clsregistry
 
17
 
 
18
 
 
19
def _declared_mapping_info(cls):
 
20
    # deferred mapping
 
21
    if cls in _MapperConfig.configs:
 
22
        return _MapperConfig.configs[cls]
 
23
    # regular mapping
 
24
    elif _is_mapped_class(cls):
 
25
        return class_mapper(cls, configure=False)
 
26
    else:
 
27
        return None
 
28
 
 
29
 
 
30
def _as_declarative(cls, classname, dict_):
 
31
    from .api import declared_attr
 
32
 
 
33
    # dict_ will be a dictproxy, which we can't write to, and we need to!
 
34
    dict_ = dict(dict_)
 
35
 
 
36
    column_copies = {}
 
37
    potential_columns = {}
 
38
 
 
39
    mapper_args_fn = None
 
40
    table_args = inherited_table_args = None
 
41
    tablename = None
 
42
 
 
43
    declarative_props = (declared_attr, util.classproperty)
 
44
 
 
45
    for base in cls.__mro__:
 
46
        _is_declarative_inherits = hasattr(base, '_decl_class_registry')
 
47
 
 
48
        if '__declare_last__' in base.__dict__:
 
49
            @event.listens_for(mapper, "after_configured")
 
50
            def go():
 
51
                cls.__declare_last__()
 
52
        if '__abstract__' in base.__dict__:
 
53
            if (base is cls or
 
54
                (base in cls.__bases__ and not _is_declarative_inherits)
 
55
            ):
 
56
                return
 
57
 
 
58
        class_mapped = _declared_mapping_info(base) is not None
 
59
 
 
60
        for name, obj in vars(base).items():
 
61
            if name == '__mapper_args__':
 
62
                if not mapper_args_fn and (
 
63
                                        not class_mapped or
 
64
                                        isinstance(obj, declarative_props)
 
65
                                    ):
 
66
                    # don't even invoke __mapper_args__ until
 
67
                    # after we've determined everything about the
 
68
                    # mapped table.
 
69
                    mapper_args_fn = lambda: cls.__mapper_args__
 
70
            elif name == '__tablename__':
 
71
                if not tablename and (
 
72
                                        not class_mapped or
 
73
                                        isinstance(obj, declarative_props)
 
74
                                    ):
 
75
                    tablename = cls.__tablename__
 
76
            elif name == '__table_args__':
 
77
                if not table_args and (
 
78
                                        not class_mapped or
 
79
                                        isinstance(obj, declarative_props)
 
80
                                    ):
 
81
                    table_args = cls.__table_args__
 
82
                    if not isinstance(table_args, (tuple, dict, type(None))):
 
83
                        raise exc.ArgumentError(
 
84
                                "__table_args__ value must be a tuple, "
 
85
                                "dict, or None")
 
86
                    if base is not cls:
 
87
                        inherited_table_args = True
 
88
            elif class_mapped:
 
89
                if isinstance(obj, declarative_props):
 
90
                    util.warn("Regular (i.e. not __special__) "
 
91
                            "attribute '%s.%s' uses @declared_attr, "
 
92
                            "but owning class %s is mapped - "
 
93
                            "not applying to subclass %s."
 
94
                            % (base.__name__, name, base, cls))
 
95
                continue
 
96
            elif base is not cls:
 
97
                # we're a mixin.
 
98
                if isinstance(obj, Column):
 
99
                    if getattr(cls, name) is not obj:
 
100
                        # if column has been overridden
 
101
                        # (like by the InstrumentedAttribute of the
 
102
                        # superclass), skip
 
103
                        continue
 
104
                    if obj.foreign_keys:
 
105
                        raise exc.InvalidRequestError(
 
106
                        "Columns with foreign keys to other columns "
 
107
                        "must be declared as @declared_attr callables "
 
108
                        "on declarative mixin classes. ")
 
109
                    if name not in dict_ and not (
 
110
                            '__table__' in dict_ and
 
111
                            (obj.name or name) in dict_['__table__'].c
 
112
                            ) and name not in potential_columns:
 
113
                        potential_columns[name] = \
 
114
                                column_copies[obj] = \
 
115
                                obj.copy()
 
116
                        column_copies[obj]._creation_order = \
 
117
                                obj._creation_order
 
118
                elif isinstance(obj, MapperProperty):
 
119
                    raise exc.InvalidRequestError(
 
120
                        "Mapper properties (i.e. deferred,"
 
121
                        "column_property(), relationship(), etc.) must "
 
122
                        "be declared as @declared_attr callables "
 
123
                        "on declarative mixin classes.")
 
124
                elif isinstance(obj, declarative_props):
 
125
                    dict_[name] = ret = \
 
126
                            column_copies[obj] = getattr(cls, name)
 
127
                    if isinstance(ret, (Column, MapperProperty)) and \
 
128
                        ret.doc is None:
 
129
                        ret.doc = obj.__doc__
 
130
 
 
131
    # apply inherited columns as we should
 
132
    for k, v in potential_columns.items():
 
133
        dict_[k] = v
 
134
 
 
135
    if inherited_table_args and not tablename:
 
136
        table_args = None
 
137
 
 
138
    clsregistry.add_class(classname, cls)
 
139
    our_stuff = util.OrderedDict()
 
140
 
 
141
    for k in list(dict_):
 
142
 
 
143
        # TODO: improve this ?  all dunders ?
 
144
        if k in ('__table__', '__tablename__', '__mapper_args__'):
 
145
            continue
 
146
 
 
147
        value = dict_[k]
 
148
        if isinstance(value, declarative_props):
 
149
            value = getattr(cls, k)
 
150
 
 
151
        if (isinstance(value, tuple) and len(value) == 1 and
 
152
            isinstance(value[0], (Column, MapperProperty))):
 
153
            util.warn("Ignoring declarative-like tuple value of attribute "
 
154
                      "%s: possibly a copy-and-paste error with a comma "
 
155
                      "left at the end of the line?" % k)
 
156
            continue
 
157
        if not isinstance(value, (Column, MapperProperty)):
 
158
            if not k.startswith('__'):
 
159
                dict_.pop(k)
 
160
                setattr(cls, k, value)
 
161
            continue
 
162
        if k == 'metadata':
 
163
            raise exc.InvalidRequestError(
 
164
                "Attribute name 'metadata' is reserved "
 
165
                "for the MetaData instance when using a "
 
166
                "declarative base class."
 
167
            )
 
168
        prop = clsregistry._deferred_relationship(cls, value)
 
169
        our_stuff[k] = prop
 
170
 
 
171
    # set up attributes in the order they were created
 
172
    our_stuff.sort(key=lambda key: our_stuff[key]._creation_order)
 
173
 
 
174
    # extract columns from the class dict
 
175
    declared_columns = set()
 
176
    for key, c in our_stuff.iteritems():
 
177
        if isinstance(c, (ColumnProperty, CompositeProperty)):
 
178
            for col in c.columns:
 
179
                if isinstance(col, Column) and \
 
180
                    col.table is None:
 
181
                    _undefer_column_name(key, col)
 
182
                    declared_columns.add(col)
 
183
        elif isinstance(c, Column):
 
184
            _undefer_column_name(key, c)
 
185
            declared_columns.add(c)
 
186
            # if the column is the same name as the key,
 
187
            # remove it from the explicit properties dict.
 
188
            # the normal rules for assigning column-based properties
 
189
            # will take over, including precedence of columns
 
190
            # in multi-column ColumnProperties.
 
191
            if key == c.key:
 
192
                del our_stuff[key]
 
193
    declared_columns = sorted(
 
194
        declared_columns, key=lambda c: c._creation_order)
 
195
    table = None
 
196
 
 
197
    if hasattr(cls, '__table_cls__'):
 
198
        table_cls = util.unbound_method_to_callable(cls.__table_cls__)
 
199
    else:
 
200
        table_cls = Table
 
201
 
 
202
    if '__table__' not in dict_:
 
203
        if tablename is not None:
 
204
 
 
205
            args, table_kw = (), {}
 
206
            if table_args:
 
207
                if isinstance(table_args, dict):
 
208
                    table_kw = table_args
 
209
                elif isinstance(table_args, tuple):
 
210
                    if isinstance(table_args[-1], dict):
 
211
                        args, table_kw = table_args[0:-1], table_args[-1]
 
212
                    else:
 
213
                        args = table_args
 
214
 
 
215
            autoload = dict_.get('__autoload__')
 
216
            if autoload:
 
217
                table_kw['autoload'] = True
 
218
 
 
219
            cls.__table__ = table = table_cls(
 
220
                tablename, cls.metadata,
 
221
                *(tuple(declared_columns) + tuple(args)),
 
222
                **table_kw)
 
223
    else:
 
224
        table = cls.__table__
 
225
        if declared_columns:
 
226
            for c in declared_columns:
 
227
                if not table.c.contains_column(c):
 
228
                    raise exc.ArgumentError(
 
229
                        "Can't add additional column %r when "
 
230
                        "specifying __table__" % c.key
 
231
                    )
 
232
 
 
233
    if hasattr(cls, '__mapper_cls__'):
 
234
        mapper_cls = util.unbound_method_to_callable(cls.__mapper_cls__)
 
235
    else:
 
236
        mapper_cls = mapper
 
237
 
 
238
    for c in cls.__bases__:
 
239
        if _declared_mapping_info(c) is not None:
 
240
            inherits = c
 
241
            break
 
242
    else:
 
243
        inherits = None
 
244
 
 
245
    if table is None and inherits is None:
 
246
        raise exc.InvalidRequestError(
 
247
            "Class %r does not have a __table__ or __tablename__ "
 
248
            "specified and does not inherit from an existing "
 
249
            "table-mapped class." % cls
 
250
            )
 
251
    elif inherits:
 
252
        inherited_mapper = _declared_mapping_info(inherits)
 
253
        inherited_table = inherited_mapper.local_table
 
254
        inherited_mapped_table = inherited_mapper.mapped_table
 
255
 
 
256
        if table is None:
 
257
            # single table inheritance.
 
258
            # ensure no table args
 
259
            if table_args:
 
260
                raise exc.ArgumentError(
 
261
                    "Can't place __table_args__ on an inherited class "
 
262
                    "with no table."
 
263
                    )
 
264
            # add any columns declared here to the inherited table.
 
265
            for c in declared_columns:
 
266
                if c.primary_key:
 
267
                    raise exc.ArgumentError(
 
268
                        "Can't place primary key columns on an inherited "
 
269
                        "class with no table."
 
270
                        )
 
271
                if c.name in inherited_table.c:
 
272
                    if inherited_table.c[c.name] is c:
 
273
                        continue
 
274
                    raise exc.ArgumentError(
 
275
                        "Column '%s' on class %s conflicts with "
 
276
                        "existing column '%s'" %
 
277
                        (c, cls, inherited_table.c[c.name])
 
278
                    )
 
279
                inherited_table.append_column(c)
 
280
                if inherited_mapped_table is not None and \
 
281
                    inherited_mapped_table is not inherited_table:
 
282
                    inherited_mapped_table._refresh_for_new_column(c)
 
283
 
 
284
    mt = _MapperConfig(mapper_cls,
 
285
                       cls, table,
 
286
                       inherits,
 
287
                       declared_columns,
 
288
                       column_copies,
 
289
                       our_stuff,
 
290
                       mapper_args_fn)
 
291
    if not hasattr(cls, '_sa_decl_prepare'):
 
292
        mt.map()
 
293
 
 
294
 
 
295
class _MapperConfig(object):
 
296
    configs = util.OrderedDict()
 
297
    mapped_table = None
 
298
 
 
299
    def __init__(self, mapper_cls,
 
300
                        cls,
 
301
                        table,
 
302
                        inherits,
 
303
                        declared_columns,
 
304
                        column_copies,
 
305
                        properties, mapper_args_fn):
 
306
        self.mapper_cls = mapper_cls
 
307
        self.cls = cls
 
308
        self.local_table = table
 
309
        self.inherits = inherits
 
310
        self.properties = properties
 
311
        self.mapper_args_fn = mapper_args_fn
 
312
        self.declared_columns = declared_columns
 
313
        self.column_copies = column_copies
 
314
        self.configs[cls] = self
 
315
 
 
316
    def _prepare_mapper_arguments(self):
 
317
        properties = self.properties
 
318
        if self.mapper_args_fn:
 
319
            mapper_args = self.mapper_args_fn()
 
320
        else:
 
321
            mapper_args = {}
 
322
 
 
323
        # make sure that column copies are used rather
 
324
        # than the original columns from any mixins
 
325
        for k in ('version_id_col', 'polymorphic_on',):
 
326
            if k in mapper_args:
 
327
                v = mapper_args[k]
 
328
                mapper_args[k] = self.column_copies.get(v, v)
 
329
 
 
330
        assert 'inherits' not in mapper_args, \
 
331
            "Can't specify 'inherits' explicitly with declarative mappings"
 
332
 
 
333
        if self.inherits:
 
334
            mapper_args['inherits'] = self.inherits
 
335
 
 
336
        if self.inherits and not mapper_args.get('concrete', False):
 
337
            # single or joined inheritance
 
338
            # exclude any cols on the inherited table which are
 
339
            # not mapped on the parent class, to avoid
 
340
            # mapping columns specific to sibling/nephew classes
 
341
            inherited_mapper = _declared_mapping_info(self.inherits)
 
342
            inherited_table = inherited_mapper.local_table
 
343
 
 
344
            if 'exclude_properties' not in mapper_args:
 
345
                mapper_args['exclude_properties'] = exclude_properties = \
 
346
                    set([c.key for c in inherited_table.c
 
347
                         if c not in inherited_mapper._columntoproperty])
 
348
                exclude_properties.difference_update(
 
349
                        [c.key for c in self.declared_columns])
 
350
 
 
351
            # look through columns in the current mapper that
 
352
            # are keyed to a propname different than the colname
 
353
            # (if names were the same, we'd have popped it out above,
 
354
            # in which case the mapper makes this combination).
 
355
            # See if the superclass has a similar column property.
 
356
            # If so, join them together.
 
357
            for k, col in properties.items():
 
358
                if not isinstance(col, expression.ColumnElement):
 
359
                    continue
 
360
                if k in inherited_mapper._props:
 
361
                    p = inherited_mapper._props[k]
 
362
                    if isinstance(p, ColumnProperty):
 
363
                        # note here we place the subclass column
 
364
                        # first.  See [ticket:1892] for background.
 
365
                        properties[k] = [col] + p.columns
 
366
        result_mapper_args = mapper_args.copy()
 
367
        result_mapper_args['properties'] = properties
 
368
        return result_mapper_args
 
369
 
 
370
    def map(self):
 
371
        self.configs.pop(self.cls, None)
 
372
        mapper_args = self._prepare_mapper_arguments()
 
373
        self.cls.__mapper__ = self.mapper_cls(
 
374
            self.cls,
 
375
            self.local_table,
 
376
            **mapper_args
 
377
        )
 
378
 
 
379
 
 
380
def _add_attribute(cls, key, value):
 
381
    """add an attribute to an existing declarative class.
 
382
 
 
383
    This runs through the logic to determine MapperProperty,
 
384
    adds it to the Mapper, adds a column to the mapped Table, etc.
 
385
 
 
386
    """
 
387
    if '__mapper__' in cls.__dict__:
 
388
        if isinstance(value, Column):
 
389
            _undefer_column_name(key, value)
 
390
            cls.__table__.append_column(value)
 
391
            cls.__mapper__.add_property(key, value)
 
392
        elif isinstance(value, ColumnProperty):
 
393
            for col in value.columns:
 
394
                if isinstance(col, Column) and col.table is None:
 
395
                    _undefer_column_name(key, col)
 
396
                    cls.__table__.append_column(col)
 
397
            cls.__mapper__.add_property(key, value)
 
398
        elif isinstance(value, MapperProperty):
 
399
            cls.__mapper__.add_property(
 
400
                key,
 
401
                clsregistry._deferred_relationship(cls, value)
 
402
            )
 
403
        else:
 
404
            type.__setattr__(cls, key, value)
 
405
    else:
 
406
        type.__setattr__(cls, key, value)
 
407
 
 
408
 
 
409
def _declarative_constructor(self, **kwargs):
 
410
    """A simple constructor that allows initialization from kwargs.
 
411
 
 
412
    Sets attributes on the constructed instance using the names and
 
413
    values in ``kwargs``.
 
414
 
 
415
    Only keys that are present as
 
416
    attributes of the instance's class are allowed. These could be,
 
417
    for example, any mapped columns or relationships.
 
418
    """
 
419
    cls_ = type(self)
 
420
    for k in kwargs:
 
421
        if not hasattr(cls_, k):
 
422
            raise TypeError(
 
423
                "%r is an invalid keyword argument for %s" %
 
424
                (k, cls_.__name__))
 
425
        setattr(self, k, kwargs[k])
 
426
_declarative_constructor.__name__ = '__init__'
 
427
 
 
428
 
 
429
def _undefer_column_name(key, column):
 
430
    if column.key is None:
 
431
        column.key = key
 
432
    if column.name is None:
 
433
        column.name = key