root/galaxy-central/eggs/SQLAlchemy-0.5.6_dev_r6498-py2.6.egg/sqlalchemy/orm/mapper.py @ 3

リビジョン 3, 80.0 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# mapper.py
2# Copyright (C) 2005, 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com
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
7"""Logic to map Python classes to and from selectables.
8
9Defines the :class:`~sqlalchemy.orm.mapper.Mapper` class, the central configurational
10unit which associates a class with a database table.
11
12This is a semi-private module; the main configurational API of the ORM is
13available in :class:`~sqlalchemy.orm.`.
14
15"""
16
17import types
18import weakref
19import operator
20from itertools import chain
21deque = __import__('collections').deque
22
23from sqlalchemy import sql, util, log, exc as sa_exc
24from sqlalchemy.sql import expression, visitors, operators, util as sqlutil
25from sqlalchemy.orm import attributes, exc, sync
26from sqlalchemy.orm.interfaces import (
27    MapperProperty, EXT_CONTINUE, PropComparator
28    )
29from sqlalchemy.orm.util import (
30     ExtensionCarrier, _INSTRUMENTOR, _class_to_mapper, _state_has_identity,
31     _state_mapper, class_mapper, instance_str, state_str,
32     )
33
34__all__ = (
35    'Mapper',
36    '_mapper_registry',
37    'class_mapper',
38    'object_mapper',
39    )
40
41_mapper_registry = weakref.WeakKeyDictionary()
42_new_mappers = False
43_already_compiling = False
44
45# a list of MapperExtensions that will be installed in all mappers by default
46global_extensions = []
47
48# a constant returned by _get_attr_by_column to indicate
49# this mapper is not handling an attribute for a particular
50# column
51NO_ATTRIBUTE = util.symbol('NO_ATTRIBUTE')
52
53# lock used to synchronize the "mapper compile" step
54_COMPILE_MUTEX = util.threading.RLock()
55
56# initialize these lazily
57ColumnProperty = None
58SynonymProperty = None
59ComparableProperty = None
60RelationProperty = None
61ConcreteInheritedProperty = None
62_expire_state = None
63_state_session = None
64
65class Mapper(object):
66    """Define the correlation of class attributes to database table
67    columns.
68
69    Instances of this class should be constructed via the
70    :func:`~sqlalchemy.orm.mapper` function.
71
72    """
73    def __init__(self,
74                 class_,
75                 local_table,
76                 properties = None,
77                 primary_key = None,
78                 non_primary = False,
79                 inherits = None,
80                 inherit_condition = None,
81                 inherit_foreign_keys = None,
82                 extension = None,
83                 order_by = False,
84                 always_refresh = False,
85                 version_id_col = None,
86                 polymorphic_on=None,
87                 _polymorphic_map=None,
88                 polymorphic_identity=None,
89                 polymorphic_fetch=None,
90                 concrete=False,
91                 select_table=None,
92                 with_polymorphic=None,
93                 allow_null_pks=False,
94                 batch=True,
95                 column_prefix=None,
96                 include_properties=None,
97                 exclude_properties=None,
98                 eager_defaults=False):
99        """Construct a new mapper.
100
101        Mappers are normally constructed via the :func:`~sqlalchemy.orm.mapper`
102        function.  See for details.
103
104        """
105
106        self.class_ = util.assert_arg_type(class_, type, 'class_')
107
108        self.class_manager = None
109
110        self.primary_key_argument = primary_key
111        self.non_primary = non_primary
112
113        if order_by:
114            self.order_by = util.to_list(order_by)
115        else:
116            self.order_by = order_by
117       
118        self.always_refresh = always_refresh
119        self.version_id_col = version_id_col
120        self.concrete = concrete
121        self.single = False
122        self.inherits = inherits
123        self.local_table = local_table
124        self.inherit_condition = inherit_condition
125        self.inherit_foreign_keys = inherit_foreign_keys
126        self.extension = extension
127        self._init_properties = properties or {}
128        self.allow_null_pks = allow_null_pks
129        self.delete_orphans = []
130        self.batch = batch
131        self.eager_defaults = eager_defaults
132        self.column_prefix = column_prefix
133        self.polymorphic_on = polymorphic_on
134        self._dependency_processors = []
135        self._validators = {}
136        self._clause_adapter = None
137        self._requires_row_aliasing = False
138        self._inherits_equated_pairs = None
139
140
141        self.select_table = select_table
142        if select_table:
143            util.warn_deprecated('select_table option is deprecated.  Use with_polymorphic=("*", selectable) '
144                            'instead.')
145
146            if with_polymorphic:
147                raise sa_exc.ArgumentError("select_table can't be used with "
148                            "with_polymorphic (they define conflicting settings)")
149            self.with_polymorphic = ('*', select_table)
150        else:
151            if with_polymorphic == '*':
152                self.with_polymorphic = ('*', None)
153            elif isinstance(with_polymorphic, (tuple, list)):
154                if isinstance(with_polymorphic[0], (basestring, tuple, list)):
155                    self.with_polymorphic = with_polymorphic
156                else:
157                    self.with_polymorphic = (with_polymorphic, None)
158            elif with_polymorphic is not None:
159                raise sa_exc.ArgumentError("Invalid setting for with_polymorphic")
160            else:
161                self.with_polymorphic = None
162
163        if isinstance(self.local_table, expression._SelectBaseMixin):
164            util.warn("mapper %s creating an alias for the given "
165                        "selectable.  References to the original selectable "
166                        "may be misinterpreted by queries, polymorphic_on, etc. "
167                        " Consider passing an explicit selectable.alias() construct instead." % self)
168            self.local_table = self.local_table.alias()
169
170        if self.with_polymorphic and isinstance(self.with_polymorphic[1], expression._SelectBaseMixin):
171            self.with_polymorphic = (self.with_polymorphic[0], self.with_polymorphic[1].alias())
172
173        # our 'polymorphic identity', a string name that when located in a result set row
174        # indicates this Mapper should be used to construct the object instance for that row.
175        self.polymorphic_identity = polymorphic_identity
176
177        if polymorphic_fetch:
178            util.warn_deprecated('polymorphic_fetch option is deprecated.  Unloaded columns '
179                            'load as deferred in all cases; loading can be controlled '
180                            'using the "with_polymorphic" option.')
181
182        # a dictionary of 'polymorphic identity' names, associating those names with
183        # Mappers that will be used to construct object instances upon a select operation.
184        if _polymorphic_map is None:
185            self.polymorphic_map = {}
186        else:
187            self.polymorphic_map = _polymorphic_map
188
189        self.include_properties = include_properties
190        self.exclude_properties = exclude_properties
191
192        self.compiled = False
193
194        self._configure_inheritance()
195        self._configure_extensions()
196        self._configure_class_instrumentation()
197        self._configure_properties()
198        self._configure_pks()
199        global _new_mappers
200        _new_mappers = True
201        self._log("constructed")
202
203    # configurational / mutating methods.  not threadsafe
204    # except for compile().
205   
206    def _configure_inheritance(self):
207        """Configure settings related to inherting and/or inherited mappers being present."""
208
209        # a set of all mappers which inherit from this one.
210        self._inheriting_mappers = set()
211
212        if self.inherits:
213            if isinstance(self.inherits, type):
214                self.inherits = class_mapper(self.inherits, compile=False)
215            if not issubclass(self.class_, self.inherits.class_):
216                raise sa_exc.ArgumentError(
217                        "Class '%s' does not inherit from '%s'" %
218                        (self.class_.__name__, self.inherits.class_.__name__))
219            if self.non_primary != self.inherits.non_primary:
220                np = not self.non_primary and "primary" or "non-primary"
221                raise sa_exc.ArgumentError("Inheritance of %s mapper for class '%s' is "
222                        "only allowed from a %s mapper" % (np, self.class_.__name__, np))
223            # inherit_condition is optional.
224            if self.local_table is None:
225                self.local_table = self.inherits.local_table
226                self.mapped_table = self.inherits.mapped_table
227                self.single = True
228            elif not self.local_table is self.inherits.local_table:
229                if self.concrete:
230                    self.mapped_table = self.local_table
231                    for mapper in self.iterate_to_root():
232                        if mapper.polymorphic_on:
233                            mapper._requires_row_aliasing = True
234                else:
235                    if not self.inherit_condition:
236                        # figure out inherit condition from our table to the immediate table
237                        # of the inherited mapper, not its full table which could pull in other
238                        # stuff we dont want (allows test/inheritance.InheritTest4 to pass)
239                        self.inherit_condition = sqlutil.join_condition(self.inherits.local_table, self.local_table)
240                    self.mapped_table = sql.join(self.inherits.mapped_table, self.local_table, self.inherit_condition)
241
242                    fks = util.to_set(self.inherit_foreign_keys)
243                    self._inherits_equated_pairs = sqlutil.criterion_as_pairs(self.mapped_table.onclause, consider_as_foreign_keys=fks)
244            else:
245                self.mapped_table = self.local_table
246
247            if self.polymorphic_identity is not None and not self.concrete:
248                self._identity_class = self.inherits._identity_class
249            else:
250                self._identity_class = self.class_
251
252            if self.version_id_col is None:
253                self.version_id_col = self.inherits.version_id_col
254
255            for mapper in self.iterate_to_root():
256                util.reset_memoized(mapper, '_equivalent_columns')
257                util.reset_memoized(mapper, '_sorted_tables')
258               
259            if self.order_by is False and not self.concrete and self.inherits.order_by is not False:
260                self.order_by = self.inherits.order_by
261
262            self.polymorphic_map = self.inherits.polymorphic_map
263            self.batch = self.inherits.batch
264            self.inherits._inheriting_mappers.add(self)
265            self.base_mapper = self.inherits.base_mapper
266            self._all_tables = self.inherits._all_tables
267
268            if self.polymorphic_identity is not None:
269                self.polymorphic_map[self.polymorphic_identity] = self
270                if not self.polymorphic_on:
271                    for mapper in self.iterate_to_root():
272                        # try to set up polymorphic on using correesponding_column(); else leave
273                        # as None
274                        if mapper.polymorphic_on:
275                            self.polymorphic_on = self.mapped_table.corresponding_column(mapper.polymorphic_on)
276                            break
277        else:
278            self._all_tables = set()
279            self.base_mapper = self
280            self.mapped_table = self.local_table
281            if self.polymorphic_identity is not None:
282                self.polymorphic_map[self.polymorphic_identity] = self
283            self._identity_class = self.class_
284
285        if self.mapped_table is None:
286            raise sa_exc.ArgumentError("Mapper '%s' does not have a mapped_table specified." % self)
287
288    def _configure_extensions(self):
289        """Go through the global_extensions list as well as the list
290        of ``MapperExtensions`` specified for this ``Mapper`` and
291        creates a linked list of those extensions.
292       
293        """
294        extlist = util.OrderedSet()
295
296        extension = self.extension
297        if extension:
298            for ext_obj in util.to_list(extension):
299                # local MapperExtensions have already instrumented the class
300                extlist.add(ext_obj)
301
302        if self.inherits:
303            for ext in self.inherits.extension:
304                if ext not in extlist:
305                    extlist.add(ext)
306        else:
307            for ext in global_extensions:
308                if isinstance(ext, type):
309                    ext = ext()
310                if ext not in extlist:
311                    extlist.add(ext)
312
313        self.extension = ExtensionCarrier()
314        for ext in extlist:
315            self.extension.append(ext)
316
317    def _configure_class_instrumentation(self):
318        """If this mapper is to be a primary mapper (i.e. the
319        non_primary flag is not set), associate this Mapper with the
320        given class_ and entity name.
321
322        Subsequent calls to ``class_mapper()`` for the class_/entity
323        name combination will return this mapper.  Also decorate the
324        `__init__` method on the mapped class to include optional
325        auto-session attachment logic.
326
327        """
328        manager = attributes.manager_of_class(self.class_)
329
330        if self.non_primary:
331            if not manager or manager.mapper is None:
332                raise sa_exc.InvalidRequestError(
333                    "Class %s has no primary mapper configured.  Configure "
334                    "a primary mapper first before setting up a non primary "
335                    "Mapper.")
336            self.class_manager = manager
337            _mapper_registry[self] = True
338            return
339
340        if manager is not None:
341            assert manager.class_ is self.class_
342            if manager.mapper:
343                raise sa_exc.ArgumentError(
344                    "Class '%s' already has a primary mapper defined. "
345                    "Use non_primary=True to "
346                    "create a non primary Mapper.  clear_mappers() will "
347                    "remove *all* current mappers from all classes." %
348                    self.class_)
349            #else:
350                # a ClassManager may already exist as
351                # ClassManager.instrument_attribute() creates
352                # new managers for each subclass if they don't yet exist.
353               
354        _mapper_registry[self] = True
355
356        self.extension.instrument_class(self, self.class_)
357
358        if manager is None:
359            manager = attributes.register_class(self.class_,
360                deferred_scalar_loader = _load_scalar_attributes
361            )
362
363        self.class_manager = manager
364
365        manager.mapper = self
366
367        # The remaining members can be added by any mapper, e_name None or not.
368        if manager.info.get(_INSTRUMENTOR, False):
369            return
370
371        event_registry = manager.events
372        event_registry.add_listener('on_init', _event_on_init)
373        event_registry.add_listener('on_init_failure', _event_on_init_failure)
374        event_registry.add_listener('on_resurrect', _event_on_resurrect)
375       
376        for key, method in util.iterate_attributes(self.class_):
377            if isinstance(method, types.FunctionType):
378                if hasattr(method, '__sa_reconstructor__'):
379                    event_registry.add_listener('on_load', method)
380                elif hasattr(method, '__sa_validators__'):
381                    for name in method.__sa_validators__:
382                        self._validators[name] = method
383
384        if 'reconstruct_instance' in self.extension:
385            def reconstruct(instance):
386                self.extension.reconstruct_instance(self, instance)
387            event_registry.add_listener('on_load', reconstruct)
388
389        manager.info[_INSTRUMENTOR] = self
390
391    def dispose(self):
392        # Disable any attribute-based compilation.
393        self.compiled = True
394       
395        if hasattr(self, '_compile_failed'):
396            del self._compile_failed
397           
398        if not self.non_primary and self.class_manager.mapper is self:
399            attributes.unregister_class(self.class_)
400
401    def _configure_pks(self):
402
403        self.tables = sqlutil.find_tables(self.mapped_table)
404
405        if not self.tables:
406            raise sa_exc.InvalidRequestError("Could not find any Table objects in mapped table '%s'" % str(self.mapped_table))
407
408        self._pks_by_table = {}
409        self._cols_by_table = {}
410
411        all_cols = util.column_set(chain(*[col.proxy_set for col in self._columntoproperty]))
412        pk_cols = util.column_set(c for c in all_cols if c.primary_key)
413
414        # identify primary key columns which are also mapped by this mapper.
415        tables = set(self.tables + [self.mapped_table])
416        self._all_tables.update(tables)
417        for t in tables:
418            if t.primary_key and pk_cols.issuperset(t.primary_key):
419                # ordering is important since it determines the ordering of mapper.primary_key (and therefore query.get())
420                self._pks_by_table[t] = util.ordered_column_set(t.primary_key).intersection(pk_cols)
421            self._cols_by_table[t] = util.ordered_column_set(t.c).intersection(all_cols)
422
423        # determine cols that aren't expressed within our tables; mark these
424        # as "read only" properties which are refreshed upon INSERT/UPDATE
425        self._readonly_props = set(
426            self._columntoproperty[col]
427            for col in self._columntoproperty
428            if not hasattr(col, 'table') or col.table not in self._cols_by_table)
429
430        # if explicit PK argument sent, add those columns to the primary key mappings
431        if self.primary_key_argument:
432            for k in self.primary_key_argument:
433                if k.table not in self._pks_by_table:
434                    self._pks_by_table[k.table] = util.OrderedSet()
435                self._pks_by_table[k.table].add(k)
436
437        if self.mapped_table not in self._pks_by_table or len(self._pks_by_table[self.mapped_table]) == 0:
438            raise sa_exc.ArgumentError("Mapper %s could not assemble any primary "
439                    "key columns for mapped table '%s'" % (self, self.mapped_table.description))
440
441        if self.inherits and not self.concrete and not self.primary_key_argument:
442            # if inheriting, the "primary key" for this mapper is that of the inheriting (unless concrete or explicit)
443            self.primary_key = self.inherits.primary_key
444        else:
445            # determine primary key from argument or mapped_table pks - reduce to the minimal set of columns
446            if self.primary_key_argument:
447                primary_key = sqlutil.reduce_columns(
448                    [self.mapped_table.corresponding_column(c) for c in self.primary_key_argument],
449                    ignore_nonexistent_tables=True)
450            else:
451                primary_key = sqlutil.reduce_columns(
452                    self._pks_by_table[self.mapped_table], ignore_nonexistent_tables=True)
453
454            if len(primary_key) == 0:
455                raise sa_exc.ArgumentError("Mapper %s could not assemble any primary "
456                    "key columns for mapped table '%s'" % (self, self.mapped_table.description))
457
458            self.primary_key = primary_key
459            self._log("Identified primary key columns: " + str(primary_key))
460
461    def _configure_properties(self):
462       
463        # Column and other ClauseElement objects which are mapped
464        self.columns = self.c = util.OrderedProperties()
465
466        # object attribute names mapped to MapperProperty objects
467        self._props = util.OrderedDict()
468
469        # table columns mapped to lists of MapperProperty objects
470        # using a list allows a single column to be defined as
471        # populating multiple object attributes
472        self._columntoproperty = util.column_dict()
473
474        # load custom properties
475        if self._init_properties:
476            for key, prop in self._init_properties.iteritems():
477                self._configure_property(key, prop, False)
478
479        # pull properties from the inherited mapper if any.
480        if self.inherits:
481            for key, prop in self.inherits._props.iteritems():
482                if key not in self._props and not self._should_exclude(key, key, local=False):
483                    self._adapt_inherited_property(key, prop, False)
484
485        # create properties for each column in the mapped table,
486        # for those columns which don't already map to a property
487        for column in self.mapped_table.columns:
488            if column in self._columntoproperty:
489                continue
490
491            column_key = (self.column_prefix or '') + column.key
492
493            if self._should_exclude(column.key, column_key, local=self.local_table.c.contains_column(column)):
494                continue
495
496            # adjust the "key" used for this column to that
497            # of the inheriting mapper
498            for mapper in self.iterate_to_root():
499                if column in mapper._columntoproperty:
500                    column_key = mapper._columntoproperty[column].key
501
502            self._configure_property(column_key, column, init=False, setparent=True)
503
504        # do a special check for the "discriminiator" column, as it may only be present
505        # in the 'with_polymorphic' selectable but we need it for the base mapper
506        if self.polymorphic_on and self.polymorphic_on not in self._columntoproperty:
507            col = self.mapped_table.corresponding_column(self.polymorphic_on)
508            if not col:
509                dont_instrument = True
510                col = self.polymorphic_on
511            else:
512                dont_instrument = False
513            if self._should_exclude(col.key, col.key, local=False):
514                raise sa_exc.InvalidRequestError("Cannot exclude or override the discriminator column %r" % col.key)
515            self._configure_property(col.key, ColumnProperty(col, _no_instrument=dont_instrument), init=False, setparent=True)
516
517    def _adapt_inherited_property(self, key, prop, init):
518        if not self.concrete:
519            self._configure_property(key, prop, init=False, setparent=False)
520        elif key not in self._props:
521            self._configure_property(key, ConcreteInheritedProperty(), init=init, setparent=True)
522           
523    def _configure_property(self, key, prop, init=True, setparent=True):
524        self._log("_configure_property(%s, %s)" % (key, prop.__class__.__name__))
525
526        if not isinstance(prop, MapperProperty):
527            # we were passed a Column or a list of Columns; generate a ColumnProperty
528            columns = util.to_list(prop)
529            column = columns[0]
530            if not expression.is_column(column):
531                raise sa_exc.ArgumentError("%s=%r is not an instance of MapperProperty or Column" % (key, prop))
532
533            prop = self._props.get(key, None)
534
535            if isinstance(prop, ColumnProperty):
536                # TODO: the "property already exists" case is still not well defined here.
537                # assuming single-column, etc.
538
539                if prop.parent is not self:
540                    # existing ColumnProperty from an inheriting mapper.
541                    # make a copy and append our column to it
542                    prop = prop.copy()
543                prop.columns.append(column)
544                self._log("appending to existing ColumnProperty %s" % (key))
545            elif prop is None or isinstance(prop, ConcreteInheritedProperty):
546                mapped_column = []
547                for c in columns:
548                    mc = self.mapped_table.corresponding_column(c)
549                    if not mc:
550                        mc = self.local_table.corresponding_column(c)
551                        if mc:
552                            # if the column is in the local table but not the mapped table,
553                            # this corresponds to adding a column after the fact to the local table.
554                            # [ticket:1523]
555                            self.mapped_table._reset_exported()
556                        mc = self.mapped_table.corresponding_column(c)
557                        if not mc:
558                            raise sa_exc.ArgumentError("Column '%s' is not represented in mapper's table.  "
559                                "Use the `column_property()` function to force this column "
560                                "to be mapped as a read-only attribute." % c)
561                    mapped_column.append(mc)
562                prop = ColumnProperty(*mapped_column)
563            else:
564                raise sa_exc.ArgumentError("WARNING: column '%s' conflicts with property '%r'.  "
565                    "To resolve this, map the column to the class under a different "
566                    "name in the 'properties' dictionary.  Or, to remove all awareness "
567                    "of the column entirely (including its availability as a foreign key), "
568                    "use the 'include_properties' or 'exclude_properties' mapper arguments "
569                    "to control specifically which table columns get mapped." % (column.key, prop))
570
571        if isinstance(prop, ColumnProperty):
572            col = self.mapped_table.corresponding_column(prop.columns[0])
573           
574            # if the column is not present in the mapped table,
575            # test if a column has been added after the fact to the parent table
576            # (or their parent, etc.)
577            # [ticket:1570]
578            if col is None and self.inherits:
579                path = [self]
580                for m in self.inherits.iterate_to_root():
581                    col = m.local_table.corresponding_column(prop.columns[0])
582                    if col is not None:
583                        for m2 in path:
584                            m2.mapped_table._reset_exported()
585                        col = self.mapped_table.corresponding_column(prop.columns[0])
586                        break
587                    path.append(m)
588               
589            # otherwise, col might not be present! the selectable given
590            # to the mapper need not include "deferred"
591            # columns (included in zblog tests)
592            if col is None:
593                col = prop.columns[0]
594
595                # column is coming in after _readonly_props was initialized; check
596                # for 'readonly'
597                if hasattr(self, '_readonly_props') and \
598                    (not hasattr(col, 'table') or col.table not in self._cols_by_table):
599                        self._readonly_props.add(prop)
600
601            else:
602                # if column is coming in after _cols_by_table was initialized, ensure the col is in the
603                # right set
604                if hasattr(self, '_cols_by_table') and col.table in self._cols_by_table and col not in self._cols_by_table[col.table]:
605                    self._cols_by_table[col.table].add(col)
606           
607            # if this ColumnProperty represents the "polymorphic discriminator"
608            # column, mark it.  We'll need this when rendering columns
609            # in SELECT statements.
610            if not hasattr(prop, '_is_polymorphic_discriminator'):
611                prop._is_polymorphic_discriminator = (col is self.polymorphic_on or prop.columns[0] is self.polymorphic_on)
612               
613            self.columns[key] = col
614            for col in prop.columns:
615                for col in col.proxy_set:
616                    self._columntoproperty[col] = prop
617
618        elif isinstance(prop, (ComparableProperty, SynonymProperty)) and setparent:
619            if prop.descriptor is None:
620                desc = getattr(self.class_, key, None)
621                if self._is_userland_descriptor(desc):
622                    prop.descriptor = desc
623            if getattr(prop, 'map_column', False):
624                if key not in self.mapped_table.c:
625                    raise sa_exc.ArgumentError(
626                        "Can't compile synonym '%s': no column on table '%s' named '%s'"
627                         % (prop.name, self.mapped_table.description, key))
628                self._configure_property(prop.name, ColumnProperty(self.mapped_table.c[key]), init=init, setparent=setparent)
629
630        self._props[key] = prop
631        prop.key = key
632
633        if setparent:
634            prop.set_parent(self)
635
636        if not self.non_primary:
637            prop.instrument_class(self)
638
639        for mapper in self._inheriting_mappers:
640            mapper._adapt_inherited_property(key, prop, init)
641
642        if init:
643            prop.init()
644            prop.post_instrument_class(self)
645
646
647    def compile(self):
648        """Compile this mapper and all other non-compiled mappers.
649
650        This method checks the local compiled status as well as for
651        any new mappers that have been defined, and is safe to call
652        repeatedly.
653
654        """
655        global _new_mappers
656        if self.compiled and not _new_mappers:
657            return self
658
659        _COMPILE_MUTEX.acquire()
660        try:
661            try:
662                global _already_compiling
663                if _already_compiling:
664                    # re-entrance to compile() occurs rarely, when a class-mapped construct is
665                    # used within a ForeignKey, something that is possible
666                    # when using the declarative layer
667                    self._post_configure_properties()
668                    return
669                _already_compiling = True
670                try:
671
672                    # double-check inside mutex
673                    if self.compiled and not _new_mappers:
674                        return self
675
676                    # initialize properties on all mappers
677                    # note that _mapper_registry is unordered, which
678                    # may randomly conceal/reveal issues related to
679                    # the order of mapper compilation
680                    for mapper in list(_mapper_registry):
681                        if getattr(mapper, '_compile_failed', False):
682                            raise sa_exc.InvalidRequestError("One or more mappers failed to compile.  Exception was probably "
683                                    "suppressed within a hasattr() call. "
684                                    "Message was: %s" % mapper._compile_failed)
685                        if not mapper.compiled:
686                            mapper._post_configure_properties()
687
688                    _new_mappers = False
689                    return self
690                finally:
691                    _already_compiling = False
692            except:
693                import sys
694                exc = sys.exc_info()[1]
695                self._compile_failed = exc
696                raise
697        finally:
698            _COMPILE_MUTEX.release()
699
700    def _post_configure_properties(self):
701        """Call the ``init()`` method on all ``MapperProperties``
702        attached to this mapper.
703
704        This is a deferred configuration step which is intended
705        to execute once all mappers have been constructed.
706       
707        """
708
709        self._log("_post_configure_properties() started")
710        l = [(key, prop) for key, prop in self._props.iteritems()]
711        for key, prop in l:
712            self._log("initialize prop " + key)
713           
714            if prop.parent is self and not prop._compile_started:
715                prop.init()
716           
717            if prop._compile_finished:
718                prop.post_instrument_class(self)
719           
720        self._log("_post_configure_properties() complete")
721        self.compiled = True
722           
723    def add_properties(self, dict_of_properties):
724        """Add the given dictionary of properties to this mapper,
725        using `add_property`.
726
727        """
728        for key, value in dict_of_properties.iteritems():
729            self.add_property(key, value)
730
731    def add_property(self, key, prop):
732        """Add an individual MapperProperty to this mapper.
733
734        If the mapper has not been compiled yet, just adds the
735        property to the initial properties dictionary sent to the
736        constructor.  If this Mapper has already been compiled, then
737        the given MapperProperty is compiled immediately.
738
739        """
740        self._init_properties[key] = prop
741        self._configure_property(key, prop, init=self.compiled)
742
743
744    # class formatting / logging.
745   
746    def _log(self, msg):
747        if self._should_log_info:
748            self.logger.info(
749                "(" + self.class_.__name__ +
750                "|" +
751                (self.local_table and self.local_table.description or str(self.local_table)) +
752                (self.non_primary and "|non-primary" or "") + ") " +
753                msg)
754
755    def _log_debug(self, msg):
756        if self._should_log_debug:
757            self.logger.debug(
758                "(" + self.class_.__name__ +
759                "|" +
760                (self.local_table and self.local_table.description or str(self.local_table)) +
761                (self.non_primary and "|non-primary" or "") + ") " +
762                msg)
763
764    def __repr__(self):
765        return '<Mapper at 0x%x; %s>' % (
766            id(self), self.class_.__name__)
767
768    def __str__(self):
769        return "Mapper|" + self.class_.__name__ + "|" + \
770                (self.local_table and self.local_table.description or str(self.local_table)) + \
771                (self.non_primary and "|non-primary" or "")
772
773    # informational / status
774   
775    def _is_orphan(self, state):
776        o = False
777        for mapper in self.iterate_to_root():
778            for (key, cls) in mapper.delete_orphans:
779                if attributes.manager_of_class(cls).has_parent(
780                    state, key, optimistic=_state_has_identity(state)):
781                    return False
782            o = o or bool(mapper.delete_orphans)
783        return o
784
785    def has_property(self, key):
786        return key in self._props
787
788    def get_property(self, key, resolve_synonyms=False, raiseerr=True):
789        """return a MapperProperty associated with the given key."""
790
791        self.compile()
792        return self._get_property(key, resolve_synonyms=resolve_synonyms, raiseerr=raiseerr)
793
794    def _get_property(self, key, resolve_synonyms=False, raiseerr=True):
795        prop = self._props.get(key, None)
796        if resolve_synonyms:
797            while isinstance(prop, SynonymProperty):
798                prop = self._props.get(prop.name, None)
799        if prop is None and raiseerr:
800            raise sa_exc.InvalidRequestError("Mapper '%s' has no property '%s'" % (str(self), key))
801        return prop
802   
803    @property
804    def iterate_properties(self):
805        """return an iterator of all MapperProperty objects."""
806        self.compile()
807        return self._props.itervalues()
808
809    def _mappers_from_spec(self, spec, selectable):
810        """given a with_polymorphic() argument, return the set of mappers it represents.
811
812        Trims the list of mappers to just those represented within the given selectable, if present.
813        This helps some more legacy-ish mappings.
814
815        """
816        if spec == '*':
817            mappers = list(self.polymorphic_iterator())
818        elif spec:
819            mappers = [_class_to_mapper(m) for m in util.to_list(spec)]
820            for m in mappers:
821                if not m.isa(self):
822                    raise sa_exc.InvalidRequestError("%r does not inherit from %r"  % (m, self))
823        else:
824            mappers = []
825
826        if selectable:
827            tables = set(sqlutil.find_tables(selectable, include_aliases=True))
828            mappers = [m for m in mappers if m.local_table in tables]
829
830        return mappers
831
832    def _selectable_from_mappers(self, mappers):
833        """given a list of mappers (assumed to be within this mapper's inheritance hierarchy),
834        construct an outerjoin amongst those mapper's mapped tables.
835
836        """
837       
838        from_obj = self.mapped_table
839        for m in mappers:
840            if m is self:
841                continue
842            if m.concrete:
843                raise sa_exc.InvalidRequestError("'with_polymorphic()' requires 'selectable' argument when concrete-inheriting mappers are used.")
844            elif not m.single:
845                from_obj = from_obj.outerjoin(m.local_table, m.inherit_condition)
846
847        return from_obj
848
849    @property
850    def _single_table_criterion(self):
851        if self.single and \
852            self.inherits and \
853            self.polymorphic_on and \
854            self.polymorphic_identity is not None:
855            return self.polymorphic_on.in_(
856                m.polymorphic_identity
857                for m in self.polymorphic_iterator())
858        else:
859            return None
860       
861   
862    @util.memoized_property
863    def _with_polymorphic_mappers(self):
864        if not self.with_polymorphic:
865            return [self]
866        return self._mappers_from_spec(*self.with_polymorphic)
867
868    @util.memoized_property
869    def _with_polymorphic_selectable(self):
870        if not self.with_polymorphic:
871            return self.mapped_table
872
873        spec, selectable = self.with_polymorphic
874        if selectable:
875            return selectable
876        else:
877            return self._selectable_from_mappers(self._mappers_from_spec(spec, selectable))
878
879    def _with_polymorphic_args(self, spec=None, selectable=False):
880        if self.with_polymorphic:
881            if not spec:
882                spec = self.with_polymorphic[0]
883            if selectable is False:
884                selectable = self.with_polymorphic[1]
885
886        mappers = self._mappers_from_spec(spec, selectable)
887        if selectable:
888            return mappers, selectable
889        else:
890            return mappers, self._selectable_from_mappers(mappers)
891
892    def _iterate_polymorphic_properties(self, mappers=None):
893        """Return an iterator of MapperProperty objects which will render into a SELECT."""
894       
895        if mappers is None:
896            mappers = self._with_polymorphic_mappers
897
898        if not mappers:
899            for c in self.iterate_properties:
900                yield c
901        else:
902            # in the polymorphic case, filter out discriminator columns
903            # from other mappers, as these are sometimes dependent on that
904            # mapper's polymorphic selectable (which we don't want rendered)
905            for c in util.unique_list(
906                chain(*[list(mapper.iterate_properties) for mapper in [self] + mappers])
907            ):
908                if getattr(c, '_is_polymorphic_discriminator', False) and \
909                    (not self.polymorphic_on or c.columns[0] is not self.polymorphic_on):
910                        continue
911                yield c
912   
913    @property
914    def properties(self):
915        raise NotImplementedError("Public collection of MapperProperty objects is "
916                    "provided by the get_property() and iterate_properties accessors.")
917
918    @util.memoized_property
919    def _get_clause(self):
920        """create a "get clause" based on the primary key.  this is used
921        by query.get() and many-to-one lazyloads to load this item
922        by primary key.
923
924        """
925        params = [(primary_key, sql.bindparam(None, type_=primary_key.type)) for primary_key in self.primary_key]
926        return sql.and_(*[k==v for (k, v) in params]), util.column_dict(params)
927
928    @util.memoized_property
929    def _equivalent_columns(self):
930        """Create a map of all *equivalent* columns, based on
931        the determination of column pairs that are equated to
932        one another based on inherit condition.  This is designed
933        to work with the queries that util.polymorphic_union
934        comes up with, which often don't include the columns from
935        the base table directly (including the subclass table columns
936        only).
937
938        The resulting structure is a dictionary of columns mapped
939        to lists of equivalent columns, i.e.
940
941        {
942            tablea.col1:
943                set([tableb.col1, tablec.col1]),
944            tablea.col2:
945                set([tabled.col2])
946        }
947
948        """
949        result = util.column_dict()
950        def visit_binary(binary):
951            if binary.operator == operators.eq:
952                if binary.left in result:
953                    result[binary.left].add(binary.right)
954                else:
955                    result[binary.left] = util.column_set((binary.right,))
956                if binary.right in result:
957                    result[binary.right].add(binary.left)
958                else:
959                    result[binary.right] = util.column_set((binary.left,))
960        for mapper in self.base_mapper.polymorphic_iterator():
961            if mapper.inherit_condition:
962                visitors.traverse(mapper.inherit_condition, {}, {'binary':visit_binary})
963
964        return result
965
966    def _is_userland_descriptor(self, obj):
967        return not isinstance(obj, (MapperProperty, attributes.InstrumentedAttribute)) and hasattr(obj, '__get__')
968
969    def _should_exclude(self, name, assigned_name, local):
970        """determine whether a particular property should be implicitly present on the class.
971
972        This occurs when properties are propagated from an inherited class, or are
973        applied from the columns present in the mapped table.
974
975        """
976
977        # check for descriptors, either local or from
978        # an inherited class
979        if local:
980            if self.class_.__dict__.get(assigned_name, None)\
981                and self._is_userland_descriptor(self.class_.__dict__[assigned_name]):
982                return True
983        else:
984            if getattr(self.class_, assigned_name, None)\
985                and self._is_userland_descriptor(getattr(self.class_, assigned_name)):
986                return True
987
988        if (self.include_properties is not None and
989            name not in self.include_properties):
990            self._log("not including property %s" % (name))
991            return True
992
993        if (self.exclude_properties is not None and
994            name in self.exclude_properties):
995            self._log("excluding property %s" % (name))
996            return True
997
998        return False
999
1000    def common_parent(self, other):
1001        """Return true if the given mapper shares a common inherited parent as this mapper."""
1002
1003        return self.base_mapper is other.base_mapper
1004
1005    def _canload(self, state, allow_subtypes):
1006        s = self.primary_mapper()
1007        if self.polymorphic_on or allow_subtypes:
1008            return _state_mapper(state).isa(s)
1009        else:
1010            return _state_mapper(state) is s
1011
1012    def isa(self, other):
1013        """Return True if the this mapper inherits from the given mapper."""
1014
1015        m = self
1016        while m and m is not other:
1017            m = m.inherits
1018        return bool(m)
1019
1020    def iterate_to_root(self):
1021        m = self
1022        while m:
1023            yield m
1024            m = m.inherits
1025
1026    def polymorphic_iterator(self):
1027        """Iterate through the collection including this mapper and
1028        all descendant mappers.
1029
1030        This includes not just the immediately inheriting mappers but
1031        all their inheriting mappers as well.
1032
1033        To iterate through an entire hierarchy, use
1034        ``mapper.base_mapper.polymorphic_iterator()``.
1035       
1036        """
1037        stack = deque([self])
1038        while stack:
1039            item = stack.popleft()
1040            yield item
1041            stack.extend(item._inheriting_mappers)
1042
1043    def primary_mapper(self):
1044        """Return the primary mapper corresponding to this mapper's class key (class)."""
1045       
1046        return self.class_manager.mapper
1047
1048    def identity_key_from_row(self, row, adapter=None):
1049        """Return an identity-map key for use in storing/retrieving an
1050        item from the identity map.
1051
1052        row
1053          A ``sqlalchemy.engine.base.RowProxy`` instance or a
1054          dictionary corresponding result-set ``ColumnElement``
1055          instances to their values within a row.
1056
1057        """
1058        pk_cols = self.primary_key
1059        if adapter:
1060            pk_cols = [adapter.columns[c] for c in pk_cols]
1061
1062        return (self._identity_class, tuple(row[column] for column in pk_cols))
1063
1064    def identity_key_from_primary_key(self, primary_key):
1065        """Return an identity-map key for use in storing/retrieving an
1066        item from an identity map.
1067
1068        primary_key
1069          A list of values indicating the identifier.
1070
1071        """
1072        return (self._identity_class, tuple(util.to_list(primary_key)))
1073
1074    def identity_key_from_instance(self, instance):
1075        """Return the identity key for the given instance, based on
1076        its primary key attributes.
1077
1078        This value is typically also found on the instance state under the
1079        attribute name `key`.
1080
1081        """
1082        return self.identity_key_from_primary_key(self.primary_key_from_instance(instance))
1083
1084    def _identity_key_from_state(self, state):
1085        return self.identity_key_from_primary_key(self._primary_key_from_state(state))
1086
1087    def primary_key_from_instance(self, instance):
1088        """Return the list of primary key values for the given
1089        instance.
1090
1091        """
1092        state = attributes.instance_state(instance)
1093        return self._primary_key_from_state(state)
1094
1095    def _primary_key_from_state(self, state):
1096        return [self._get_state_attr_by_column(state, column) for column in self.primary_key]
1097
1098    def _get_col_to_prop(self, column):
1099        try:
1100            return self._columntoproperty[column]
1101        except KeyError:
1102            prop = self._props.get(column.key, None)
1103            if prop:
1104                raise exc.UnmappedColumnError("Column '%s.%s' is not available, due to conflicting property '%s':%s" % (column.table.name, column.name, column.key, repr(prop)))
1105            else:
1106                raise exc.UnmappedColumnError("No column %s is configured on mapper %s..." % (column, self))
1107
1108    # TODO: improve names?
1109    def _get_state_attr_by_column(self, state, column):
1110        return self._get_col_to_prop(column).getattr(state, column)
1111
1112    def _set_state_attr_by_column(self, state, column, value):
1113        return self._get_col_to_prop(column).setattr(state, value, column)
1114
1115    def _get_committed_attr_by_column(self, obj, column):
1116        state = attributes.instance_state(obj)
1117        return self._get_committed_state_attr_by_column(state, column)
1118
1119    def _get_committed_state_attr_by_column(self, state, column, passive=False):
1120        return self._get_col_to_prop(column).getcommitted(state, column, passive=passive)
1121
1122    def _optimized_get_statement(self, state, attribute_names):
1123        """assemble a WHERE clause which retrieves a given state by primary key, using a minimized set of tables.
1124       
1125        Applies to a joined-table inheritance mapper where the
1126        requested attribute names are only present on joined tables,
1127        not the base table.  The WHERE clause attempts to include
1128        only those tables to minimize joins.
1129       
1130        """
1131        props = self._props
1132       
1133        tables = set(chain(*
1134                        (sqlutil.find_tables(props[key].columns[0], check_columns=True)
1135                        for key in attribute_names)
1136                    ))
1137       
1138        if self.base_mapper.local_table in tables:
1139            return None
1140
1141        class ColumnsNotAvailable(Exception):
1142            pass
1143
1144        def visit_binary(binary):
1145            leftcol = binary.left
1146            rightcol = binary.right
1147            if leftcol is None or rightcol is None:
1148                return
1149
1150            if leftcol.table not in tables:
1151                leftval = self._get_committed_state_attr_by_column(state, leftcol, passive=True)
1152                if leftval is attributes.PASSIVE_NORESULT:
1153                    raise ColumnsNotAvailable()
1154                binary.left = sql.bindparam(None, leftval, type_=binary.right.type)
1155            elif rightcol.table not in tables:
1156                rightval = self._get_committed_state_attr_by_column(state, rightcol, passive=True)
1157                if rightval is attributes.PASSIVE_NORESULT:
1158                    raise ColumnsNotAvailable()
1159                binary.right = sql.bindparam(None, rightval, type_=binary.right.type)
1160
1161        allconds = []
1162
1163        try:
1164            start = False
1165            for mapper in reversed(list(self.iterate_to_root())):
1166                if mapper.local_table in tables:
1167                    start = True
1168                if start and not mapper.single:
1169                    allconds.append(visitors.cloned_traverse(mapper.inherit_condition, {}, {'binary':visit_binary}))
1170        except ColumnsNotAvailable:
1171            return None
1172
1173        cond = sql.and_(*allconds)
1174
1175        return sql.select([props[key].columns[0] for key in attribute_names], cond, use_labels=True)
1176
1177    def cascade_iterator(self, type_, state, halt_on=None):
1178        """Iterate each element and its mapper in an object graph,
1179        for all relations that meet the given cascade rule.
1180
1181        ``type\_``:
1182          The name of the cascade rule (i.e. save-update, delete,
1183          etc.)
1184
1185        ``state``:
1186          The lead InstanceState.  child items will be processed per
1187          the relations defined for this object's mapper.
1188
1189        the return value are object instances; this provides a strong
1190        reference so that they don't fall out of scope immediately.
1191
1192        """
1193        visited_instances = util.IdentitySet()
1194        visitables = [(self._props.itervalues(), 'property', state)]
1195
1196        while visitables:
1197            iterator, item_type, parent_state = visitables[-1]
1198            try:
1199                if item_type == 'property':
1200                    prop = iterator.next()
1201                    visitables.append((prop.cascade_iterator(type_, parent_state, visited_instances, halt_on), 'mapper', None))
1202                elif item_type == 'mapper':
1203                    instance, instance_mapper, corresponding_state  = iterator.next()
1204                    yield (instance, instance_mapper)
1205                    visitables.append((instance_mapper._props.itervalues(), 'property', corresponding_state))
1206            except StopIteration:
1207                visitables.pop()
1208
1209    # persistence
1210
1211    @util.memoized_property
1212    def _sorted_tables(self):
1213        table_to_mapper = {}
1214        for mapper in self.base_mapper.polymorphic_iterator():
1215            for t in mapper.tables:
1216                table_to_mapper[t] = mapper
1217       
1218        sorted_ = sqlutil.sort_tables(table_to_mapper.iterkeys())
1219        ret = util.OrderedDict()
1220        for t in sorted_:
1221            ret[t] = table_to_mapper[t]
1222        return ret
1223
1224    def _save_obj(self, states, uowtransaction, postupdate=False, post_update_cols=None, single=False):
1225        """Issue ``INSERT`` and/or ``UPDATE`` statements for a list of objects.
1226
1227        This is called within the context of a UOWTransaction during a
1228        flush operation.
1229
1230        `_save_obj` issues SQL statements not just for instances mapped
1231        directly by this mapper, but for instances mapped by all
1232        inheriting mappers as well.  This is to maintain proper insert
1233        ordering among a polymorphic chain of instances. Therefore
1234        _save_obj is typically called only on a *base mapper*, or a
1235        mapper which does not inherit from any other mapper.
1236       
1237        """
1238        if self._should_log_debug:
1239            self._log_debug("_save_obj() start, " + (single and "non-batched" or "batched"))
1240
1241        # if batch=false, call _save_obj separately for each object
1242        if not single and not self.batch:
1243            for state in _sort_states(states):
1244                self._save_obj([state], uowtransaction, postupdate=postupdate, post_update_cols=post_update_cols, single=True)
1245            return
1246
1247        # if session has a connection callable,
1248        # organize individual states with the connection to use for insert/update
1249        tups = []
1250        if 'connection_callable' in uowtransaction.mapper_flush_opts:
1251            connection_callable = uowtransaction.mapper_flush_opts['connection_callable']
1252            for state in _sort_states(states):
1253                m = _state_mapper(state)
1254                tups.append(
1255                    (
1256                        state,
1257                        m,
1258                        connection_callable(self, state.obj()),
1259                        _state_has_identity(state),
1260                        state.key or m._identity_key_from_state(state)
1261                    )
1262                )
1263        else:
1264            connection = uowtransaction.transaction.connection(self)
1265            for state in _sort_states(states):
1266                m = _state_mapper(state)
1267                tups.append(
1268                    (
1269                        state,
1270                        m,
1271                        connection,
1272                        _state_has_identity(state),
1273                        state.key or m._identity_key_from_state(state)
1274                    )
1275                )
1276
1277        if not postupdate:
1278            # call before_XXX extensions
1279            for state, mapper, connection, has_identity, instance_key in tups:
1280                if not has_identity:
1281                    if 'before_insert' in mapper.extension:
1282                        mapper.extension.before_insert(mapper, connection, state.obj())
1283                else:
1284                    if 'before_update' in mapper.extension:
1285                        mapper.extension.before_update(mapper, connection, state.obj())
1286
1287        row_switches = set()
1288        if not postupdate:
1289            for state, mapper, connection, has_identity, instance_key in tups:
1290                # detect if we have a "pending" instance (i.e. has no instance_key attached to it),
1291                # and another instance with the same identity key already exists as persistent.  convert to an
1292                # UPDATE if so.
1293                if not has_identity and instance_key in uowtransaction.session.identity_map:
1294                    instance = uowtransaction.session.identity_map[instance_key]
1295                    existing = attributes.instance_state(instance)
1296                    if not uowtransaction.is_deleted(existing):
1297                        raise exc.FlushError(
1298                            "New instance %s with identity key %s conflicts with persistent instance %s" %
1299                            (state_str(state), instance_key, state_str(existing)))
1300                    if self._should_log_debug:
1301                        self._log_debug(
1302                            "detected row switch for identity %s.  will update %s, remove %s from "
1303                            "transaction" % (instance_key, state_str(state), state_str(existing)))
1304                           
1305                    # remove the "delete" flag from the existing element
1306                    uowtransaction.set_row_switch(existing)
1307                    row_switches.add(state)
1308       
1309        table_to_mapper = self._sorted_tables
1310
1311        for table in table_to_mapper.iterkeys():
1312            insert = []
1313            update = []
1314
1315            for state, mapper, connection, has_identity, instance_key in tups:
1316                if table not in mapper._pks_by_table:
1317                    continue
1318                   
1319                pks = mapper._pks_by_table[table]
1320               
1321                if self._should_log_debug:
1322                    self._log_debug("_save_obj() table '%s' instance %s identity %s" %
1323                                    (table.name, state_str(state), str(instance_key)))
1324
1325                isinsert = not has_identity and not postupdate and state not in row_switches
1326               
1327                params = {}
1328                value_params = {}
1329                hasdata = False
1330
1331                if isinsert:
1332                    for col in mapper._cols_by_table[table]:
1333                        if col is mapper.version_id_col:
1334                            params[col.key] = 1
1335                        elif mapper.polymorphic_on and mapper.polymorphic_on.shares_lineage(col):
1336                            if self._should_log_debug:
1337                                self._log_debug(
1338                                    "Using polymorphic identity '%s' for insert column '%s'" %
1339                                    (mapper.polymorphic_identity, col.key))
1340                            value = mapper.polymorphic_identity
1341                            if ((col.default is None and
1342                                 col.server_default is None) or
1343                                value is not None):
1344                                params[col.key] = value
1345                        elif col in pks:
1346                            value = mapper._get_state_attr_by_column(state, col)
1347                            if value is not None:
1348                                params[col.key] = value
1349                        else:
1350                            value = mapper._get_state_attr_by_column(state, col)
1351                            if ((col.default is None and
1352                                 col.server_default is None) or
1353                                value is not None):
1354                                if isinstance(value, sql.ClauseElement):
1355                                    value_params[col] = value
1356                                else:
1357                                    params[col.key] = value
1358                    insert.append((state, params, mapper, connection, value_params))
1359                else:
1360                    for col in mapper._cols_by_table[table]:
1361                        if col is mapper.version_id_col:
1362                            params[col._label] = mapper._get_state_attr_by_column(state, col)
1363                            params[col.key] = params[col._label] + 1
1364                            for prop in mapper._columntoproperty.itervalues():
1365                                history = attributes.get_state_history(state, prop.key, passive=True)
1366                                if history.added:
1367                                    hasdata = True
1368                        elif mapper.polymorphic_on and mapper.polymorphic_on.shares_lineage(col) and col not in pks:
1369                            pass
1370                        else:
1371                            if post_update_cols is not None and col not in post_update_cols:
1372                                if col in pks:
1373                                    params[col._label] = mapper._get_state_attr_by_column(state, col)
1374                                continue
1375
1376                            prop = mapper._columntoproperty[col]
1377                            history = attributes.get_state_history(state, prop.key, passive=True)
1378                            if history.added:
1379                                if isinstance(history.added[0], sql.ClauseElement):
1380                                    value_params[col] = history.added[0]
1381                                else:
1382                                    params[col.key] = prop.get_col_value(col, history.added[0])
1383                                if col in pks:
1384                                    if history.deleted:
1385                                        params[col._label] = prop.get_col_value(col, history.deleted[0])
1386                                        hasdata = True
1387                                    else:
1388                                        # row switch logic can reach us here
1389                                        # remove the pk from the update params so the update doesn't
1390                                        # attempt to include the pk in the update statement
1391                                        del params[col.key]
1392                                        params[col._label] = prop.get_col_value(col, history.added[0])
1393                                else:
1394                                    hasdata = True
1395                            elif col in pks:
1396                                params[col._label] = mapper._get_state_attr_by_column(state, col)
1397                    if hasdata:
1398                        update.append((state, params, mapper, connection, value_params))
1399
1400            if update:
1401                mapper = table_to_mapper[table]
1402                clause = sql.and_()
1403
1404                for col in mapper._pks_by_table[table]:
1405                    clause.clauses.append(col == sql.bindparam(col._label, type_=col.type))
1406
1407                if mapper.version_id_col and table.c.contains_column(mapper.version_id_col):
1408                    clause.clauses.append(mapper.version_id_col == sql.bindparam(mapper.version_id_col._label, type_=col.type))
1409
1410                statement = table.update(clause)
1411                rows = 0
1412                for state, params, mapper, connection, value_params in update:
1413                    c = connection.execute(statement.values(value_params), params)
1414                    mapper._postfetch(uowtransaction, connection, table, state, c, c.last_updated_params(), value_params)
1415
1416                    rows += c.rowcount
1417
1418                if c.supports_sane_rowcount() and rows != len(update):
1419                    raise exc.ConcurrentModificationError("Updated rowcount %d does not match number of objects updated %d" % (rows, len(update)))
1420
1421            if insert:
1422                statement = table.insert()
1423                for state, params, mapper, connection, value_params in insert:
1424                    c = connection.execute(statement.values(value_params), params)
1425                    primary_key = c.last_inserted_ids()
1426
1427                    if primary_key is not None:
1428                        # set primary key attributes
1429                        for i, col in enumerate(mapper._pks_by_table[table]):
1430                            if mapper._get_state_attr_by_column(state, col) is None and len(primary_key) > i:
1431                                mapper._set_state_attr_by_column(state, col, primary_key[i])
1432                    mapper._postfetch(uowtransaction, connection, table, state, c, c.last_inserted_params(), value_params)
1433
1434                    # synchronize newly inserted ids from one table to the next
1435                    # TODO: this performs some unnecessary attribute transfers
1436                    # from an attribute to itself, since the attribute is often mapped
1437                    # to multiple, equivalent columns.  it also may fire off more
1438                    # than needed overall.
1439                    for m in mapper.iterate_to_root():
1440                        if m._inherits_equated_pairs:
1441                            sync.populate(state, m, state, m, m._inherits_equated_pairs)
1442
1443        if not postupdate:
1444            for state, mapper, connection, has_identity, instance_key in tups:
1445
1446                # expire readonly attributes
1447                readonly = state.unmodified.intersection(
1448                    p.key for p in mapper._readonly_props
1449                )
1450
1451                if readonly:
1452                    _expire_state(state, readonly)
1453
1454                # if specified, eagerly refresh whatever has
1455                # been expired.
1456                if self.eager_defaults and state.unloaded:
1457                    state.key = self._identity_key_from_state(state)
1458                    uowtransaction.session.query(self)._get(
1459                        state.key, refresh_state=state,
1460                        only_load_props=state.unloaded)
1461
1462                # call after_XXX extensions
1463                if not has_identity:
1464                    if 'after_insert' in mapper.extension:
1465                        mapper.extension.after_insert(mapper, connection, state.obj())
1466                else:
1467                    if 'after_update' in mapper.extension:
1468                        mapper.extension.after_update(mapper, connection, state.obj())
1469
1470    def _postfetch(self, uowtransaction, connection, table, state, resultproxy, params, value_params):
1471        """Expire attributes in need of newly persisted database state."""
1472
1473        postfetch_cols = resultproxy.postfetch_cols()
1474        generated_cols = list(resultproxy.prefetch_cols())
1475
1476        if self.polymorphic_on:
1477            po = table.corresponding_column(self.polymorphic_on)
1478            if po:
1479                generated_cols.append(po)
1480
1481        if self.version_id_col:
1482            generated_cols.append(self.version_id_col)
1483
1484        for c in generated_cols:
1485            if c.key in params and c in self._columntoproperty:
1486                self._set_state_attr_by_column(state, c, params[c.key])
1487
1488        deferred_props = [prop.key for prop in [self._columntoproperty[c] for c in postfetch_cols]]
1489
1490        if deferred_props:
1491            _expire_state(state, deferred_props)
1492
1493    def _delete_obj(self, states, uowtransaction):
1494        """Issue ``DELETE`` statements for a list of objects.
1495
1496        This is called within the context of a UOWTransaction during a
1497        flush operation.
1498
1499        """
1500        if self._should_log_debug:
1501            self._log_debug("_delete_obj() start")
1502
1503        if 'connection_callable' in uowtransaction.mapper_flush_opts:
1504            connection_callable = uowtransaction.mapper_flush_opts['connection_callable']
1505            tups = [(state, _state_mapper(state), connection_callable(self, state.obj())) for state in _sort_states(states)]
1506        else:
1507            connection = uowtransaction.transaction.connection(self)
1508            tups = [(state, _state_mapper(state), connection) for state in _sort_states(states)]
1509
1510        for state, mapper, connection in tups:
1511            if 'before_delete' in mapper.extension:
1512                mapper.extension.before_delete(mapper, connection, state.obj())
1513
1514        table_to_mapper = self._sorted_tables
1515
1516        for table in reversed(table_to_mapper.keys()):
1517            delete = {}
1518            for state, mapper, connection in tups:
1519                if table not in mapper._pks_by_table:
1520                    continue
1521
1522                params = {}
1523                if not _state_has_identity(state):
1524                    continue
1525                else:
1526                    delete.setdefault(connection, []).append(params)
1527                for col in mapper._pks_by_table[table]:
1528                    params[col.key] = mapper._get_state_attr_by_column(state, col)
1529                if mapper.version_id_col and table.c.contains_column(mapper.version_id_col):
1530                    params[mapper.version_id_col.key] = mapper._get_state_attr_by_column(state, mapper.version_id_col)
1531
1532            for connection, del_objects in delete.iteritems():
1533                mapper = table_to_mapper[table]
1534                clause = sql.and_()
1535                for col in mapper._pks_by_table[table]:
1536                    clause.clauses.append(col == sql.bindparam(col.key, type_=col.type))
1537                if mapper.version_id_col and table.c.contains_column(mapper.version_id_col):
1538                    clause.clauses.append(
1539                        mapper.version_id_col ==
1540                        sql.bindparam(mapper.version_id_col.key, type_=mapper.version_id_col.type))
1541                statement = table.delete(clause)
1542                c = connection.execute(statement, del_objects)
1543                if c.supports_sane_multi_rowcount() and c.rowcount != len(del_objects):
1544                    raise exc.ConcurrentModificationError("Deleted rowcount %d does not match "
1545                            "number of objects deleted %d" % (c.rowcount, len(del_objects)))
1546
1547        for state, mapper, connection in tups:
1548            if 'after_delete' in mapper.extension:
1549                mapper.extension.after_delete(mapper, connection, state.obj())
1550
1551    def _register_dependencies(self, uowcommit):
1552        """Register ``DependencyProcessor`` instances with a
1553        ``unitofwork.UOWTransaction``.
1554
1555        This call `register_dependencies` on all attached
1556        ``MapperProperty`` instances.
1557       
1558        """
1559        for dep in self._props.values() + self._dependency_processors:
1560            dep.register_dependencies(uowcommit)
1561
1562    def _register_processors(self, uowcommit):
1563        for dep in self._props.values() + self._dependency_processors:
1564            dep.register_processors(uowcommit)
1565
1566    # result set conversion
1567
1568    def _instance_processor(self, context, path, adapter, polymorphic_from=None, extension=None, only_load_props=None, refresh_state=None, polymorphic_discriminator=None):
1569        """Produce a mapper level row processor callable which processes rows into mapped instances."""
1570       
1571        pk_cols = self.primary_key
1572
1573        if polymorphic_from or refresh_state:
1574            polymorphic_on = None
1575        else:
1576            polymorphic_on = polymorphic_discriminator or self.polymorphic_on
1577            polymorphic_instances = util.PopulateDict(self._configure_subclass_mapper(context, path, adapter))
1578
1579        version_id_col = self.version_id_col
1580
1581        if adapter:
1582            pk_cols = [adapter.columns[c] for c in pk_cols]
1583            if polymorphic_on:
1584                polymorphic_on = adapter.columns[polymorphic_on]
1585            if version_id_col:
1586                version_id_col = adapter.columns[version_id_col]
1587
1588        identity_class = self._identity_class
1589        def identity_key(row):
1590            return (identity_class, tuple(row[column] for column in pk_cols))
1591
1592        new_populators = []
1593        existing_populators = []
1594
1595        def populate_state(state, dict_, row, isnew, only_load_props, **flags):
1596            if isnew:
1597                if context.propagate_options:
1598                    state.load_options = context.propagate_options
1599                if state.load_options:
1600                    state.load_path = context.query._current_path + path
1601
1602            if not new_populators:
1603                new_populators[:], existing_populators[:] = self._populators(context, path, row, adapter)
1604
1605            if isnew:
1606                populators = new_populators
1607            else:
1608                populators = existing_populators
1609
1610            if only_load_props:
1611                populators = [p for p in populators if p[0] in only_load_props]
1612
1613            for key, populator in populators:
1614                populator(state, dict_, row, isnew=isnew, **flags)
1615
1616        session_identity_map = context.session.identity_map
1617
1618        if not extension:
1619            extension = self.extension
1620
1621        translate_row = extension.get('translate_row', None)
1622        create_instance = extension.get('create_instance', None)
1623        populate_instance = extension.get('populate_instance', None)
1624        append_result = extension.get('append_result', None)
1625        populate_existing = context.populate_existing or self.always_refresh
1626
1627        def _instance(row, result):
1628            if translate_row:
1629                ret = translate_row(self, context, row)
1630                if ret is not EXT_CONTINUE:
1631                    row = ret
1632
1633            if polymorphic_on:
1634                discriminator = row[polymorphic_on]
1635                if discriminator is not None:
1636                    _instance = polymorphic_instances[discriminator]
1637                    if _instance:
1638                        return _instance(row, result)
1639
1640            # determine identity key
1641            if refresh_state:
1642                identitykey = refresh_state.key
1643                if identitykey is None:
1644                    # super-rare condition; a refresh is being called
1645                    # on a non-instance-key instance; this is meant to only
1646                    # occur within a flush()
1647                    identitykey = self._identity_key_from_state(refresh_state)
1648            else:
1649                identitykey = identity_key(row)
1650
1651            if identitykey in session_identity_map:
1652                instance = session_identity_map[identitykey]
1653                state = attributes.instance_state(instance)
1654                dict_ = attributes.instance_dict(instance)
1655
1656                if self._should_log_debug:
1657                    self._log_debug("_instance(): using existing instance %s identity %s" %
1658                                    (instance_str(instance), identitykey))
1659
1660                isnew = state.runid != context.runid
1661                currentload = not isnew
1662                loaded_instance = False
1663
1664                if not currentload and version_id_col and context.version_check and \
1665                        self._get_state_attr_by_column(state, self.version_id_col) != row[version_id_col]:
1666                    raise exc.ConcurrentModificationError(
1667                            "Instance '%s' version of %s does not match %s"
1668                            % (state_str(state), self._get_state_attr_by_column(state, self.version_id_col), row[version_id_col]))
1669            elif refresh_state:
1670                # out of band refresh_state detected (i.e. its not in the session.identity_map)
1671                # honor it anyway.  this can happen if a _get() occurs within save_obj(), such as
1672                # when eager_defaults is True.
1673                state = refresh_state
1674                instance = state.obj()
1675                dict_ = attributes.instance_dict(instance)
1676                isnew = state.runid != context.runid
1677                currentload = True
1678                loaded_instance = False
1679            else:
1680                if self._should_log_debug:
1681                    self._log_debug("_instance(): identity key %s not in session" % (identitykey,))
1682
1683                if self.allow_null_pks:
1684                    for x in identitykey[1]:
1685                        if x is not None:
1686                            break
1687                    else:
1688                        return None
1689                else:
1690                    if None in identitykey[1]:
1691                        return None
1692                isnew = True
1693                currentload = True
1694                loaded_instance = True
1695
1696                if create_instance:
1697                    instance = create_instance(self, context, row, self.class_)
1698                    if instance is EXT_CONTINUE:
1699                        instance = self.class_manager.new_instance()
1700                    else:
1701                        manager = attributes.manager_of_class(instance.__class__)
1702                        # TODO: if manager is None, raise a friendly error about
1703                        # returning instances of unmapped types
1704                        manager.setup_instance(instance)
1705                else:
1706                    instance = self.class_manager.new_instance()
1707
1708                if self._should_log_debug:
1709                    self._log_debug("_instance(): created new instance %s identity %s" %
1710                                    (instance_str(instance), identitykey))
1711
1712                dict_ = attributes.instance_dict(instance)
1713                state = attributes.instance_state(instance)
1714                state.key = identitykey
1715
1716                # manually adding instance to session.  for a complete add,
1717                # session._finalize_loaded() must be called.
1718                state.session_id = context.session.hash_key
1719                session_identity_map.add(state)
1720
1721            if currentload or populate_existing:
1722                if isnew:
1723                    state.runid = context.runid
1724                    context.progress[state] = dict_
1725
1726                if not populate_instance or \
1727                        populate_instance(self, context, row, instance,
1728                            only_load_props=only_load_props, instancekey=identitykey, isnew=isnew) is EXT_CONTINUE:
1729                    populate_state(state, dict_, row, isnew, only_load_props)
1730
1731            else:
1732                # populate attributes on non-loading instances which have been expired
1733                # TODO: apply eager loads to un-lazy loaded collections ?
1734                if state in context.partials or state.unloaded:
1735
1736                    if state in context.partials:
1737                        isnew = False
1738                        (d_, attrs) = context.partials[state]
1739                    else:
1740                        isnew = True
1741                        attrs = state.unloaded
1742                        context.partials[state] = (dict_, attrs)  #<-- allow query.instances to commit the subset of attrs
1743
1744                    if not populate_instance or \
1745                            populate_instance(self, context, row, instance,
1746                                only_load_props=attrs, instancekey=identitykey, isnew=isnew) is EXT_CONTINUE:
1747                        populate_state(state, dict_, row, isnew, attrs, instancekey=identitykey)
1748
1749            if loaded_instance:
1750                state._run_on_load(instance)
1751
1752            if result is not None and \
1753                        (not append_result or
1754                            append_result(self, context, row, instance, result, instancekey=identitykey, isnew=isnew) is EXT_CONTINUE):
1755                result.append(instance)
1756
1757            return instance
1758        return _instance
1759
1760    def _populators(self, context, path, row, adapter):
1761        """Produce a collection of attribute level row processor callables."""
1762       
1763        new_populators, existing_populators = [], []
1764        for prop in self._props.itervalues():
1765            newpop, existingpop = prop.create_row_processor(context, path, self, row, adapter)
1766            if newpop:
1767                new_populators.append((prop.key, newpop))
1768            if existingpop:
1769                existing_populators.append((prop.key, existingpop))
1770        return new_populators, existing_populators
1771
1772    def _configure_subclass_mapper(self, context, path, adapter):
1773        """Produce a mapper level row processor callable factory for mappers inheriting this one."""
1774       
1775        def configure_subclass_mapper(discriminator):
1776            try:
1777                mapper = self.polymorphic_map[discriminator]
1778            except KeyError:
1779                raise AssertionError("No such polymorphic_identity %r is defined" % discriminator)
1780            if mapper is self:
1781                return None
1782            return mapper._instance_processor(context, path, adapter, polymorphic_from=self)
1783        return configure_subclass_mapper
1784
1785log.class_logger(Mapper)
1786
1787
1788def reconstructor(fn):
1789    """Decorate a method as the 'reconstructor' hook.
1790
1791    Designates a method as the "reconstructor", an ``__init__``-like
1792    method that will be called by the ORM after the instance has been
1793    loaded from the database or otherwise reconstituted.
1794
1795    The reconstructor will be invoked with no arguments.  Scalar
1796    (non-collection) database-mapped attributes of the instance will
1797    be available for use within the function.  Eagerly-loaded
1798    collections are generally not yet available and will usually only
1799    contain the first element.  ORM state changes made to objects at
1800    this stage will not be recorded for the next flush() operation, so
1801    the activity within a reconstructor should be conservative.
1802
1803    """
1804    fn.__sa_reconstructor__ = True
1805    return fn
1806
1807def validates(*names):
1808    """Decorate a method as a 'validator' for one or more named properties.
1809
1810    Designates a method as a validator, a method which receives the
1811    name of the attribute as well as a value to be assigned, or in the
1812    case of a collection to be added to the collection.  The function
1813    can then raise validation exceptions to halt the process from continuing,
1814    or can modify or replace the value before proceeding.   The function
1815    should otherwise return the given value.
1816
1817    """
1818    def wrap(fn):
1819        fn.__sa_validators__ = names
1820        return fn
1821    return wrap
1822
1823def _event_on_init(state, instance, args, kwargs):
1824    """Trigger mapper compilation and run init_instance hooks."""
1825
1826    instrumenting_mapper = state.manager.info[_INSTRUMENTOR]
1827    # compile() always compiles all mappers
1828    instrumenting_mapper.compile()
1829    if 'init_instance' in instrumenting_mapper.extension:
1830        instrumenting_mapper.extension.init_instance(
1831            instrumenting_mapper, instrumenting_mapper.class_,
1832            state.manager.events.original_init,
1833            instance, args, kwargs)
1834
1835def _event_on_init_failure(state, instance, args, kwargs):
1836    """Run init_failed hooks."""
1837
1838    instrumenting_mapper = state.manager.info[_INSTRUMENTOR]
1839    if 'init_failed' in instrumenting_mapper.extension:
1840        util.warn_exception(
1841            instrumenting_mapper.extension.init_failed,
1842            instrumenting_mapper, instrumenting_mapper.class_,
1843            state.manager.events.original_init, instance, args, kwargs)
1844
1845def _event_on_resurrect(state, instance):
1846    # re-populate the primary key elements
1847    # of the dict based on the mapping.
1848    instrumenting_mapper = state.manager.info[_INSTRUMENTOR]
1849    for col, val in zip(instrumenting_mapper.primary_key, state.key[1]):
1850        instrumenting_mapper._set_state_attr_by_column(state, col, val)
1851   
1852   
1853def _sort_states(states):
1854    return sorted(states, key=operator.attrgetter('sort_key'))
1855
1856def _load_scalar_attributes(state, attribute_names):
1857    """initiate a column-based attribute refresh operation."""
1858   
1859    mapper = _state_mapper(state)
1860    session = _state_session(state)
1861    if not session:
1862        raise sa_exc.UnboundExecutionError("Instance %s is not bound to a Session; "
1863                    "attribute refresh operation cannot proceed" % (state_str(state)))
1864
1865    has_key = _state_has_identity(state)
1866
1867    result = False
1868    if mapper.inherits and not mapper.concrete:
1869        statement = mapper._optimized_get_statement(state, attribute_names)
1870        if statement:
1871            result = session.query(mapper).from_statement(statement)._get(None, only_load_props=attribute_names, refresh_state=state)
1872
1873    if result is False:
1874        if has_key:
1875            identity_key = state.key
1876        else:
1877            identity_key = mapper._identity_key_from_state(state)
1878        result = session.query(mapper)._get(identity_key, refresh_state=state, only_load_props=attribute_names)
1879
1880    # if instance is pending, a refresh operation may not complete (even if PK attributes are assigned)
1881    if has_key and result is None:
1882        raise exc.ObjectDeletedError("Instance '%s' has been deleted." % state_str(state))
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。