[Sqlalchemy-commits] [2582] sqlalchemy/trunk: - restored old "column_property()" ORM function ( use
Brought to you by:
zzzeek
From: <co...@sq...> - 2007-04-29 22:26:20
|
<!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>[2582] sqlalchemy/trunk: - restored old "column_property()" ORM function ( used to be called</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>2582</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2007-04-29 18:26:39 -0400 (Sun, 29 Apr 2007)</dd> </dl> <h3>Log Message</h3> <pre>- restored old "column_property()" ORM function (used to be called "column()") to force any column expression to be added as a property on a mapper, particularly those that aren't present in the mapped selectable. this allows "scalar expressions" of any kind to be added as relations (though they have issues with eager loads).</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunkCHANGES">sqlalchemy/trunk/CHANGES</a></li> <li><a href="#sqlalchemytrunklibsqlalchemyorm__init__py">sqlalchemy/trunk/lib/sqlalchemy/orm/__init__.py</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="#sqlalchemytrunklibsqlalchemysqlpy">sqlalchemy/trunk/lib/sqlalchemy/sql.py</a></li> <li><a href="#sqlalchemytrunktestormmapperpy">sqlalchemy/trunk/test/orm/mapper.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunkCHANGES"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/CHANGES (2581 => 2582)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/CHANGES 2007-04-29 21:33:05 UTC (rev 2581) +++ sqlalchemy/trunk/CHANGES 2007-04-29 22:26:39 UTC (rev 2582) </span><span class="lines">@@ -94,12 +94,17 @@ </span><span class="cx"> #552 </span><span class="cx"> - fix to using distinct() or distinct=True in combination with </span><span class="cx"> join() and similar </span><del>- - corresponding to label/bindparam name generataion, eager loaders </del><ins>+ - corresponding to label/bindparam name generation, eager loaders </ins><span class="cx"> generate deterministic names for the aliases they create using </span><span class="cx"> md5 hashes. </span><span class="cx"> - improved/fixed custom collection classes when giving it "set"/ </span><span class="cx"> "sets.Set" classes or subclasses (was still looking for append() </span><span class="cx"> methods on them during lazy loads) </span><ins>+ - restored old "column_property()" ORM function (used to be called + "column()") to force any column expression to be added as a property + on a mapper, particularly those that aren't present in the mapped + selectable. this allows "scalar expressions" of any kind to be + added as relations (though they have issues with eager loads). </ins><span class="cx"> - fix to many-to-many relationships targeting polymorphic mappers </span><span class="cx"> [ticket:533] </span><span class="cx"> - making progress with session.merge() as well as combining its </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyorm__init__py"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/orm/__init__.py (2581 => 2582)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/orm/__init__.py 2007-04-29 21:33:05 UTC (rev 2581) +++ sqlalchemy/trunk/lib/sqlalchemy/orm/__init__.py 2007-04-29 22:26:39 UTC (rev 2582) </span><span class="lines">@@ -19,7 +19,7 @@ </span><span class="cx"> from sqlalchemy.orm.session import Session as create_session </span><span class="cx"> from sqlalchemy.orm.session import object_session, attribute_manager </span><span class="cx"> </span><del>-__all__ = ['relation', 'backref', 'eagerload', 'lazyload', 'noload', 'deferred', 'defer', 'undefer', 'extension', </del><ins>+__all__ = ['relation', 'column_property', 'backref', 'eagerload', 'lazyload', 'noload', 'deferred', 'defer', 'undefer', 'extension', </ins><span class="cx"> 'mapper', 'clear_mappers', 'compile_mappers', 'clear_mapper', 'class_mapper', 'object_mapper', 'MapperExtension', 'Query', </span><span class="cx"> 'cascade_mappers', 'polymorphic_union', 'create_session', 'synonym', 'contains_alias', 'contains_eager', 'EXT_PASS', 'object_session' </span><span class="cx"> ] </span><span class="lines">@@ -34,6 +34,33 @@ </span><span class="cx"> raise exceptions.ArgumentError("relation(class, table, **kwargs) is deprecated. Please use relation(class, **kwargs) or relation(mapper, **kwargs).") </span><span class="cx"> return _relation_loader(*args, **kwargs) </span><span class="cx"> </span><ins>+def column_property(*args, **kwargs): + """Provide a column-level property for use with a Mapper. + + Normally, custom column-level properties that represent columns + directly or indirectly present within the mapped selectable + can just be added to the ``properties`` dictionary directly, + in which case this function's usage is not necessary. + + In the case of a ``ColumnElement`` directly present within the + ``properties`` dictionary, the given column is converted to be the exact column + located within the mapped selectable, in the case that the mapped selectable + is not the exact parent selectable of the given column, but shares a common + base table relationship with that column. + + Use this function when the column expression being added does not + correspond to any single column within the mapped selectable, + such as a labeled function or scalar-returning subquery, to force the element + to become a mapped property regardless of it not being present within the + mapped selectable. + + Note that persistence of instances is driven from the collection of columns + within the mapped selectable, so column properties attached to a Mapper which have + no direct correspondence to the mapped selectable will effectively be non-persisted + attributes. + """ + return properties.ColumnProperty(*args, **kwargs) + </ins><span class="cx"> def _relation_loader(mapper, secondary=None, primaryjoin=None, secondaryjoin=None, lazy=True, **kwargs): </span><span class="cx"> return properties.PropertyLoader(mapper, secondary, primaryjoin, secondaryjoin, lazy=lazy, **kwargs) </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyormmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py (2581 => 2582)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py 2007-04-29 21:33:05 UTC (rev 2581) +++ sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py 2007-04-29 22:26:39 UTC (rev 2582) </span><span class="lines">@@ -792,33 +792,24 @@ </span><span class="cx"> self._compile_all() </span><span class="cx"> self._compile_property(key, prop, init=True) </span><span class="cx"> </span><del>- def _create_prop_from_column(self, column, skipmissing=False): - if sql.is_column(column): - try: - column = self.mapped_table.corresponding_column(column) - except KeyError: - if skipmissing: - return - raise exceptions.ArgumentError("Column '%s' is not represented in mapper's table" % prop._label) - return ColumnProperty(column) - elif isinstance(column, list) and sql.is_column(column[0]): - try: - column = [self.mapped_table.corresponding_column(c) for c in column] - except KeyError, e: - # TODO: want to take the columns we have from this - if skipmissing: - return - raise exceptions.ArgumentError("Column '%s' is not represented in mapper's table" % e.args[0]) - return ColumnProperty(*column) - else: </del><ins>+ def _create_prop_from_column(self, column): + column = util.to_list(column) + if not sql.is_column(column[0]): </ins><span class="cx"> return None </span><ins>+ mapped_column = [] + for c in column: + mc = self.mapped_table.corresponding_column(c, raiseerr=False) + if not mc: + raise exceptions.ArgumentError("Column '%s' is not represented in mapper's table. Use the `column_property()` function to force this column to be mapped as a read-only attribute." % str(c)) + mapped_column.append(mc) + return ColumnProperty(*mapped_column) </ins><span class="cx"> </span><span class="cx"> def _adapt_inherited_property(self, key, prop): </span><span class="cx"> if not self.concrete: </span><span class="cx"> self._compile_property(key, prop, init=False, setparent=False) </span><span class="cx"> # TODO: concrete properties dont adapt at all right now....will require copies of relations() etc. </span><span class="cx"> </span><del>- def _compile_property(self, key, prop, init=True, skipmissing=False, setparent=True): </del><ins>+ def _compile_property(self, key, prop, init=True, setparent=True): </ins><span class="cx"> """Add a ``MapperProperty`` to this or another ``Mapper``, </span><span class="cx"> including configuration of the property. </span><span class="cx"> </span><span class="lines">@@ -833,7 +824,7 @@ </span><span class="cx"> self.__log("_compile_property(%s, %s)" % (key, prop.__class__.__name__)) </span><span class="cx"> </span><span class="cx"> if not isinstance(prop, MapperProperty): </span><del>- col = self._create_prop_from_column(prop, skipmissing=skipmissing) </del><ins>+ col = self._create_prop_from_column(prop) </ins><span class="cx"> if col is None: </span><span class="cx"> raise exceptions.ArgumentError("%s=%r is not an instance of MapperProperty or Column" % (key, prop)) </span><span class="cx"> prop = col </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyormstrategiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/orm/strategies.py (2581 => 2582)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/orm/strategies.py 2007-04-29 21:33:05 UTC (rev 2581) +++ sqlalchemy/trunk/lib/sqlalchemy/orm/strategies.py 2007-04-29 22:26:39 UTC (rev 2582) </span><span class="lines">@@ -23,7 +23,11 @@ </span><span class="cx"> def setup_query(self, context, eagertable=None, **kwargs): </span><span class="cx"> for c in self.columns: </span><span class="cx"> if eagertable is not None: </span><del>- context.statement.append_column(eagertable.corresponding_column(c)) </del><ins>+ conv = eagertable.corresponding_column(c, raiseerr=False) + if conv: + context.statement.append_column(conv) + else: + context.statement.append_column(c) </ins><span class="cx"> else: </span><span class="cx"> context.statement.append_column(c) </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemysqlpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/sql.py (2581 => 2582)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/sql.py 2007-04-29 21:33:05 UTC (rev 2581) +++ sqlalchemy/trunk/lib/sqlalchemy/sql.py 2007-04-29 22:26:39 UTC (rev 2582) </span><span class="lines">@@ -1617,7 +1617,7 @@ </span><span class="cx"> if not raiseerr: </span><span class="cx"> return None </span><span class="cx"> else: </span><del>- raise exceptions.InvalidRequestError("Given column '%s', attached to table '%s', failed to locate a corresponding column from table '%s'" % (str(column), str(column.table), self.name)) </del><ins>+ raise exceptions.InvalidRequestError("Given column '%s', attached to table '%s', failed to locate a corresponding column from table '%s'" % (str(column), str(getattr(column, 'table', None)), self.name)) </ins><span class="cx"> </span><span class="cx"> def _get_exported_attribute(self, name): </span><span class="cx"> try: </span></span></pre></div> <a id="sqlalchemytrunktestormmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/orm/mapper.py (2581 => 2582)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/orm/mapper.py 2007-04-29 21:33:05 UTC (rev 2581) +++ sqlalchemy/trunk/test/orm/mapper.py 2007-04-29 22:26:39 UTC (rev 2582) </span><span class="lines">@@ -21,6 +21,9 @@ </span><span class="cx"> pass </span><span class="cx"> </span><span class="cx"> class MapperTest(MapperSuperTest): </span><ins>+ # TODO: MapperTest has grown much larger than it originally was and needs + # to be broken up among various functions, including querying, session operations, + # mapper configurational issues </ins><span class="cx"> def testget(self): </span><span class="cx"> s = create_session() </span><span class="cx"> mapper(User, users) </span><span class="lines">@@ -248,24 +251,6 @@ </span><span class="cx"> </span><span class="cx"> s.refresh(u) #hangs </span><span class="cx"> </span><del>- def testmagic(self): - """not sure what this is really testing.""" - mapper(User, users, properties = { - 'addresses' : relation(mapper(Address, addresses)) - }) - sess = create_session() - l = sess.query(User).select_by(user_name='fred') - self.assert_result(l, User, *[{'user_id':9}]) - u = l[0] - - u2 = sess.query(User).get_by_user_name('fred') - self.assert_(u is u2) - - l = sess.query(User).select_by(email_address='ed...@be...') - self.assert_result(l, User, *[{'user_id':8}]) - - l = sess.query(User).select_by(User.c.user_name=='fred', addresses.c.email_address!='ed...@be...', user_id=9) - </del><span class="cx"> def testprops(self): </span><span class="cx"> """tests the various attributes of the properties attached to classes""" </span><span class="cx"> m = mapper(User, users, properties = { </span><span class="lines">@@ -273,8 +258,8 @@ </span><span class="cx"> }).compile() </span><span class="cx"> self.assert_(User.addresses.property is m.props['addresses']) </span><span class="cx"> </span><del>- def testload(self): - """tests loading rows with a mapper and producing object instances""" </del><ins>+ def testquery(self): + """test a basic Query.select() operation.""" </ins><span class="cx"> mapper(User, users) </span><span class="cx"> l = create_session().query(User).select() </span><span class="cx"> self.assert_result(l, User, *user_result) </span><span class="lines">@@ -456,7 +441,46 @@ </span><span class="cx"> print "User", u.user_id, u.user_name, u.concat, u.count </span><span class="cx"> assert l[0].concat == l[0].user_id * 2 == 14 </span><span class="cx"> assert l[1].concat == l[1].user_id * 2 == 16 </span><ins>+ + def testexternalcolumns(self): + """test creating mappings that reference external columns or functions""" + + f = (users.c.user_id *2).label('concat') + try: + mapper(User, users, properties={ + 'concat': f, + }) + class_mapper(User) + except exceptions.ArgumentError, e: + assert str(e) == "Column '%s' is not represented in mapper's table. Use the `column_property()` function to force this column to be mapped as a read-only attribute." % str(f) + clear_mappers() </ins><span class="cx"> </span><ins>+ mapper(Address, addresses, properties={ + 'user':relation(User, lazy=False) + }) + + mapper(User, users, properties={ + 'concat': column_property(f), + 'count': column_property(select([func.count(addresses.c.address_id)], users.c.user_id==addresses.c.user_id, scalar=True).label('count')) + }) + + sess = create_session() + l = sess.query(User).select() + for u in l: + print "User", u.user_id, u.user_name, u.concat, u.count + assert l[0].concat == l[0].user_id * 2 == 14 + assert l[1].concat == l[1].user_id * 2 == 16 + + ### eager loads, not really working across all DBs, no column aliasing in place so + # results still wont be good for larger situations + #l = sess.query(Address).select() + l = sess.query(Address).options(lazyload('user')).select() + for a in l: + print "User", a.user.user_id, a.user.user_name, a.user.concat, a.user.count + assert l[0].user.concat == l[0].user.user_id * 2 == 14 + assert l[1].user.concat == l[1].user.user_id * 2 == 16 + + </ins><span class="cx"> @testbase.unsupported('firebird') </span><span class="cx"> def testcount(self): </span><span class="cx"> """test the count function on Query. </span><span class="lines">@@ -563,9 +587,6 @@ </span><span class="cx"> assert not hasattr(l.addresses[0], 'TEST') </span><span class="cx"> assert not hasattr(l.addresses[0], 'TEST2') </span><span class="cx"> </span><del>- - - </del><span class="cx"> def testeageroptions(self): </span><span class="cx"> """tests that a lazy relation can be upgraded to an eager relation via the options method""" </span><span class="cx"> sess = create_session() </span></span></pre> </div> </div> </body> </html> |