[Sqlalchemy-commits] [4866] sqlalchemy/trunk: - implemented [ticket:887], refresh readonly props up
Brought to you by:
zzzeek
From: <co...@sq...> - 2008-06-21 17:23:17
|
<!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><meta http-equiv="content-type" content="text/html; charset=utf-8" /><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, #header, #footer { 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; } #header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; } #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>[4866] sqlalchemy/trunk: - implemented [ticket:887], refresh readonly props upon save</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>4866</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2008-06-21 13:23:14 -0400 (Sat, 21 Jun 2008)</dd> </dl> <h3>Log Message</h3> <pre>- implemented [ticket:887], refresh readonly props upon save - moved up "eager_defaults" active refresh step (this is an option used by just one user pretty much) to be per-instance instead of per-table - fixed table defs from previous deferred attributes enhancement - CompositeColumnLoader equality comparison fixed for a/b == None; I suspect the composite capability in SA needs a lot more work than this</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunkCHANGES">sqlalchemy/trunk/CHANGES</a></li> <li><a href="#sqlalchemytrunklibsqlalchemyormmapperpy">sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemyormstrategiespy">sqlalchemy/trunk/lib/sqlalchemy/orm/strategies.py</a></li> <li><a href="#sqlalchemytrunktestormmapperpy">sqlalchemy/trunk/test/orm/mapper.py</a></li> <li><a href="#sqlalchemytrunktestormunitofworkpy">sqlalchemy/trunk/test/orm/unitofwork.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunkCHANGES"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/CHANGES (4865 => 4866)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/CHANGES 2008-06-21 16:08:04 UTC (rev 4865) +++ sqlalchemy/trunk/CHANGES 2008-06-21 17:23:14 UTC (rev 4866) </span><span class="lines">@@ -5,9 +5,17 @@ </span><span class="cx"> ======= </span><span class="cx"> 0.5beta2 </span><span class="cx"> ======== </span><ins>+- orm </ins><span class="cx"> - In addition to expired attributes, deferred attributes </span><del>- also load if their data is present in the result set </del><ins>+ also load if their data is present in the result set. </ins><span class="cx"> [ticket:870] </span><ins>+ + - column_property() attributes which represent SQL expressions + or columns that are not present in the mapped tables + (such as those from views) are automatically expired + after an INSERT or UPDATE, assuming they have not been + locally modified, so that they are refreshed with the + most recent data upon access. [ticket:887] </ins><span class="cx"> </span><span class="cx"> 0.5beta1 </span><span class="cx"> ======== </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyormmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py (4865 => 4866)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py 2008-06-21 16:08:04 UTC (rev 4865) +++ sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py 2008-06-21 17:23:14 UTC (rev 4866) </span><span class="lines">@@ -138,7 +138,7 @@ </span><span class="cx"> self._clause_adapter = None </span><span class="cx"> self._requires_row_aliasing = False </span><span class="cx"> self.__inherits_equated_pairs = None </span><del>- </del><ins>+ </ins><span class="cx"> if not issubclass(class_, object): </span><span class="cx"> raise sa_exc.ArgumentError("Class '%s' is not a new-style class" % class_.__name__) </span><span class="cx"> </span><span class="lines">@@ -521,7 +521,15 @@ </span><span class="cx"> # ordering is important since it determines the ordering of mapper.primary_key (and therefore query.get()) </span><span class="cx"> self._pks_by_table[t] = util.OrderedSet(t.primary_key).intersection(pk_cols) </span><span class="cx"> self._cols_by_table[t] = util.OrderedSet(t.c).intersection(all_cols) </span><del>- </del><ins>+ + # determine cols that aren't expressed within our tables; + # mark these as "read only" properties which are refreshed upon + # INSERT/UPDATE + self._readonly_props = util.Set([ + self._columntoproperty[col] for col in all_cols if + not hasattr(col, 'table') or col.table not in self._cols_by_table + ]) + </ins><span class="cx"> # if explicit PK argument sent, add those columns to the primary key mappings </span><span class="cx"> if self.primary_key_argument: </span><span class="cx"> for k in self.primary_key_argument: </span><span class="lines">@@ -720,6 +728,13 @@ </span><span class="cx"> # columns (included in zblog tests) </span><span class="cx"> if col is None: </span><span class="cx"> col = prop.columns[0] </span><ins>+ + # column is coming in after _readonly_props was initialized; check + # for 'readonly' + if hasattr(self, '_readonly_props') and \ + (not hasattr(col, 'table') or col.table not in self._cols_by_table): + self._readonly_props.add(prop) + </ins><span class="cx"> else: </span><span class="cx"> # if column is coming in after _cols_by_table was initialized, ensure the col is in the </span><span class="cx"> # right set </span><span class="lines">@@ -1169,10 +1184,27 @@ </span><span class="cx"> </span><span class="cx"> # testlib.pragma exempt:__hash__ </span><span class="cx"> inserted_objects.add((state, connection)) </span><del>- </del><ins>+ </ins><span class="cx"> if not postupdate: </span><del>- # call after_XXX extensions </del><span class="cx"> for state, mapper, connection, has_identity in tups: </span><ins>+ + # expire readonly attributes + readonly = state.unmodified.intersection([ + p.key for p in chain(*[m._readonly_props for m in mapper.iterate_to_root()]) + ]) + + if readonly: + _expire_state(state, readonly) + + # if specified, eagerly refresh whatever has + # been expired. + if self.eager_defaults and state.unloaded: + state.key = self._identity_key_from_state(state) + uowtransaction.session.query(self)._get( + state.key, refresh_state=state, + only_load_props=state.unloaded) + + # call after_XXX extensions </ins><span class="cx"> if not has_identity: </span><span class="cx"> if 'after_insert' in mapper.extension.methods: </span><span class="cx"> mapper.extension.after_insert(mapper, connection, state.obj()) </span><span class="lines">@@ -1184,12 +1216,13 @@ </span><span class="cx"> sync.populate(state, self, state, self, self.__inherits_equated_pairs) </span><span class="cx"> </span><span class="cx"> def __postfetch(self, uowtransaction, connection, table, state, resultproxy, params, value_params): </span><del>- """After an ``INSERT`` or ``UPDATE``, assemble newly generated - values on an instance. For columns which are marked as being generated - on the database side, set up a group-based "deferred" loader - which will populate those attributes in one query when next accessed. </del><ins>+ """For a given Table that has just been inserted/updated, + mark as 'expired' those attributes which correspond to columns + that are marked as 'postfetch', and populate attributes which + correspond to columns marked as 'prefetch' or were otherwise generated + within _save_obj(). + </ins><span class="cx"> """ </span><del>- </del><span class="cx"> postfetch_cols = resultproxy.postfetch_cols() </span><span class="cx"> generated_cols = list(resultproxy.prefetch_cols()) </span><span class="cx"> </span><span class="lines">@@ -1197,6 +1230,7 @@ </span><span class="cx"> po = table.corresponding_column(self.polymorphic_on) </span><span class="cx"> if po: </span><span class="cx"> generated_cols.append(po) </span><ins>+ </ins><span class="cx"> if self.version_id_col: </span><span class="cx"> generated_cols.append(self.version_id_col) </span><span class="cx"> </span><span class="lines">@@ -1205,15 +1239,9 @@ </span><span class="cx"> self._set_state_attr_by_column(state, c, params[c.key]) </span><span class="cx"> </span><span class="cx"> deferred_props = [prop.key for prop in [self._columntoproperty[c] for c in postfetch_cols]] </span><del>- </del><ins>+ </ins><span class="cx"> if deferred_props: </span><del>- if self.eager_defaults: - state.key = self._identity_key_from_state(state) - uowtransaction.session.query(self)._get( - state.key, refresh_state=state, - only_load_props=deferred_props) - else: - _expire_state(state, deferred_props) </del><ins>+ _expire_state(state, deferred_props) </ins><span class="cx"> </span><span class="cx"> def _delete_obj(self, states, uowtransaction): </span><span class="cx"> """Issue ``DELETE`` statements for a list of objects. </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyormstrategiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/orm/strategies.py (4865 => 4866)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/orm/strategies.py 2008-06-21 16:08:04 UTC (rev 4865) +++ sqlalchemy/trunk/lib/sqlalchemy/orm/strategies.py 2008-06-21 17:23:14 UTC (rev 4866) </span><span class="lines">@@ -96,6 +96,9 @@ </span><span class="cx"> return self.parent_property.composite_class(*obj.__composite_values__()) </span><span class="cx"> </span><span class="cx"> def compare(a, b): </span><ins>+ if a is None or b is None: + return a is b + </ins><span class="cx"> for col, aprop, bprop in zip(self.columns, </span><span class="cx"> a.__composite_values__(), </span><span class="cx"> b.__composite_values__()): </span></span></pre></div> <a id="sqlalchemytrunktestormmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/orm/mapper.py (4865 => 4866)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/orm/mapper.py 2008-06-21 16:08:04 UTC (rev 4865) +++ sqlalchemy/trunk/test/orm/mapper.py 2008-06-21 17:23:14 UTC (rev 4866) </span><span class="lines">@@ -1262,12 +1262,12 @@ </span><span class="cx"> def define_tables(self, metadata): </span><span class="cx"> Table("thing", metadata, </span><span class="cx"> Column("id", Integer, primary_key=True), </span><del>- Column("name", String)) </del><ins>+ Column("name", String(20))) </ins><span class="cx"> </span><span class="cx"> Table("human", metadata, </span><span class="cx"> Column("id", Integer, primary_key=True), </span><span class="cx"> Column("thing_id", Integer, ForeignKey("thing.id")), </span><del>- Column("name", String)) </del><ins>+ Column("name", String(20))) </ins><span class="cx"> </span><span class="cx"> @testing.resolve_artifact_names </span><span class="cx"> def setup_mappers(self): </span></span></pre></div> <a id="sqlalchemytrunktestormunitofworkpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/orm/unitofwork.py (4865 => 4866)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/orm/unitofwork.py 2008-06-21 16:08:04 UTC (rev 4865) +++ sqlalchemy/trunk/test/orm/unitofwork.py 2008-06-21 17:23:14 UTC (rev 4866) </span><span class="lines">@@ -7,8 +7,8 @@ </span><span class="cx"> from sqlalchemy.orm import mapper as orm_mapper </span><span class="cx"> </span><span class="cx"> from testlib import engines, sa, testing </span><del>-from testlib.sa import Table, Column, Integer, String, ForeignKey -from testlib.sa.orm import mapper, relation, create_session </del><ins>+from testlib.sa import Table, Column, Integer, String, ForeignKey, literal_column +from testlib.sa.orm import mapper, relation, create_session, column_property </ins><span class="cx"> from testlib.testing import eq_, ne_ </span><span class="cx"> from testlib.compat import set </span><span class="cx"> from orm import _base, _fixtures </span><span class="lines">@@ -985,7 +985,50 @@ </span><span class="cx"> Secondary(data='s1'), </span><span class="cx"> Secondary(data='s2')])) </span><span class="cx"> </span><ins>+class ColumnPropertyTest(_base.MappedTest): + def define_tables(self, metadata): + Table('data', metadata, + Column('id', Integer, primary_key=True), + Column('a', String(50)), + Column('b', String(50)) + ) + + def setup_mappers(self): + class Data(_base.BasicEntity): + pass + + @testing.resolve_artifact_names + def test_refreshes(self): + mapper(Data, data, properties={ + 'aplusb':column_property(data.c.a + literal_column("' '") + data.c.b) + }) + self._test() </ins><span class="cx"> </span><ins>+ @testing.resolve_artifact_names + def test_refreshes_post_init(self): + m = mapper(Data, data) + m.add_property('aplusb', column_property(data.c.a + literal_column("' '") + data.c.b)) + self._test() + + @testing.resolve_artifact_names + def _test(self): + sess = create_session() + + d1 = Data(a="hello", b="there") + sess.add(d1) + sess.flush() + + self.assertEquals(d1.aplusb, "hello there") + + d1.b = "bye" + sess.flush() + self.assertEquals(d1.aplusb, "hello bye") + + d1.b = 'foobar' + d1.aplusb = 'im setting this explicitly' + sess.flush() + self.assertEquals(d1.aplusb, "im setting this explicitly") + </ins><span class="cx"> class OneToManyTest(_fixtures.FixtureTest): </span><span class="cx"> run_inserts = None </span><span class="cx"> </span></span></pre> </div> </div> </body> </html> |