[Sqlalchemy-commits] [1227] sqlalchemy/trunk/doc/build/content: starting to refactor mapper slightly
Brought to you by:
zzzeek
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><style type="text/css"><!-- #msg dl { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; } #msg dt { float: left; width: 6em; font-weight: bold; } #msg dt:after { content:':';} #msg dl, #msg dt, #msg ul, #msg li { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; } #msg dl a { font-weight: bold} #msg dl a:link { color:#fc3; } #msg dl a:active { color:#ff0; } #msg dl a:visited { color:#cc6; } h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; } #msg pre { overflow: auto; background: #ffc; border: 1px #fc0 solid; padding: 6px; } #msg ul, pre { overflow: auto; } #patch { width: 100%; } #patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;} #patch .propset h4, #patch .binary h4 {margin:0;} #patch pre {padding:0;line-height:1.2em;margin:0;} #patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;} #patch .propset .diff, #patch .binary .diff {padding:10px 0;} #patch span {display:block;padding:0 10px;} #patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;} #patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;} #patch .lines, .info {color:#888;background:#fff;} --></style> <title>[1227] sqlalchemy/trunk/doc/build/content: starting to refactor mapper slightly, adding entity_name, version_id_col, allowing keywords in mapper.options()</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1227</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-03-30 22:27:05 -0600 (Thu, 30 Mar 2006)</dd> </dl> <h3>Log Message</h3> <pre>starting to refactor mapper slightly, adding entity_name, version_id_col, allowing keywords in mapper.options()</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunkdocbuildcontentadv_datamappingmyt">sqlalchemy/trunk/doc/build/content/adv_datamapping.myt</a></li> <li><a href="#sqlalchemytrunklibsqlalchemymappingmapperpy">sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunkdocbuildcontentadv_datamappingmyt"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/doc/build/content/adv_datamapping.myt (1226 => 1227)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/doc/build/content/adv_datamapping.myt 2006-03-31 02:41:32 UTC (rev 1226) +++ sqlalchemy/trunk/doc/build/content/adv_datamapping.myt 2006-03-31 04:27:05 UTC (rev 1227) </span><span class="lines">@@ -314,7 +314,14 @@ </span><span class="cx"> # set the referenced mapper 'photos' to defer its loading of the column 'imagedata' </span><span class="cx"> m = book_mapper.options(defer('photos.imagedata')) </span><span class="cx"> </&> </span><del>- </del><ins>+ <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]) + </&> </ins><span class="cx"> </span><span class="cx"> </&> </span><span class="cx"> </span><span class="lines">@@ -557,7 +564,16 @@ </span><span class="cx"> address = r[1] </span><span class="cx"> </&> </span><span class="cx"> </&> </span><del>- </del><ins>+<&|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> +</&> </ins><span class="cx"> <&|doclib.myt:item, name="extending", description="Extending Mapper" &> </span><span class="cx"> <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. </span><span class="cx"> <&|formatting.myt:code&> </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemymappingmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py (1226 => 1227)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py 2006-03-31 02:41:32 UTC (rev 1226) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py 2006-03-31 04:27:05 UTC (rev 1227) </span><span class="lines">@@ -40,7 +40,9 @@ </span><span class="cx"> extension = None, </span><span class="cx"> order_by = False, </span><span class="cx"> allow_column_override = False, </span><ins>+ entity_name = None, </ins><span class="cx"> always_refresh = False, </span><ins>+ version_id_col = None, </ins><span class="cx"> **kwargs): </span><span class="cx"> </span><span class="cx"> if primarytable is not None: </span><span class="lines">@@ -55,6 +57,8 @@ </span><span class="cx"> self.order_by = order_by </span><span class="cx"> self._options = {} </span><span class="cx"> self.always_refresh = always_refresh </span><ins>+ self.entity_name = entity_name + self.version_id_col = version_id_col </ins><span class="cx"> </span><span class="cx"> if not issubclass(class_, object): </span><span class="cx"> raise ArgumentError("Class '%s' is not a new-style class" % class_.__name__) </span><span class="lines">@@ -85,7 +89,7 @@ </span><span class="cx"> # stricter set of tables to create "sync rules" by,based on the immediate </span><span class="cx"> # inherited table, rather than all inherited tables </span><span class="cx"> self._synchronizer = sync.ClauseSynchronizer(self, self, sync.ONETOMANY) </span><del>- self._synchronizer.compile(self.table.onclause, util.HashSet([inherits.noninherited_table]), TableFinder(table)) </del><ins>+ self._synchronizer.compile(self.table.onclause, util.HashSet([inherits.noninherited_table]), mapperutil.TableFinder(table)) </ins><span class="cx"> # the old rule </span><span class="cx"> #self._synchronizer.compile(self.table.onclause, inherits.tables, TableFinder(table)) </span><span class="cx"> else: </span><span class="lines">@@ -100,7 +104,7 @@ </span><span class="cx"> </span><span class="cx"> # locate all tables contained within the "table" passed in, which </span><span class="cx"> # may be a join or other construct </span><del>- self.tables = TableFinder(self.table) </del><ins>+ self.tables = mapperutil.TableFinder(self.table) </ins><span class="cx"> </span><span class="cx"> # determine primary key columns, either passed in, or get them from our set of tables </span><span class="cx"> self.pks_by_table = {} </span><span class="lines">@@ -350,9 +354,10 @@ </span><span class="cx"> compiling or executing it""" </span><span class="cx"> return self._compile(whereclause, **options) </span><span class="cx"> </span><del>- def copy(self): </del><ins>+ def copy(self, **kwargs): </ins><span class="cx"> mapper = Mapper.__new__(Mapper) </span><span class="cx"> mapper.__dict__.update(self.__dict__) </span><ins>+ mapper.__dict__.update(kwargs) </ins><span class="cx"> mapper.props = self.props.copy() </span><span class="cx"> return mapper </span><span class="cx"> </span><span class="lines">@@ -374,7 +379,7 @@ </span><span class="cx"> return callit </span><span class="cx"> return Proxy() </span><span class="cx"> </span><del>- def options(self, *options): </del><ins>+ def options(self, *options, **kwargs): </ins><span class="cx"> """uses this mapper as a prototype for a new mapper with different behavior. </span><span class="cx"> *options is a list of options directives, which include eagerload(), lazyload(), and noload()""" </span><span class="cx"> </span><span class="lines">@@ -382,7 +387,7 @@ </span><span class="cx"> try: </span><span class="cx"> return self._options[optkey] </span><span class="cx"> except KeyError: </span><del>- mapper = self.copy() </del><ins>+ mapper = self.copy(**kwargs) </ins><span class="cx"> for option in options: </span><span class="cx"> option.process(mapper) </span><span class="cx"> self._options[optkey] = mapper </span><span class="lines">@@ -610,7 +615,13 @@ </span><span class="cx"> self.extension.before_update(self, obj) </span><span class="cx"> hasdata = False </span><span class="cx"> for col in table.columns: </span><del>- if self.pks_by_table[table].contains(col): </del><ins>+ 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): </ins><span class="cx"> # column is a primary key ? </span><span class="cx"> if not isinsert: </span><span class="cx"> # doing an UPDATE? put primary key values as "WHERE" parameters </span><span class="lines">@@ -664,6 +675,8 @@ </span><span class="cx"> clause = sql.and_() </span><span class="cx"> for col in self.pks_by_table[table]: </span><span class="cx"> clause.clauses.append(col == sql.bindparam(col._label)) </span><ins>+ if self.version_id_col is not None: + clause.clauses.append(self.version_id_col == sql.bindparam(self.version_id_col._label)) </ins><span class="cx"> statement = table.update(clause) </span><span class="cx"> rows = 0 </span><span class="cx"> for rec in update: </span><span class="lines">@@ -729,11 +742,15 @@ </span><span class="cx"> delete.append(params) </span><span class="cx"> for col in self.pks_by_table[table]: </span><span class="cx"> params[col.key] = self._getattrbycolumn(obj, col) </span><ins>+ if self.version_id_col is not None: + params[self.version_id_col.key] = self._getattrbycolumn(obj, self.version_id_col) </ins><span class="cx"> self.extension.before_delete(self, obj) </span><span class="cx"> if len(delete): </span><span class="cx"> clause = sql.and_() </span><span class="cx"> for col in self.pks_by_table[table]: </span><span class="cx"> clause.clauses.append(col == sql.bindparam(col.key)) </span><ins>+ if self.version_id_col is not None: + clause.clauses.append(self.version_id_col == sql.bindparam(self.version_id_col.key)) </ins><span class="cx"> statement = table.delete(clause) </span><span class="cx"> c = statement.execute(*delete) </span><span class="cx"> if table.engine.supports_sane_rowcount() and c.rowcount != len(delete): </span><span class="lines">@@ -1036,28 +1053,6 @@ </span><span class="cx"> if self.next is not None: </span><span class="cx"> self.next.before_delete(mapper, instance) </span><span class="cx"> </span><del>-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) </del><span class="cx"> </span><span class="cx"> def hash_key(obj): </span><span class="cx"> if obj is None: </span></span></pre> </div> </div> </body> </html> |