diff options
Diffstat (limited to 'lib/sqlalchemy/orm/mapper.py')
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 234 |
1 files changed, 128 insertions, 106 deletions
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 761e1af56..dceccf70f 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -206,16 +206,16 @@ class Mapper(object): local_table = None """The :class:`.Selectable` which this :class:`.Mapper` manages. - Typically is an instance of :class:`.Table` or :class:`.Alias`. - May also be ``None``. + Typically is an instance of :class:`.Table` or :class:`.Alias`. + May also be ``None``. The "local" table is the - selectable that the :class:`.Mapper` is directly responsible for + selectable that the :class:`.Mapper` is directly responsible for managing from an attribute access and flush perspective. For non-inheriting mappers, the local table is the same as the "mapped" table. For joined-table inheritance mappers, local_table will be the particular sub-table of the overall "join" which - this :class:`.Mapper` represents. If this mapper is a + this :class:`.Mapper` represents. If this mapper is a single-table inheriting mapper, local_table will be ``None``. See also :attr:`~.Mapper.mapped_table`. @@ -225,11 +225,11 @@ class Mapper(object): mapped_table = None """The :class:`.Selectable` to which this :class:`.Mapper` is mapped. - Typically an instance of :class:`.Table`, :class:`.Join`, or + Typically an instance of :class:`.Table`, :class:`.Join`, or :class:`.Alias`. - The "mapped" table is the selectable that - the mapper selects from during queries. For non-inheriting + The "mapped" table is the selectable that + the mapper selects from during queries. For non-inheriting mappers, the mapped table is the same as the "local" table. For joined-table inheritance mappers, mapped_table references the full :class:`.Join` representing full rows for this particular @@ -241,7 +241,7 @@ class Mapper(object): """ inherits = None - """References the :class:`.Mapper` which this :class:`.Mapper` + """References the :class:`.Mapper` which this :class:`.Mapper` inherits from, if any. This is a *read only* attribute determined during mapper construction. @@ -260,7 +260,7 @@ class Mapper(object): """ concrete = None - """Represent ``True`` if this :class:`.Mapper` is a concrete + """Represent ``True`` if this :class:`.Mapper` is a concrete inheritance mapper. This is a *read only* attribute determined during mapper construction. @@ -283,7 +283,7 @@ class Mapper(object): primary_key = None """An iterable containing the collection of :class:`.Column` objects - which comprise the 'primary key' of the mapped table, from the + 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 @@ -293,7 +293,7 @@ class Mapper(object): 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` + collection associated with the underlying tables; the :class:`.Mapper` features a ``primary_key`` argument that can override what the :class:`.Mapper` considers as primary key columns. @@ -320,7 +320,7 @@ class Mapper(object): """ single = None - """Represent ``True`` if this :class:`.Mapper` is a single table + """Represent ``True`` if this :class:`.Mapper` is a single table inheritance mapper. :attr:`~.Mapper.local_table` will be ``None`` if this flag is set. @@ -331,8 +331,8 @@ class Mapper(object): """ non_primary = None - """Represent ``True`` if this :class:`.Mapper` is a "non-primary" - mapper, e.g. a mapper that is used only to selet rows but not for + """Represent ``True`` if this :class:`.Mapper` is a "non-primary" + mapper, e.g. a mapper that is used only to selet rows but not for persistence management. This is a *read only* attribute determined during mapper construction. @@ -356,10 +356,10 @@ class Mapper(object): """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 + The identifiers can be of any type which is comparable to the type of column represented by :attr:`~.Mapper.polymorphic_on`. - An inheritance chain of mappers will all reference the same + An inheritance chain of mappers will all reference the same polymorphic map object. The object is used to correlate incoming result rows to target mappers. @@ -394,10 +394,10 @@ class Mapper(object): """ columns = None - """A collection of :class:`.Column` or other scalar expression + """A collection of :class:`.Column` or other scalar expression objects maintained by this :class:`.Mapper`. - The collection behaves the same as that of the ``c`` attribute on + The collection behaves the same as that of the ``c`` attribute on any :class:`.Table` object, except that only those columns included in this mapping are present, and are keyed based on the attribute name defined in the mapping, not necessarily the ``key`` attribute of the @@ -411,11 +411,11 @@ class Mapper(object): validators = None """An immutable dictionary of attributes which have been decorated - using the :func:`~.orm.validates` decorator. - + using the :func:`~.orm.validates` decorator. + The dictionary contains string attribute names as keys mapped to the actual validation method. - + """ c = None @@ -439,13 +439,13 @@ class Mapper(object): self.inherits = class_mapper(self.inherits, compile=False) if not issubclass(self.class_, self.inherits.class_): raise sa_exc.ArgumentError( - "Class '%s' does not inherit from '%s'" % + "Class '%s' does not inherit from '%s'" % (self.class_.__name__, self.inherits.class_.__name__)) if self.non_primary != self.inherits.non_primary: np = not self.non_primary and "primary" or "non-primary" raise sa_exc.ArgumentError( "Inheritance of %s mapper for class '%s' is " - "only allowed from a %s mapper" % + "only allowed from a %s mapper" % (np, self.class_.__name__, np)) # inherit_condition is optional. if self.local_table is None: @@ -468,7 +468,7 @@ class Mapper(object): self.inherits.local_table, self.local_table) self.mapped_table = sql.join( - self.inherits.mapped_table, + self.inherits.mapped_table, self.local_table, self.inherit_condition) @@ -495,7 +495,7 @@ class Mapper(object): "the inherited versioning column. " "version_id_col should only be specified on " "the base-most mapper that includes versioning." % - (self.version_id_col.description, + (self.version_id_col.description, self.inherits.version_id_col.description) ) @@ -524,7 +524,7 @@ class Mapper(object): if self.mapped_table is None: raise sa_exc.ArgumentError( - "Mapper '%s' does not have a mapped_table specified." + "Mapper '%s' does not have a mapped_table specified." % self) def _set_with_polymorphic(self, with_polymorphic): @@ -586,7 +586,7 @@ class Mapper(object): if self.inherits: self.dispatch._update(self.inherits.dispatch) super_extensions = set( - chain(*[m._deprecated_extensions + chain(*[m._deprecated_extensions for m in self.inherits.iterate_to_root()])) else: super_extensions = set() @@ -598,7 +598,7 @@ class Mapper(object): def _configure_listeners(self): if self.inherits: super_extensions = set( - chain(*[m._deprecated_extensions + chain(*[m._deprecated_extensions for m in self.inherits.iterate_to_root()])) else: super_extensions = set() @@ -645,8 +645,8 @@ class Mapper(object): "remove *all* current mappers from all classes." % self.class_) #else: - # a ClassManager may already exist as - # ClassManager.instrument_attribute() creates + # a ClassManager may already exist as + # ClassManager.instrument_attribute() creates # new managers for each subclass if they don't yet exist. _mapper_registry[self] = True @@ -660,8 +660,8 @@ class Mapper(object): manager.mapper = self manager.deferred_scalar_loader = self._load_scalar_attributes - - # The remaining members can be added by any mapper, + + # The remaining members can be added by any mapper, # e_name None or not. if manager.info.get(_INSTRUMENTOR, False): return @@ -745,10 +745,10 @@ class Mapper(object): self._readonly_props = set( self._columntoproperty[col] for col in self._columntoproperty - if not hasattr(col, 'table') or + if not hasattr(col, 'table') or col.table not in self._cols_by_table) - # if explicit PK argument sent, add those columns to the + # if explicit PK argument sent, add those columns to the # primary key mappings if self._primary_key_argument: for k in self._primary_key_argument: @@ -761,23 +761,23 @@ class Mapper(object): 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'" % + "key columns for mapped table '%s'" % (self, self.mapped_table.description)) elif self.local_table not in self._pks_by_table and \ isinstance(self.local_table, schema.Table): util.warn("Could not assemble any primary " "keys for locally mapped table '%s' - " - "no rows will be persisted in this Table." + "no rows will be persisted in this Table." % self.local_table.description) if self.inherits and \ not self.concrete and \ not self._primary_key_argument: - # if inheriting, the "primary key" for this mapper is + # if inheriting, the "primary key" for this mapper is # that of the inheriting (unless concrete or explicit) self.primary_key = self.inherits.primary_key else: - # determine primary key from argument or mapped_table pks - + # determine primary key from argument or mapped_table pks - # reduce to the minimal set of columns if self._primary_key_argument: primary_key = sql_util.reduce_columns( @@ -792,7 +792,7 @@ class Mapper(object): if len(primary_key) == 0: raise sa_exc.ArgumentError( "Mapper %s could not assemble any primary " - "key columns for mapped table '%s'" % + "key columns for mapped table '%s'" % (self, self.mapped_table.description)) self.primary_key = tuple(primary_key) @@ -844,19 +844,19 @@ class Mapper(object): if column in mapper._columntoproperty: column_key = mapper._columntoproperty[column].key - self._configure_property(column_key, - column, - init=False, + self._configure_property(column_key, + column, + init=False, setparent=True) def _configure_polymorphic_setter(self, init=False): - """Configure an attribute on the mapper representing the - 'polymorphic_on' column, if applicable, and not + """Configure an attribute on the mapper representing the + 'polymorphic_on' column, if applicable, and not already generated by _configure_properties (which is typical). Also create a setter function which will assign this attribute to the value of the 'polymorphic_identity' - upon instance construction, also if applicable. This + upon instance construction, also if applicable. This routine will run when an instance is created. """ @@ -905,15 +905,15 @@ class Mapper(object): 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 + # 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) if col is None: - # polymorphic_on doesn't derive from any column/expression + # 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 + # 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 @@ -931,11 +931,11 @@ class Mapper(object): "loads will not function properly" % col.description) else: - # column/expression that polymorphic_on derives from + # column/expression that polymorphic_on derives from # is present in our mapped table # and is probably mapped, but polymorphic_on itself - # is not. This happens when - # the polymorphic_on is only directly present in the + # is not. This happens when + # the polymorphic_on is only directly present in the # with_polymorphic selectable, as when use polymorphic_union. # we'll make a separate ColumnProperty for it. instrument = True @@ -951,7 +951,7 @@ class Mapper(object): key = col.key self._configure_property( - key, + key, properties.ColumnProperty(col, _instrument=instrument), init=init, setparent=True) polymorphic_key = key @@ -997,15 +997,15 @@ class Mapper(object): self._configure_property(key, prop, init=False, setparent=False) elif key not in self._props: self._configure_property( - key, - properties.ConcreteInheritedProperty(), + key, + properties.ConcreteInheritedProperty(), init=init, setparent=True) def _configure_property(self, key, prop, init=True, setparent=True): 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; + # we were passed a Column or a list of Columns; # generate a properties.ColumnProperty columns = util.to_list(prop) column = columns[0] @@ -1025,7 +1025,7 @@ class Mapper(object): "explicitly." % (prop.columns[-1], column, key)) - # existing properties.ColumnProperty from an inheriting + # existing properties.ColumnProperty from an inheriting # mapper. make a copy and append our column to it prop = prop.copy() prop.columns.insert(0, column) @@ -1064,14 +1064,14 @@ class Mapper(object): "(including its availability as a foreign key), " "use the 'include_properties' or 'exclude_properties' " "mapper arguments to control specifically which table " - "columns get mapped." % + "columns get mapped." % (key, self, column.key, prop)) if isinstance(prop, properties.ColumnProperty): col = self.mapped_table.corresponding_column(prop.columns[0]) - # if the column is not present in the mapped table, - # test if a column has been added after the fact to the + # if the column is not present in the mapped table, + # test if a column has been added after the fact to the # parent table (or their parent, etc.) [ticket:1570] if col is None and self.inherits: path = [self] @@ -1085,20 +1085,20 @@ class Mapper(object): break path.append(m) - # subquery expression, column not present in the mapped + # subquery expression, column not present in the mapped # selectable. if col is None: col = prop.columns[0] - # column is coming in after _readonly_props was + # column is coming in after _readonly_props was # initialized; check for 'readonly' if hasattr(self, '_readonly_props') and \ - (not hasattr(col, 'table') or + (not hasattr(col, 'table') or col.table not in self._cols_by_table): self._readonly_props.add(prop) else: - # if column is coming in after _cols_by_table was + # if column is coming in after _cols_by_table was # initialized, ensure the col is in the right set if hasattr(self, '_cols_by_table') and \ col.table in self._cols_by_table and \ @@ -1198,10 +1198,10 @@ class Mapper(object): def _log_desc(self): return "(" + self.class_.__name__ + \ "|" + \ - (self.local_table is not None and - self.local_table.description or + (self.local_table is not None and + self.local_table.description or str(self.local_table)) +\ - (self.non_primary and + (self.non_primary and "|non-primary" or "") + ")" def _log(self, msg, *args): @@ -1222,7 +1222,7 @@ class Mapper(object): def __str__(self): return "Mapper|%s|%s%s" % ( self.class_.__name__, - self.local_table is not None and + self.local_table is not None and self.local_table.description or None, self.non_primary and "|non-primary" or "" ) @@ -1281,7 +1281,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 = [] @@ -1347,7 +1347,7 @@ class Mapper(object): self._mappers_from_spec(spec, selectable), False) - def _with_polymorphic_args(self, spec=None, selectable=False, + def _with_polymorphic_args(self, spec=None, selectable=False, innerjoin=False): if self.with_polymorphic: if not spec: @@ -1360,7 +1360,7 @@ class Mapper(object): if selectable is not None: return mappers, selectable else: - return mappers, self._selectable_from_mappers(mappers, + return mappers, self._selectable_from_mappers(mappers, innerjoin) @_memoized_configured_property @@ -1387,7 +1387,7 @@ class Mapper(object): mappers]) ): if getattr(c, '_is_polymorphic_discriminator', False) and \ - (self.polymorphic_on is None or + (self.polymorphic_on is None or c.columns[0] is not self.polymorphic_on): continue yield c @@ -1475,7 +1475,7 @@ class Mapper(object): return result def _is_userland_descriptor(self, obj): - if isinstance(obj, (MapperProperty, + if isinstance(obj, (MapperProperty, attributes.QueryableAttribute)): return False elif not hasattr(obj, '__get__'): @@ -1528,7 +1528,7 @@ class Mapper(object): return False def common_parent(self, other): - """Return true if the given mapper shares a + """Return true if the given mapper shares a common inherited parent as this mapper.""" return self.base_mapper is other.base_mapper @@ -1657,7 +1657,7 @@ class Mapper(object): for col in self.primary_key ] - def _get_state_attr_by_column(self, state, dict_, column, + def _get_state_attr_by_column(self, state, dict_, column, passive=attributes.PASSIVE_OFF): prop = self._columntoproperty[column] return state.manager[prop.key].impl.get(state, dict_, passive=passive) @@ -1671,7 +1671,7 @@ class Mapper(object): dict_ = attributes.instance_dict(obj) return self._get_committed_state_attr_by_column(state, dict_, column) - def _get_committed_state_attr_by_column(self, state, dict_, + def _get_committed_state_attr_by_column(self, state, dict_, column, passive=attributes.PASSIVE_OFF): prop = self._columntoproperty[column] @@ -1698,8 +1698,8 @@ class Mapper(object): if statement is not None: result = loading.load_on_ident( session.query(self).from_statement(statement), - None, - only_load_props=attribute_names, + None, + only_load_props=attribute_names, refresh_state=state ) @@ -1723,17 +1723,17 @@ class Mapper(object): _none_set.issuperset(identity_key): util.warn("Instance %s to be refreshed doesn't " "contain a full primary key - can't be refreshed " - "(and shouldn't be expired, either)." + "(and shouldn't be expired, either)." % state_str(state)) return result = loading.load_on_ident( session.query(self), - identity_key, - refresh_state=state, + identity_key, + refresh_state=state, only_load_props=attribute_names) - # if instance is pending, a refresh operation + # if instance is pending, a refresh operation # may not complete (even if PK attributes are assigned) if has_key and result is None: raise orm_exc.ObjectDeletedError(state) @@ -1742,16 +1742,16 @@ class Mapper(object): """assemble a WHERE clause which retrieves a given state by primary key, using a minimized set of tables. - Applies to a joined-table inheritance mapper where the + Applies to a joined-table inheritance mapper where the requested attribute names are only present on joined tables, - not the base table. The WHERE clause attempts to include + not the base table. The WHERE clause attempts to include only those tables to minimize joins. """ props = self._props tables = set(chain( - *[sql_util.find_tables(c, check_columns=True) + *[sql_util.find_tables(c, check_columns=True) for key in attribute_names for c in props[key].columns] )) @@ -1770,8 +1770,8 @@ class Mapper(object): if leftcol.table not in tables: leftval = self._get_committed_state_attr_by_column( - state, state.dict, - leftcol, + state, state.dict, + leftcol, passive=attributes.PASSIVE_NO_INITIALIZE) if leftval is attributes.PASSIVE_NO_RESULT or leftval is None: raise ColumnsNotAvailable() @@ -1779,8 +1779,8 @@ class Mapper(object): type_=binary.right.type) elif rightcol.table not in tables: rightval = self._get_committed_state_attr_by_column( - state, state.dict, - rightcol, + state, state.dict, + rightcol, passive=attributes.PASSIVE_NO_INITIALIZE) if rightval is attributes.PASSIVE_NO_RESULT or rightval is None: raise ColumnsNotAvailable() @@ -1796,8 +1796,8 @@ class Mapper(object): start = True if start and not mapper.single: allconds.append(visitors.cloned_traverse( - mapper.inherit_condition, - {}, + mapper.inherit_condition, + {}, {'binary':visit_binary} ) ) @@ -1830,7 +1830,7 @@ class Mapper(object): visited_states = set() prp, mpp = object(), object() - visitables = deque([(deque(self._props.values()), prp, + visitables = deque([(deque(self._props.values()), prp, state, state.dict)]) while visitables: @@ -1843,7 +1843,7 @@ class Mapper(object): prop = iterator.popleft() if type_ not in prop.cascade: continue - queue = deque(prop.cascade_iterator(type_, parent_state, + queue = deque(prop.cascade_iterator(type_, parent_state, parent_dict, visited_states, halt_on)) if queue: visitables.append((queue,mpp, None, None)) @@ -1852,8 +1852,8 @@ class Mapper(object): corresponding_dict = iterator.popleft() yield instance, instance_mapper, \ corresponding_state, corresponding_dict - visitables.append((deque(instance_mapper._props.values()), - prp, corresponding_state, + visitables.append((deque(instance_mapper._props.values()), + prp, corresponding_state, corresponding_dict)) @_memoized_configured_property @@ -1865,9 +1865,31 @@ class Mapper(object): table_to_mapper = {} for mapper in self.base_mapper.self_and_descendants: for t in mapper.tables: - table_to_mapper[t] = mapper + table_to_mapper.setdefault(t, mapper) + + def skip(fk): + # attempt to skip dependencies that are not + # significant to the inheritance chain + # for two tables that are related by inheritance. + # while that dependency may be important, it's techinically + # not what we mean to sort on here. + parent = table_to_mapper.get(fk.parent.table) + dep = table_to_mapper.get(fk.column.table) + if parent is not None and \ + dep is not None and \ + dep is not parent and \ + dep.inherit_condition is not None: + cols = set(sql_util.find_columns(dep.inherit_condition)) + if parent.inherit_condition is not None: + cols = cols.union(sql_util.find_columns( + parent.inherit_condition)) + return fk.parent not in cols and fk.column not in cols + else: + return fk.parent not in cols + return False - sorted_ = sql_util.sort_tables(table_to_mapper.iterkeys()) + sorted_ = sql_util.sort_tables(table_to_mapper.iterkeys(), + skip_fn=skip) ret = util.OrderedDict() for t in sorted_: ret[t] = table_to_mapper[t] @@ -1910,7 +1932,7 @@ class Mapper(object): @util.memoized_property def _table_to_equated(self): - """memoized map of tables to collections of columns to be + """memoized map of tables to collections of columns to be synchronized upwards to the base mapper.""" result = util.defaultdict(list) @@ -1931,14 +1953,14 @@ def configure_mappers(): """Initialize the inter-mapper relationships of all mappers that have been constructed thus far. - This function can be called any number of times, but in + This function can be called any number of times, but in most cases is handled internally. """ global _new_mappers if not _new_mappers: - return + return _call_configured = None _COMPILE_MUTEX.acquire() @@ -1954,8 +1976,8 @@ def configure_mappers(): return # initialize properties on all mappers - # note that _mapper_registry is unordered, which - # may randomly conceal/reveal issues related to + # note that _mapper_registry is unordered, which + # may randomly conceal/reveal issues related to # the order of mapper compilation for mapper in list(_mapper_registry): if getattr(mapper, '_configure_failed', False): @@ -2022,7 +2044,7 @@ def validates(*names, **kw): condition which is not supported. :param \*names: list of attribute names to be validated. - :param include_removes: if True, "remove" events will be + :param include_removes: if True, "remove" events will be sent as well - the validation function must accept an additional argument "is_remove" which will be a boolean. @@ -2043,7 +2065,7 @@ def _event_on_load(state, ctx): def _event_on_first_init(manager, cls): """Initial mapper compilation trigger. - + instrumentation calls this one when InstanceState is first generated, and is needed for legacy mutable attributes to work. @@ -2056,11 +2078,11 @@ def _event_on_first_init(manager, cls): def _event_on_init(state, args, kwargs): """Run init_instance hooks. - + This also includes mapper compilation, normally not needed here but helps with some piecemeal configuration scenarios (such as in the ORM tutorial). - + """ instrumenting_mapper = state.manager.info.get(_INSTRUMENTOR) |
