diff options
Diffstat (limited to 'lib/sqlalchemy/orm/mapper.py')
-rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 180 |
1 files changed, 110 insertions, 70 deletions
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 7a92e8946..3374ea72d 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -43,6 +43,7 @@ _new_mappers = False _already_compiling = False _none_set = frozenset([None]) +_memoized_compiled_property = util.group_expirable_memoized_property() # a list of MapperExtensions that will be installed in all mappers by default global_extensions = [] @@ -192,8 +193,14 @@ class Mapper(object): else: self.polymorphic_map = _polymorphic_map - self.include_properties = include_properties - self.exclude_properties = exclude_properties + if include_properties: + self.include_properties = util.to_set(include_properties) + else: + self.include_properties = None + if exclude_properties: + self.exclude_properties = util.to_set(exclude_properties) + else: + self.exclude_properties = None self.compiled = False @@ -210,6 +217,7 @@ class Mapper(object): global _new_mappers _new_mappers = True self._log("constructed") + self._expire_memoizations() finally: _COMPILE_MUTEX.release() @@ -274,11 +282,6 @@ class Mapper(object): self.version_id_col = self.inherits.version_id_col self.version_id_generator = self.inherits.version_id_generator - for mapper in self.iterate_to_root(): - util.reset_memoized(mapper, '_equivalent_columns') - util.reset_memoized(mapper, '_sorted_tables') - util.reset_memoized(mapper, '_compiled_cache') - if self.order_by is False and \ not self.concrete and \ self.inherits.order_by is not False: @@ -471,7 +474,7 @@ class Mapper(object): for col in self._columntoproperty if not hasattr(col, 'table') or col.table not in self._cols_by_table) - + # if explicit PK argument sent, add those columns to the # primary key mappings if self.primary_key_argument: @@ -479,13 +482,14 @@ class Mapper(object): if k.table not in self._pks_by_table: self._pks_by_table[k.table] = util.OrderedSet() self._pks_by_table[k.table].add(k) - - if self.mapped_table not in self._pks_by_table or \ - len(self._pks_by_table[self.mapped_table]) == 0: - raise sa_exc.ArgumentError( - "Mapper %s could not assemble any primary " - "key columns for mapped table '%s'" % - (self, self.mapped_table.description)) + + # otherwise, see that we got a full PK for the mapped table + elif self.mapped_table not in self._pks_by_table or \ + len(self._pks_by_table[self.mapped_table]) == 0: + raise sa_exc.ArgumentError( + "Mapper %s could not assemble any primary " + "key columns for mapped table '%s'" % + (self, self.mapped_table.description)) if self.inherits and \ not self.concrete and \ @@ -526,7 +530,7 @@ class Mapper(object): # table columns mapped to lists of MapperProperty objects # using a list allows a single column to be defined as # populating multiple object attributes - self._columntoproperty = util.column_dict() + self._columntoproperty = _ColumnMapping(self) # load custom properties if self._init_properties: @@ -537,7 +541,7 @@ 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): + 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, @@ -550,7 +554,8 @@ class Mapper(object): if self._should_exclude( column.key, column_key, - local=self.local_table.c.contains_column(column) + local=self.local_table.c.contains_column(column), + column=column ): continue @@ -583,7 +588,7 @@ class Mapper(object): % col.description) else: instrument = True - if self._should_exclude(col.key, col.key, local=False): + if self._should_exclude(col.key, col.key, local=False, column=col): raise sa_exc.InvalidRequestError( "Cannot exclude or override the discriminator column %r" % col.key) @@ -625,8 +630,18 @@ class Mapper(object): # existing ColumnProperty from an inheriting mapper. # make a copy and append our column to it prop = prop.copy() + else: + util.warn( + "Implicitly combining column %s with column " + "%s under attribute '%s'. This usage will be " + "prohibited in 0.7. Please configure one " + "or more attributes for these same-named columns " + "explicitly." + % (prop.columns[-1], column, key)) + prop.columns.append(column) self._log("appending to existing ColumnProperty %s" % (key)) + elif prop is None or isinstance(prop, ConcreteInheritedProperty): mapped_column = [] for c in columns: @@ -791,6 +806,7 @@ class Mapper(object): self._compile_failed = exc raise finally: + self._expire_memoizations() _COMPILE_MUTEX.release() def _post_configure_properties(self): @@ -835,7 +851,11 @@ class Mapper(object): """ self._init_properties[key] = prop self._configure_property(key, prop, init=self.compiled) + self._expire_memoizations() + def _expire_memoizations(self): + for mapper in self.iterate_to_root(): + _memoized_compiled_property.expire_instance(mapper) def _log(self, msg, *args): self.logger.info( @@ -914,7 +934,7 @@ class Mapper(object): """ if spec == '*': - mappers = list(self.polymorphic_iterator()) + mappers = list(self.self_and_descendants) elif spec: mappers = [_class_to_mapper(m) for m in util.to_list(spec)] for m in mappers: @@ -953,7 +973,7 @@ class Mapper(object): return from_obj - @property + @_memoized_compiled_property def _single_table_criterion(self): if self.single and \ self.inherits and \ @@ -961,18 +981,17 @@ class Mapper(object): self.polymorphic_identity is not None: return self.polymorphic_on.in_( m.polymorphic_identity - for m in self.polymorphic_iterator()) + for m in self.self_and_descendants) else: return None - - - @util.memoized_property + + @_memoized_compiled_property def _with_polymorphic_mappers(self): if not self.with_polymorphic: return [self] return self._mappers_from_spec(*self.with_polymorphic) - @util.memoized_property + @_memoized_compiled_property def _with_polymorphic_selectable(self): if not self.with_polymorphic: return self.mapped_table @@ -997,6 +1016,11 @@ class Mapper(object): else: return mappers, self._selectable_from_mappers(mappers) + @_memoized_compiled_property + def _polymorphic_properties(self): + return tuple(self._iterate_polymorphic_properties( + self._with_polymorphic_mappers)) + def _iterate_polymorphic_properties(self, mappers=None): """Return an iterator of MapperProperty objects which will render into a SELECT.""" @@ -1028,7 +1052,7 @@ class Mapper(object): "provided by the get_property() and iterate_properties " "accessors.") - @util.memoized_property + @_memoized_compiled_property def _get_clause(self): """create a "get clause" based on the primary key. this is used by query.get() and many-to-one lazyloads to load this item @@ -1040,7 +1064,7 @@ class Mapper(object): return sql.and_(*[k==v for (k, v) in params]), \ util.column_dict(params) - @util.memoized_property + @_memoized_compiled_property def _equivalent_columns(self): """Create a map of all *equivalent* columns, based on the determination of column pairs that are equated to @@ -1072,7 +1096,7 @@ class Mapper(object): result[binary.right].add(binary.left) else: result[binary.right] = util.column_set((binary.left,)) - for mapper in self.base_mapper.polymorphic_iterator(): + for mapper in self.base_mapper.self_and_descendants: if mapper.inherit_condition is not None: visitors.traverse( mapper.inherit_condition, {}, @@ -1087,7 +1111,8 @@ class Mapper(object): isinstance(obj.__get__(None, obj), attributes.QueryableAttribute) - def _should_exclude(self, name, assigned_name, local): + + def _should_exclude(self, name, assigned_name, local, column): """determine whether a particular property should be implicitly present on the class. @@ -1109,13 +1134,17 @@ class Mapper(object): getattr(self.class_, assigned_name)): return True - if (self.include_properties is not None and - name not in self.include_properties): + if self.include_properties is not None and \ + name not in self.include_properties and \ + (column is None or column not in self.include_properties): self._log("not including property %s" % (name)) return True - if (self.exclude_properties is not None and - name in self.exclude_properties): + if self.exclude_properties is not None and \ + ( + name in self.exclude_properties or \ + (column is not None and column in self.exclude_properties) + ): self._log("excluding property %s" % (name)) return True @@ -1148,6 +1177,22 @@ class Mapper(object): yield m m = m.inherits + @_memoized_compiled_property + def self_and_descendants(self): + """The collection including this mapper and all descendant mappers. + + This includes not just the immediately inheriting mappers but + all their inheriting mappers as well. + + """ + descendants = [] + stack = deque([self]) + while stack: + item = stack.popleft() + descendants.append(item) + stack.extend(item._inheriting_mappers) + return tuple(descendants) + def polymorphic_iterator(self): """Iterate through the collection including this mapper and all descendant mappers. @@ -1157,14 +1202,9 @@ class Mapper(object): To iterate through an entire hierarchy, use ``mapper.base_mapper.polymorphic_iterator()``. - + """ - stack = deque([self]) - while stack: - item = stack.popleft() - yield item - stack.extend(item._inheriting_mappers) - + return iter(self.self_and_descendants) def primary_mapper(self): """Return the primary mapper corresponding to this mapper's class key @@ -1228,33 +1268,15 @@ class Mapper(object): def _primary_key_from_state(self, state): dict_ = state.dict - return [ - self._get_state_attr_by_column(state, dict_, column) for - column in self.primary_key] - - def _get_col_to_prop(self, column): - try: - return self._columntoproperty[column] - except KeyError: - prop = self._props.get(column.key, None) - if prop: - raise orm_exc.UnmappedColumnError( - "Column '%s.%s' is not available, due to " - "conflicting property '%s':%r" % - (column.table.name, column.name, - column.key, prop)) - else: - raise orm_exc.UnmappedColumnError( - "No column %s is configured on mapper %s..." % - (column, self)) + return [self._get_state_attr_by_column(state, dict_, column) for + column in self.primary_key] # TODO: improve names? def _get_state_attr_by_column(self, state, dict_, column): - return self._get_col_to_prop(column)._getattr(state, dict_, column) + return self._columntoproperty[column]._getattr(state, dict_, column) def _set_state_attr_by_column(self, state, dict_, column, value): - return self._get_col_to_prop(column).\ - _setattr(state, dict_, value, column) + return self._columntoproperty[column]._setattr(state, dict_, value, column) def _get_committed_attr_by_column(self, obj, column): state = attributes.instance_state(obj) @@ -1263,9 +1285,8 @@ class Mapper(object): def _get_committed_state_attr_by_column(self, state, dict_, column, passive=False): - return self._get_col_to_prop(column).\ - _getcommitted(state, dict_, - column, passive=passive) + return self._columntoproperty[column]._getcommitted( + state, dict_, column, passive=passive) def _optimized_get_statement(self, state, attribute_names): """assemble a WHERE clause which retrieves a given state by primary @@ -1375,14 +1396,14 @@ class Mapper(object): except StopIteration: visitables.pop() - @util.memoized_property + @_memoized_compiled_property def _compiled_cache(self): return util.LRUCache(self._compiled_cache_size) - @util.memoized_property + @_memoized_compiled_property def _sorted_tables(self): table_to_mapper = {} - for mapper in self.base_mapper.polymorphic_iterator(): + for mapper in self.base_mapper.self_and_descendants: for t in mapper.tables: table_to_mapper[t] = mapper @@ -2399,7 +2420,8 @@ def _load_scalar_attributes(state, attribute_names): # this codepath is rare - only valid when inside a flush, and the # object is becoming persistent but hasn't yet been assigned an identity_key. # check here to ensure we have the attrs we need. - pk_attrs = [mapper._get_col_to_prop(col).key for col in mapper.primary_key] + pk_attrs = [mapper._columntoproperty[col].key + for col in mapper.primary_key] if state.expired_attributes.intersection(pk_attrs): raise sa_exc.InvalidRequestError("Instance %s cannot be refreshed - it's not " " persistent and does not " @@ -2426,3 +2448,21 @@ def _load_scalar_attributes(state, attribute_names): raise orm_exc.ObjectDeletedError( "Instance '%s' has been deleted." % state_str(state)) + + +class _ColumnMapping(util.py25_dict): + """Error reporting helper for mapper._columntoproperty.""" + + def __init__(self, mapper): + self.mapper = mapper + + def __missing__(self, column): + prop = self.mapper._props.get(column) + if prop: + raise orm_exc.UnmappedColumnError( + "Column '%s.%s' is not available, due to " + "conflicting property '%s':%r" % ( + column.table.name, column.name, column.key, prop)) + raise orm_exc.UnmappedColumnError( + "No column %s is configured on mapper %s..." % + (column, self.mapper)) |