summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/mapper.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/orm/mapper.py')
-rw-r--r--lib/sqlalchemy/orm/mapper.py180
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))