# set the referenced mapper 'photos' to defer its loading of the column 'imagedata'
m = book_mapper.options(defer('photos.imagedata'))
</&>
-
+ <p>Options can also take a limited set of keyword arguments which will be applied to a new mapper. For example, to create a mapper that refreshes all objects loaded each time:</p>
+ <&|formatting.myt:code&>
+ m2 = mapper.options(always_refresh=True)
+ </&>
+ <p>Or, a mapper with different ordering:</p>
+ <&|formatting.myt:code&>
+ m2 = mapper.options(order_by=[newcol])
+ </&>
</&>
address = r[1]
</&>
</&>
-
+<&|doclib.myt:item, name="arguments", description="Mapper Arguments" &>
+<p>Other arguments not covered above include:</p>
+<ul>
+ <li>version_id_col=None - an integer-holding Column object that will be assigned an incrementing
+ counter, which is added to the WHERE clause used by UPDATE and DELETE statements. The matching row
+ count returned by the database is compared to the expected row count, and an exception is raised if they dont match. This is a basic "optimistic concurrency" check. Without the version id column, SQLAlchemy still compares the updated rowcount.</li>
+ <li>always_refresh=False - this option will cause the mapper to refresh all the attributes of all objects loaded by select/get statements, regardless of if they already exist in the current session. this includes all lazy- and eager-loaded relationship attributes, and will also overwrite any changes made to attributes on the column.</li>
+ <li>entity_name=None - this is an optional "entity name" that will be appended to the key used to associate classes to this mapper. What this basically means is, several primary mappers can be made against the same class by using different entity names; object instances will have the entity name tagged to them, so that all operations will occur on them relative to that mapper. When instantiating new objects, use <code>_sa_entity='name'</code> to tag them to the appropriate mapper.</li>
+</ul>
+</&>
<&|doclib.myt:item, name="extending", description="Extending Mapper" &>
<p>Mappers can have functionality augmented or replaced at many points in its execution via the usage of the MapperExtension class. This class is just a series of "hooks" where various functionality takes place. An application can make its own MapperExtension objects, overriding only the methods it needs.
<&|formatting.myt:code&>
extension = None,
order_by = False,
allow_column_override = False,
+ entity_name = None,
always_refresh = False,
+ version_id_col = None,
**kwargs):
if primarytable is not None:
self.order_by = order_by
self._options = {}
self.always_refresh = always_refresh
+ self.entity_name = entity_name
+ self.version_id_col = version_id_col
if not issubclass(class_, object):
raise ArgumentError("Class '%s' is not a new-style class" % class_.__name__)
# stricter set of tables to create "sync rules" by,based on the immediate
# inherited table, rather than all inherited tables
self._synchronizer = sync.ClauseSynchronizer(self, self, sync.ONETOMANY)
- self._synchronizer.compile(self.table.onclause, util.HashSet([inherits.noninherited_table]), TableFinder(table))
+ self._synchronizer.compile(self.table.onclause, util.HashSet([inherits.noninherited_table]), mapperutil.TableFinder(table))
# the old rule
#self._synchronizer.compile(self.table.onclause, inherits.tables, TableFinder(table))
else:
# locate all tables contained within the "table" passed in, which
# may be a join or other construct
- self.tables = TableFinder(self.table)
+ self.tables = mapperutil.TableFinder(self.table)
# determine primary key columns, either passed in, or get them from our set of tables
self.pks_by_table = {}
compiling or executing it"""
return self._compile(whereclause, **options)
- def copy(self):
+ def copy(self, **kwargs):
mapper = Mapper.__new__(Mapper)
mapper.__dict__.update(self.__dict__)
+ mapper.__dict__.update(kwargs)
mapper.props = self.props.copy()
return mapper
return callit
return Proxy()
- def options(self, *options):
+ def options(self, *options, **kwargs):
"""uses this mapper as a prototype for a new mapper with different behavior.
*options is a list of options directives, which include eagerload(), lazyload(), and noload()"""
try:
return self._options[optkey]
except KeyError:
- mapper = self.copy()
+ mapper = self.copy(**kwargs)
for option in options:
option.process(mapper)
self._options[optkey] = mapper
self.extension.before_update(self, obj)
hasdata = False
for col in table.columns:
- if self.pks_by_table[table].contains(col):
+ if col is self.version_id_col:
+ if not isinsert:
+ params[col._label] = self._getattrbycolumn(obj, col)
+ params[col.key] = params[col._label] + 1
+ else:
+ params[col.key] = 1
+ elif self.pks_by_table[table].contains(col):
# column is a primary key ?
if not isinsert:
# doing an UPDATE? put primary key values as "WHERE" parameters
clause = sql.and_()
for col in self.pks_by_table[table]:
clause.clauses.append(col == sql.bindparam(col._label))
+ if self.version_id_col is not None:
+ clause.clauses.append(self.version_id_col == sql.bindparam(self.version_id_col._label))
statement = table.update(clause)
rows = 0
for rec in update:
delete.append(params)
for col in self.pks_by_table[table]:
params[col.key] = self._getattrbycolumn(obj, col)
+ if self.version_id_col is not None:
+ params[self.version_id_col.key] = self._getattrbycolumn(obj, self.version_id_col)
self.extension.before_delete(self, obj)
if len(delete):
clause = sql.and_()
for col in self.pks_by_table[table]:
clause.clauses.append(col == sql.bindparam(col.key))
+ if self.version_id_col is not None:
+ clause.clauses.append(self.version_id_col == sql.bindparam(self.version_id_col.key))
statement = table.delete(clause)
c = statement.execute(*delete)
if table.engine.supports_sane_rowcount() and c.rowcount != len(delete):
if self.next is not None:
self.next.before_delete(mapper, instance)
-class TableFinder(sql.ClauseVisitor):
- """given a Clause, locates all the Tables within it into a list."""
- def __init__(self, table, check_columns=False):
- self.tables = []
- self.check_columns = check_columns
- if table is not None:
- table.accept_visitor(self)
- def visit_table(self, table):
- self.tables.append(table)
- def __len__(self):
- return len(self.tables)
- def __getitem__(self, i):
- return self.tables[i]
- def __iter__(self):
- return iter(self.tables)
- def __contains__(self, obj):
- return obj in self.tables
- def __add__(self, obj):
- return self.tables + list(obj)
- def visit_column(self, column):
- if self.check_columns:
- column.table.accept_visitor(self)
def hash_key(obj):
if obj is None: