diff options
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 240 |
1 files changed, 131 insertions, 109 deletions
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 6c7907d49..76d6b1165 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -284,11 +284,11 @@ class Mapper(object): which comprise the 'primary key' of the mapped table, from the perspective of this :class:`.Mapper`. - This list is against the selectable in :attr:`~.Mapper.mapped_table`. In the - case of inheriting mappers, some columns may be managed by a superclass - mapper. For example, in the case of a :class:`.Join`, the primary - key is determined by all of the primary key columns across all tables - referenced by the :class:`.Join`. + This list is against the selectable in :attr:`~.Mapper.mapped_table`. In + the case of inheriting mappers, some columns may be managed by a + superclass mapper. For example, in the case of a :class:`.Join`, the + primary key is determined by all of the primary key columns across all + tables referenced by the :class:`.Join`. The list is also not necessarily the same as the primary key column collection associated with the underlying tables; the :class:`.Mapper` @@ -351,8 +351,8 @@ class Mapper(object): """ polymorphic_map = None - """A mapping of "polymorphic identity" identifiers mapped to :class:`.Mapper` - instances, within an inheritance scenario. + """A mapping of "polymorphic identity" identifiers mapped to + :class:`.Mapper` instances, within an inheritance scenario. The identifiers can be of any type which is comparable to the type of column represented by :attr:`~.Mapper.polymorphic_on`. @@ -367,11 +367,12 @@ class Mapper(object): """ polymorphic_identity = None - """Represent an identifier which is matched against the :attr:`~.Mapper.polymorphic_on` - column during result row loading. + """Represent an identifier which is matched against the + :attr:`~.Mapper.polymorphic_on` column during result row loading. Used only with inheritance, this object can be of any type which is - comparable to the type of column represented by :attr:`~.Mapper.polymorphic_on`. + comparable to the type of column represented by + :attr:`~.Mapper.polymorphic_on`. This is a *read only* attribute determined during mapper construction. Behavior is undefined if directly modified. @@ -555,9 +556,9 @@ class Mapper(object): self._expire_memoizations() def _set_concrete_base(self, mapper): - """Set the given :class:`.Mapper` as the 'inherits' for this :class:`.Mapper`, - assuming this :class:`.Mapper` is concrete and does not already have - an inherits.""" + """Set the given :class:`.Mapper` as the 'inherits' for this + :class:`.Mapper`, assuming this :class:`.Mapper` is concrete + and does not already have an inherits.""" assert self.concrete assert not self.inherits @@ -733,7 +734,7 @@ class Mapper(object): if t.primary_key and pk_cols.issuperset(t.primary_key): # ordering is important since it determines the ordering of # mapper.primary_key (and therefore query.get()) - self._pks_by_table[t] =\ + self._pks_by_table[t] = \ util.ordered_column_set(t.primary_key).\ intersection(pk_cols) self._cols_by_table[t] = \ @@ -820,7 +821,8 @@ class Mapper(object): if self.inherits: for key, prop in self.inherits._props.iteritems(): if key not in self._props and \ - not self._should_exclude(key, key, local=False, column=None): + not self._should_exclude(key, key, local=False, + column=None): self._adapt_inherited_property(key, prop, False) # create properties for each column in the mapped table, @@ -886,7 +888,8 @@ class Mapper(object): elif isinstance(self.polymorphic_on, MapperProperty): # polymorphic_on is directly a MapperProperty, # ensure it's a ColumnProperty - if not isinstance(self.polymorphic_on, properties.ColumnProperty): + if not isinstance(self.polymorphic_on, + properties.ColumnProperty): raise sa_exc.ArgumentError( "Only direct column-mapped " "property or SQL expression " @@ -903,27 +906,31 @@ class Mapper(object): "can be passed for polymorphic_on" ) else: - # polymorphic_on is a Column or SQL expression and doesn't - # appear to be mapped. - # this means it can be 1. only present in the with_polymorphic - # selectable or 2. a totally standalone SQL expression which we'd + # polymorphic_on is a Column or SQL expression and + # doesn't appear to be mapped. this means it can be 1. + # only present in the with_polymorphic selectable or + # 2. a totally standalone SQL expression which we'd # hope is compatible with this mapper's mapped_table - col = self.mapped_table.corresponding_column(self.polymorphic_on) + col = self.mapped_table.corresponding_column( + self.polymorphic_on) if col is None: - # polymorphic_on doesn't derive from any column/expression - # isn't present in the mapped table. - # we will make a "hidden" ColumnProperty for it. - # Just check that if it's directly a schema.Column and we - # have with_polymorphic, it's likely a user error if the - # schema.Column isn't represented somehow in either mapped_table or - # with_polymorphic. Otherwise as of 0.7.4 we just go with it - # and assume the user wants it that way (i.e. a CASE statement) + # polymorphic_on doesn't derive from any + # column/expression isn't present in the mapped + # table. we will make a "hidden" ColumnProperty + # for it. Just check that if it's directly a + # schema.Column and we have with_polymorphic, it's + # likely a user error if the schema.Column isn't + # represented somehow in either mapped_table or + # with_polymorphic. Otherwise as of 0.7.4 we + # just go with it and assume the user wants it + # that way (i.e. a CASE statement) setter = False instrument = False col = self.polymorphic_on if isinstance(col, schema.Column) and ( self.with_polymorphic is None or \ - self.with_polymorphic[1].corresponding_column(col) is None + self.with_polymorphic[1].\ + corresponding_column(col) is None ): raise sa_exc.InvalidRequestError( "Could not map polymorphic_on column " @@ -936,7 +943,8 @@ class Mapper(object): # and is probably mapped, but polymorphic_on itself # is not. This happens when # the polymorphic_on is only directly present in the - # with_polymorphic selectable, as when use polymorphic_union. + # with_polymorphic selectable, as when use + # polymorphic_union. # we'll make a separate ColumnProperty for it. instrument = True @@ -944,15 +952,18 @@ class Mapper(object): if key: if self._should_exclude(col.key, col.key, False, col): raise sa_exc.InvalidRequestError( - "Cannot exclude or override the discriminator column %r" % + "Cannot exclude or override the " + "discriminator column %r" % col.key) else: - self.polymorphic_on = col = col.label("_sa_polymorphic_on") + self.polymorphic_on = col = \ + col.label("_sa_polymorphic_on") key = col.key self._configure_property( key, - properties.ColumnProperty(col, _instrument=instrument), + properties.ColumnProperty(col, + _instrument=instrument), init=init, setparent=True) polymorphic_key = key else: @@ -975,7 +986,8 @@ class Mapper(object): # directly; it ensures the polymorphic_identity of the # instance's mapper is used so is portable to subclasses. if self.polymorphic_on is not None: - self._set_polymorphic_identity = mapper._set_polymorphic_identity + self._set_polymorphic_identity = \ + mapper._set_polymorphic_identity else: self._set_polymorphic_identity = None return @@ -1005,67 +1017,7 @@ class Mapper(object): self._log("_configure_property(%s, %s)", key, prop.__class__.__name__) if not isinstance(prop, MapperProperty): - # we were passed a Column or a list of Columns; - # generate a properties.ColumnProperty - columns = util.to_list(prop) - column = columns[0] - if not expression.is_column(column): - raise sa_exc.ArgumentError( - "%s=%r is not an instance of MapperProperty or Column" - % (key, prop)) - - prop = self._props.get(key, None) - - if isinstance(prop, properties.ColumnProperty): - if prop.parent is self: - raise sa_exc.InvalidRequestError( - "Implicitly combining column %s with column " - "%s under attribute '%s'. Please configure one " - "or more attributes for these same-named columns " - "explicitly." - % (prop.columns[-1], column, key)) - - # existing properties.ColumnProperty from an inheriting - # mapper. make a copy and append our column to it - prop = prop.copy() - prop.columns.insert(0, column) - self._log("inserting column to existing list " - "in properties.ColumnProperty %s" % (key)) - - elif prop is None or isinstance(prop, properties.ConcreteInheritedProperty): - mapped_column = [] - for c in columns: - mc = self.mapped_table.corresponding_column(c) - if mc is None: - mc = self.local_table.corresponding_column(c) - if mc is not None: - # if the column is in the local table but not the - # mapped table, this corresponds to adding a - # column after the fact to the local table. - # [ticket:1523] - self.mapped_table._reset_exported() - mc = self.mapped_table.corresponding_column(c) - if mc is None: - raise sa_exc.ArgumentError( - "When configuring property '%s' on %s, " - "column '%s' is not represented in the mapper's " - "table. Use the `column_property()` function to " - "force this column to be mapped as a read-only " - "attribute." % (key, self, c)) - mapped_column.append(mc) - prop = properties.ColumnProperty(*mapped_column) - else: - raise sa_exc.ArgumentError( - "WARNING: when configuring property '%s' on %s, " - "column '%s' conflicts with property '%r'. " - "To resolve this, map the column to the class under a " - "different name in the 'properties' dictionary. Or, " - "to remove all awareness of the column entirely " - "(including its availability as a foreign key), " - "use the 'include_properties' or 'exclude_properties' " - "mapper arguments to control specifically which table " - "columns get mapped." % - (key, self, column.key, prop)) + prop = self._property_from_column(key, prop) if isinstance(prop, properties.ColumnProperty): col = self.mapped_table.corresponding_column(prop.columns[0]) @@ -1147,6 +1099,73 @@ class Mapper(object): if self.configured: self._expire_memoizations() + def _property_from_column(self, key, prop): + """generate/update a :class:`.ColumnProprerty` given a + :class:`.Column` object. """ + + # we were passed a Column or a list of Columns; + # generate a properties.ColumnProperty + columns = util.to_list(prop) + column = columns[0] + if not expression.is_column(column): + raise sa_exc.ArgumentError( + "%s=%r is not an instance of MapperProperty or Column" + % (key, prop)) + + prop = self._props.get(key, None) + + if isinstance(prop, properties.ColumnProperty): + if prop.parent is self: + raise sa_exc.InvalidRequestError( + "Implicitly combining column %s with column " + "%s under attribute '%s'. Please configure one " + "or more attributes for these same-named columns " + "explicitly." + % (prop.columns[-1], column, key)) + + # existing properties.ColumnProperty from an inheriting + # mapper. make a copy and append our column to it + prop = prop.copy() + prop.columns.insert(0, column) + self._log("inserting column to existing list " + "in properties.ColumnProperty %s" % (key)) + return prop + elif prop is None or isinstance(prop, + properties.ConcreteInheritedProperty): + mapped_column = [] + for c in columns: + mc = self.mapped_table.corresponding_column(c) + if mc is None: + mc = self.local_table.corresponding_column(c) + if mc is not None: + # if the column is in the local table but not the + # mapped table, this corresponds to adding a + # column after the fact to the local table. + # [ticket:1523] + self.mapped_table._reset_exported() + mc = self.mapped_table.corresponding_column(c) + if mc is None: + raise sa_exc.ArgumentError( + "When configuring property '%s' on %s, " + "column '%s' is not represented in the mapper's " + "table. Use the `column_property()` function to " + "force this column to be mapped as a read-only " + "attribute." % (key, self, c)) + mapped_column.append(mc) + return properties.ColumnProperty(*mapped_column) + else: + raise sa_exc.ArgumentError( + "WARNING: when configuring property '%s' on %s, " + "column '%s' conflicts with property '%r'. " + "To resolve this, map the column to the class under a " + "different name in the 'properties' dictionary. Or, " + "to remove all awareness of the column entirely " + "(including its availability as a foreign key), " + "use the 'include_properties' or 'exclude_properties' " + "mapper arguments to control specifically which table " + "columns get mapped." % + (key, self, column.key, prop)) + def _post_configure_properties(self): """Call the ``init()`` method on all ``MapperProperties`` attached to this mapper. @@ -1205,7 +1224,6 @@ class Mapper(object): "|non-primary" or "") + ")" def _log(self, msg, *args): - self.logger.info( "%s " + msg, *((self._log_desc,) + args) ) @@ -1281,7 +1299,7 @@ class Mapper(object): for m in mappers: if not m.isa(self): raise sa_exc.InvalidRequestError( - "%r does not inherit from %r" % + "%r does not inherit from %r" % (m, self)) else: mappers = [] @@ -1431,7 +1449,7 @@ class Mapper(object): """ params = [(primary_key, sql.bindparam(None, type_=primary_key.type)) for primary_key in self.primary_key] - return sql.and_(*[k==v for (k, v) in params]), \ + return sql.and_(*[k == v for (k, v) in params]), \ util.column_dict(params) @_memoized_configured_property @@ -1456,6 +1474,7 @@ class Mapper(object): """ result = util.column_dict() + def visit_binary(binary): if binary.operator == operators.eq: if binary.left in result: @@ -1470,7 +1489,7 @@ class Mapper(object): if mapper.inherit_condition is not None: visitors.traverse( mapper.inherit_condition, {}, - {'binary':visit_binary}) + {'binary': visit_binary}) return result @@ -1723,7 +1742,8 @@ class Mapper(object): state, state.dict, rightcol, passive=attributes.PASSIVE_NO_INITIALIZE) - if rightval is attributes.PASSIVE_NO_RESULT or rightval is None: + if rightval is attributes.PASSIVE_NO_RESULT or \ + rightval is None: raise ColumnsNotAvailable() binary.right = sql.bindparam(None, rightval, type_=binary.right.type) @@ -1739,7 +1759,7 @@ class Mapper(object): allconds.append(visitors.cloned_traverse( mapper.inherit_condition, {}, - {'binary':visit_binary} + {'binary': visit_binary} ) ) except ColumnsNotAvailable: @@ -1787,7 +1807,7 @@ class Mapper(object): queue = deque(prop.cascade_iterator(type_, parent_state, parent_dict, visited_states, halt_on)) if queue: - visitables.append((queue,mpp, None, None)) + visitables.append((queue, mpp, None, None)) elif item_type is mpp: instance, instance_mapper, corresponding_state, \ corresponding_dict = iterator.popleft() @@ -1905,7 +1925,8 @@ def configure_mappers(): try: mapper._post_configure_properties() mapper._expire_memoizations() - mapper.dispatch.mapper_configured(mapper, mapper.class_) + mapper.dispatch.mapper_configured( + mapper, mapper.class_) _call_configured = mapper except: exc = sys.exc_info()[1] @@ -1945,11 +1966,12 @@ def validates(*names, **kw): Designates a method as a validator, a method which receives the name of the attribute as well as a value to be assigned, or in the - case of a collection, the value to be added to the collection. The function - can then raise validation exceptions to halt the process from continuing - (where Python's built-in ``ValueError`` and ``AssertionError`` exceptions are - reasonable choices), or can modify or replace the value before proceeding. - The function should otherwise return the given value. + case of a collection, the value to be added to the collection. + The function can then raise validation exceptions to halt the + process from continuing (where Python's built-in ``ValueError`` + and ``AssertionError`` exceptions are reasonable choices), or can + modify or replace the value before proceeding. The function should + otherwise return the given value. Note that a validator for a collection **cannot** issue a load of that collection within the validation routine - this usage raises |